The Authentik SSO button returned "Server Error" (500) because the grafana
provider authorization_flow was NULL and the authorize view crashed building a
None flow planner. After that, the login failed with "InternalError": no email
claim reached Grafana, so it fell back to GET <api_url>/emails, which authentik
404s.
Unit env: request authentik real scopes (openid email profile, not the GitLab
user:email that crashes the authorize view); read claims from the id_token
(USE_ID_TOKEN=true) and drop ?api=1 from the API URL (Grafana v13 turns it into
a /emails 404); pin email/sub/preferred_username attribute paths; allow
first-login provisioning since the Anubis->Authentik guard fronts the callback.
scripts/authentik-grafana-sso-fix.sh idempotently re-applies the two IdP-side
causes (NULL authorization_flow; the grafana-email-scope mapping that returned
a bare string instead of {"email":..}). Verified end to end: RP login lands on /
and /api/user answers 200 with the real email, user provisioned as Viewer.
94 lines
4.7 KiB
INI
94 lines
4.7 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/
|
|
# Read claims from the signed ID token. Grafana v13 always appends /emails to
|
|
# API_URL and GETs it as a userinfo endpoint (authentik has no such path and
|
|
# 404s it, failing the login). USE_ID_TOKEN makes Grafana take sub/email/name
|
|
# from the id_token, which authentik fills from the openid/email/profile scopes.
|
|
Environment=GF_AUTH_GENERIC_OAUTH_USE_ID_TOKEN=true
|
|
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
|