fix(observability): seed grafana's OAuth secret into a per-service env file
The first deploy of the grafana unit failed: 'stat /root/.config: permission denied', exit 1, restart loop. Cause — the repo-root .env that the compose service used to pick up is a gluetun env file, and it carries HOME=/root (and the WireGuard keys). Pointing the unit's [Service] EnvironmentFile at it sets HOME for the service, so podman looks for its configuration under /root. grafana now reads /home/dank/Containers/grafana/env, the per-service file the deployer seeds from dankclaw/.env with a single-variable filter — the same shape caddy, pihole, copyparty, romm and seerr use. The seed is validated, so a missing secret fails the deploy loudly instead of starting a container whose OAuth login cannot complete. The value was copied to dankclaw/.env, which is where dankclaw/.env.example already documented it.
This commit is contained in:
@@ -62,9 +62,16 @@ HealthRetries=3
|
||||
HealthStartPeriod=60s
|
||||
|
||||
[Service]
|
||||
# systemd-side only: supplies ${GRAFANA_OAUTH_CLIENT_SECRET} for expansion
|
||||
# above. The container does not receive this file.
|
||||
EnvironmentFile=/home/dank/serverAppConfigs/.env
|
||||
# systemd-side only: supplies ${GRAFANA_OAUTH_CLIENT_SECRET} for the expansion
|
||||
# above. The container never receives this file.
|
||||
#
|
||||
# It is the per-service env file the deployer seeds from dankclaw/.env — the
|
||||
# fleet convention (caddy, pihole, copyparty, romm and seerr all read one).
|
||||
# The repo-root .env that the compose service happened to pick up is a gluetun
|
||||
# env file: it carries HOME=/root and the WireGuard keys, and importing it into
|
||||
# this service makes podman look for its configuration under /root, which it
|
||||
# cannot read, so the unit fails to start. Seed the value into dankclaw/.env.
|
||||
EnvironmentFile=/home/dank/Containers/grafana/env
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
|
||||
|
||||
@@ -378,6 +378,24 @@ deploy_compose_service() {
|
||||
esac
|
||||
}
|
||||
|
||||
# grafana is the one observability service that holds a credential: the
|
||||
# Authentik OAuth2 client secret. It is seeded into a per-service env file (the
|
||||
# fleet convention: caddy, pihole, copyparty, romm, seerr) instead of being
|
||||
# imported from the repo .env wholesale. The repo-root .env that the compose
|
||||
# service used to pick up is a gluetun env file — it also carries HOME=/root
|
||||
# and the WireGuard keys, and an EnvironmentFile of that file sets HOME for the
|
||||
# service, which makes podman look for its config under /root and fail.
|
||||
seed_grafana_env() {
|
||||
local dest=/home/dank/Containers/grafana/env
|
||||
mkdir -p /home/dank/Containers/grafana
|
||||
if [[ ! -f "$dest" ]]; then
|
||||
awk -F= '/^GRAFANA_OAUTH_CLIENT_SECRET=/ { print }' dankclaw/.env > "$dest"
|
||||
chmod 600 "$dest"
|
||||
fi
|
||||
grep -q '^GRAFANA_OAUTH_CLIENT_SECRET=.' "$dest" \
|
||||
|| { echo "grafana env missing GRAFANA_OAUTH_CLIENT_SECRET; set it in dankclaw/.env" >&2; return 1; }
|
||||
}
|
||||
|
||||
# The five observability containers left compose on 2026-09-11 (they were
|
||||
# started by hand with podman-compose and never came back after a reboot).
|
||||
# They are ordinary rootless Quadlet units now: same images, same mounts, same
|
||||
@@ -399,7 +417,10 @@ deploy_observability() {
|
||||
copy_alloy_config
|
||||
build_compose_image alloy
|
||||
;;
|
||||
grafana|node-exporter)
|
||||
grafana)
|
||||
seed_grafana_env || return 1
|
||||
;;
|
||||
node-exporter)
|
||||
;;
|
||||
*)
|
||||
echo "unknown observability service: $service" >&2
|
||||
|
||||
@@ -146,17 +146,32 @@ service_section() {
|
||||
done
|
||||
}
|
||||
|
||||
@test "grafana receives the OAuth client secret without receiving the whole .env" {
|
||||
# systemd expands ${GRAFANA_OAUTH_CLIENT_SECRET} in [Container] from the
|
||||
# [Service] EnvironmentFile. Putting that file in [Container] would hand
|
||||
# the container every fleet secret in it (Porkbun keys, Authentik keys,
|
||||
# the sudo password file's neighbours) for the sake of one value.
|
||||
@test "grafana gets its OAuth client secret without getting a whole env file" {
|
||||
# systemd expands ${GRAFANA_OAUTH_CLIENT_SECRET} for the [Container]
|
||||
# Environment= line from the [Service] EnvironmentFile, which keeps the
|
||||
# file out of the container entirely. That file is the per-service one the
|
||||
# deployer seeds (fleet convention: caddy, pihole, copyparty, romm, seerr)
|
||||
# — NOT a repo .env. The repo-root .env the compose service happened to
|
||||
# read is a gluetun env file carrying HOME=/root and the WireGuard keys;
|
||||
# importing it sets HOME for this service and podman then looks for its
|
||||
# configuration under /root and refuses to start.
|
||||
local unit="$UNIT_DIR/grafana.container"
|
||||
service_section "$unit" | grep -q '^EnvironmentFile=/home/dank/serverAppConfigs/.env$'
|
||||
service_section "$unit" | grep -q '^EnvironmentFile=/home/dank/Containers/grafana/env$'
|
||||
! service_section "$unit" | grep -q 'serverAppConfigs'
|
||||
! container_section "$unit" | grep -q '^EnvironmentFile='
|
||||
container_section "$unit" | grep -q '^Environment=GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET=\${GRAFANA_OAUTH_CLIENT_SECRET}$'
|
||||
}
|
||||
|
||||
@test "the deployer seeds exactly one value into the grafana env file" {
|
||||
# The single-variable filter is the point: a whole-file copy is how the
|
||||
# HOME=/root failure got in.
|
||||
grep -q '^seed_grafana_env() {' "$REMOTE_DEPLOY"
|
||||
grep -q '^ local dest=/home/dank/Containers/grafana/env$' "$REMOTE_DEPLOY"
|
||||
grep -q "awk -F= '/^GRAFANA_OAUTH_CLIENT_SECRET=/ { print }' dankclaw/.env > \"\$dest\"" "$REMOTE_DEPLOY"
|
||||
grep -q "grep -q '\^GRAFANA_OAUTH_CLIENT_SECRET=\.' \"\$dest\"" "$REMOTE_DEPLOY"
|
||||
grep -q 'seed_grafana_env || return 1' "$REMOTE_DEPLOY"
|
||||
}
|
||||
|
||||
@test "observability units hold no literal secret material" {
|
||||
# A secret-named setting is allowed only when its value is a ${VAR}
|
||||
# reference resolved from an EnvironmentFile systemd reads; a literal value
|
||||
|
||||
Reference in New Issue
Block a user