Files
serverAppConfigs/dankclaw/observability/quadlet/grafana.container
T
Frank 58184008dd chore(images): refresh every registry pin to current upstream digest
Container images re-resolved against their registries today; each pin is the
manifest-list digest the tag answers now, so no container is served from a
build that upstream has already replaced.

Version-tag moves: authentik 2026.8.3 -> 2026.8.5 (server/worker/outpost and
the ldap image, which now carries the tag instead of a bare digest so the pair
can never drift apart), seerr v3.4.1 -> v3.5.1, calibre 9.15.0 -> 9.17.0,
calibre-web 0.6.27-ls402 -> ls407, plex ls329 -> ls330, tautulli
v2.18.1-ls245 -> v2.18.2-ls250.

Digest-only moves inside the same stream: postgres 16-alpine, mariadb 11,
redis 7-alpine, valkey 9-alpine, grafana, alertmanager, gluetun, searxng,
open-webui, jellyfin, romm, chromedp/headless-shell, lightpanda/browser.

Rollback is the previous digest on the same Image= line.
2026-10-30 15:00:52 -07:00

95 lines
4.9 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@sha256:488984b716c0862639f91a698adbe2862677a5a07bad42da5f03f3030883b205
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/
# Standard OIDC userinfo. Grafana v13 fetches <api_url>/emails for the address
# ONLY when the token carries no `email` claim (authentik has no /emails path and
# 404s it, failing the login) — so the claim must actually be there: the fix is
# the grafana-email-scope mapping (see scripts/authentik-grafana-sso-fix.sh),
# not a Grafana flag. Do NOT append ?api=1 to this URL: Grafana then GETs
# <...>/userinfo/?api=1/emails, a 403.
Environment=GF_AUTH_GENERIC_OAUTH_API_URL=https://auth.franksdank.cloud/application/o/userinfo/
# authentik's scopes are openid/email/profile. `user:email` (a GitLab-ism) is
# not a known scope here; requesting it makes the authorize view crash.
# Quoted: quadlet word-splits Environment=, so an unquoted spaced list
# truncates SCOPES to its first token.
Environment="GF_AUTH_GENERIC_OAUTH_SCOPES=openid email profile"
Environment=GF_AUTH_GENERIC_OAUTH_EMAIL_ATTRIBUTE_PATH=email
Environment=GF_AUTH_GENERIC_OAUTH_LOGIN_ATTRIBUTE_PATH=sub
Environment=GF_AUTH_GENERIC_OAUTH_NAME_ATTRIBUTE_PATH=preferred_username
# First SSO login auto-provisions the user. Safe: unauthenticated traffic never
# reaches this callback — the Anubis -> Authentik guard fronts every request.
Environment=GF_AUTH_GENERIC_OAUTH_ALLOW_SIGN_UP=true
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