grafana/prometheus: permission the directory, not the container's files

Both apps ran `chmod -R 777` over their data dirs in install_post_start —
after the container has booted and written files as its own uid (grafana
472 -> host subuid 231543, prometheus nobody 65534 -> 296605). chmod by a
non-owner fails, so every REINSTALL printed "Operation not permitted" per
file and failed the step; a fresh install passed only because the dir was
still empty when it ran. Reproduced on a live install of both.

The permission is only needed on the DIRECTORY, so the container can
create its store on first boot. What it creates after that is its own and
must stay that way — chowning or chmod'ing it away is what would actually
break these apps. So: non-recursive 0777 on grafana_storage and prom_data.

prometheus's config dir is a separate case — the container only READS it —
so it gets a+rX,go-w instead. The go-w matters: a+rX only adds bits, so
without it prometheus.yml stays world-writable on every install the old
777 already touched, and prometheus obeys that file. Everything there is
written through runFileOp, i.e. by the owner, so owner-write is enough.

updateFileOwnership used `runSystem chown`, but the scoped sudoers grants
the manager root only for the fixed LibrePortal helpers and
systemctl/ufw/nft/sysctl — never a bare chown, which would be
root-equivalent. It was denied on every call ("I'm sorry libreportal"),
printing a red ✗ Error on every prometheus install, and its message
referenced an undefined $user_name so it read "with  ownership". Use
runFileOp (runs as the owner of the data plane) and name the user.

Verified live: prometheus and grafana both installed fresh and reinstalled
with 0 errors; prometheus.yml went 0777 -> 0755 with prometheus still
healthy (200); grafana serving 200; grafana.db and prom_data/data keep
their container uids.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
librelad 2026-08-18 23:39:44 +01:00
parent 6cc604f21f
commit 5c7372b8c2
3 changed files with 48 additions and 7 deletions

View File

@ -15,9 +15,16 @@ grafana_install_pre()
grafana_install_post_start()
{
local app_name="$1"
# The DIRECTORY, not its contents. Grafana runs as uid 472, which rootless
# maps to a subuid this user has no authority over; it needs write on the dir
# itself to create grafana.db and its plugin/png dirs on first boot, and from
# then on those files are its own. `chmod -R` walked them as the docker
# install user and failed per file with "Operation not permitted", failing the
# step on every reinstall — while a fresh install passed, because the dir was
# still empty. Grafana's files must keep Grafana's ownership regardless.
if [ -d "${containers_dir}grafana/grafana_storage" ]; then
local result
result=$(runFileOp chmod -R 777 "${containers_dir}grafana/grafana_storage")
result=$(runFileOp chmod 0777 "${containers_dir}grafana/grafana_storage")
checkSuccess "Set permissions to grafana_storage folder."
fi
}

View File

@ -26,14 +26,36 @@ prometheus_install_post_start()
if [ -f "${containers_dir}prometheus/prometheus/prometheus.yml" ]; then
updateFileOwnership "${containers_dir}prometheus/prometheus/prometheus.yml" $docker_install_user $docker_install_user
fi
# Prometheus runs as nobody (65534) inside the container, which rootless maps
# to a subuid outside this user's authority (65534 -> 296605 here). Two
# different needs, and the old blanket `chmod -R 777` on both got the second
# one wrong:
#
# prometheus/ config, READ-only to the container. a+rX is enough — world
# WRITE on a config the container obeys is not something to
# hand out, and -R is safe here because nothing but LibrePortal
# writes this dir. go-w is included so the fix actually lands on
# installs the old 777 already touched: a+rX only ADDS bits, so
# without it every existing prometheus.yml stays world-writable
# by any local user. Everything here is written through
# runFileOp — i.e. by the owner — so owner-write is all it needs.
#
# prom_data/ the container's own store. It needs write on the DIRECTORY to
# create prom_data/data on first boot; everything under that is
# created by, and belongs to, prometheus itself. Recursing into
# it meant chmod'ing files owned by 296605 as the docker install
# user: "Operation not permitted" per file and a failed step on
# every REINSTALL (a fresh install passed only because the dir
# was still empty). Those files must keep prometheus's ownership
# anyway — that is what lets it write them.
if [ -d "${containers_dir}prometheus/prometheus" ]; then
local result
result=$(runFileOp chmod -R 777 "${containers_dir}prometheus/prometheus")
result=$(runFileOp chmod -R a+rX,go-w "${containers_dir}prometheus/prometheus")
checkSuccess "Set permissions to prometheus folder."
fi
if [ -d "${containers_dir}prometheus/prom_data" ]; then
local result
result=$(runFileOp chmod -R 777 "${containers_dir}prometheus/prom_data")
result=$(runFileOp chmod 0777 "${containers_dir}prometheus/prom_data")
checkSuccess "Set permissions to prom_data folder."
fi
}

View File

@ -1,13 +1,25 @@
#!/bin/bash
# Set owner:group on a single file under the data plane.
#
# runFileOp, not runSystem: the scoped sudoers grants the manager root for the
# fixed LibrePortal helpers and systemctl/ufw/nft/sysctl only — never a bare
# `sudo chown`, since that is root-equivalent (chown /etc/sudoers). `runSystem
# chown` therefore could not succeed on any correctly-installed system; it was
# denied every time, and because the result feeds checkSuccess it printed a red
# ✗ Error on every prometheus install. runFileOp runs as the owner of the data
# plane (the docker install user under rootless, the manager under rooted), which
# is exactly who may set these.
#
# Ownership that genuinely needs root — establishing the /docker model, reclaiming
# a root-owned file — belongs in the libreportal-ownership helper instead.
updateFileOwnership()
{
local file="$1"
local file_name=$(basename "$file")
local clean_dir=$(echo "$file" | sed 's#//*#/#g')
local user_name_1="$2"
local user_name_2="$3"
local result; result=$(runSystem chown $user_name_1:$user_name_2 "$file")
checkSuccess "Updating $file_name with $user_name ownership"
local result; result=$(runFileOp chown "$user_name_1:$user_name_2" "$file")
checkSuccess "Updating $file_name with $user_name_1 ownership"
}