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.
80 lines
3.8 KiB
INI
80 lines
3.8 KiB
INI
# Quadlet unit: grafana (rootless, boot-persistent).
|
|
#
|
|
# Install path on dankclaw: ~/.config/containers/systemd/grafana.container
|
|
# Unit name: grafana.service (container name: grafana)
|
|
#
|
|
# Dashboards. This replaces the compose service of the same name in
|
|
# dankclaw/compose.yaml ("Observability" block).
|
|
#
|
|
# PUBLISHING. Bound to 127.0.0.1 for the same reason as prometheus: the browser
|
|
# path is https://grafana.franksdank.cloud (Caddy → Anubis → Authentik outpost →
|
|
# grafana:3000 over dankclaw_default) and the host-side homepage status timer
|
|
# probes 127.0.0.1:3002. Publishing on 0.0.0.0 as the compose service did left
|
|
# an off-host path into Grafana that skips the guard chain entirely.
|
|
#
|
|
# AUTH. Grafana is an OAuth2 relying party against Authentik (OAuth2Provider
|
|
# "grafana", confidential client). The guard chain fronts unauthenticated
|
|
# traffic; the login itself happens through the Authentik button inside
|
|
# Grafana. The client secret is NOT written here: it is expanded by systemd
|
|
# from the root .env (EnvironmentFile in [Service], never in [Container], so
|
|
# the container receives exactly this one secret and not the whole file).
|
|
# Without that .env entry the unit will not start — the same dependency every
|
|
# other rendered-secret deploy has.
|
|
#
|
|
# STATE. /opt/grafana/data holds grafana.db. /opt/grafana/provisioning is
|
|
# mounted read-only and was EMPTY as of 2026-09-11, and grafana.db reported
|
|
# zero datasources and zero dashboards: this Grafana is an empty install whose
|
|
# only configured account is the local admin. Nothing here queries Prometheus
|
|
# or Loki yet.
|
|
|
|
[Unit]
|
|
Description=Grafana dashboards (Dankclaw)
|
|
After=network-online.target prometheus.service
|
|
Wants=network-online.target prometheus.service
|
|
|
|
[Container]
|
|
ContainerName=grafana
|
|
Image=docker.io/grafana/grafana:latest
|
|
Network=dankclaw_default
|
|
User=0:0
|
|
PublishPort=127.0.0.1:3002:3000
|
|
Environment=GF_SERVER_ROOT_URL=https://grafana.franksdank.cloud
|
|
Environment=GF_AUTH_GENERIC_OAUTH_ENABLED=true
|
|
Environment=GF_AUTH_GENERIC_OAUTH_NAME=Authentik
|
|
Environment=GF_AUTH_GENERIC_OAUTH_CLIENT_ID=grafana-21ca190cde75d60f
|
|
Environment=GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET=${GRAFANA_OAUTH_CLIENT_SECRET}
|
|
# Global authorize path: this Authentik build resolves the provider by the
|
|
# client_id query parameter — /application/o/<slug>/authorize/ does not exist.
|
|
Environment=GF_AUTH_GENERIC_OAUTH_AUTH_URL=https://auth.franksdank.cloud/application/o/authorize/
|
|
Environment=GF_AUTH_GENERIC_OAUTH_TOKEN_URL=https://auth.franksdank.cloud/application/o/token/
|
|
# ?api=1 = introspection-style userinfo: returns custom claims (groups) too.
|
|
Environment=GF_AUTH_GENERIC_OAUTH_API_URL=https://auth.franksdank.cloud/application/o/userinfo/?api=1
|
|
Environment=GF_AUTH_GENERIC_OAUTH_ALLOW_SIGN_UP=false
|
|
Environment=GF_AUTH_GENERIC_OAUTH_AUTO_UPSERT=true
|
|
Environment=GF_AUTH_GENERIC_OAUTH_ALLOW_ASSIGN_ORG=true
|
|
Environment=GF_AUTH_GENERIC_OAUTH_ORG_ID=1
|
|
Volume=/opt/grafana/data:/var/lib/grafana
|
|
Volume=/opt/grafana/provisioning:/etc/grafana/provisioning:ro
|
|
HealthCmd=wget --spider -q http://localhost:3000/api/health
|
|
HealthInterval=30s
|
|
HealthTimeout=10s
|
|
HealthRetries=3
|
|
HealthStartPeriod=60s
|
|
|
|
[Service]
|
|
# 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
|
|
|
|
[Install]
|
|
WantedBy=default.target
|