Files
homelab/services/vpn/headplane.nix
T
darmanandClaude Sonnet 5 6f24ab69ad docs: condense comments across the repo
Comments had drifted into multi-paragraph narrative (git commit
lineage, debugging stories, restated code) in several hot spots
(scripts/deploy, hermes-agent.nix, flake.nix, gitea.nix, headscale.nix).
Trim every comment to its load-bearing "why" — gotchas, safety
warnings, and non-obvious rationale survive verbatim in substance,
just tightened to 1-2 sentences; historical narrative and anything
already covered in CLAUDE.md is cut. No code/logic changed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UJqEmY1y3AYX3JoX4Y6b21
2026-09-18 21:36:30 +02:00

56 lines
2.5 KiB
Nix

{ config, ... }:
# Headplane — web UI for headscale (services/vpn/headscale.nix; enable first),
# running as headscale's OS user.
#
# It reads headscale's config from the nix store, so the UI DISPLAYS settings
# but can't change them (edit here and rebuild instead) — except DNS
# extra-records, which are data rather than config, hence the writable
# extra_records file below.
#
# Served at vpn.mgaction.town/admin (path-routed with headscale, see
# hosts/neptun/configuration.nix); base_url excludes the /admin prefix, which
# Headplane appends itself including for the OIDC callback.
#
# Auth is Authentik via OIDC; client_id/client_secret/API key are placeholders
# until Authentik/headscale are deployed (direct API-key login works as a
# fallback until then). Once live:
# 1. In Authentik: create an OAuth2/OpenID Provider + Application with slug
# `headplane` and redirect URI
# https://vpn.mgaction.town/admin/oidc/callback. Copy the generated
# client ID into oidc.client_id below.
# 2. `./scripts/edit_secrets secrets/neptun.yaml` and replace
# headplane_oidc_client_secret with the provider's client secret.
# 3. `headscale apikeys create` on the box, and replace
# headplane_headscale_api_key the same way.
{
# Writable DNS extra-records, shared by both services (they run as the same
# user). tmpfiles seeds an empty JSON array — headscale won't start against
# a missing or unparseable file.
systemd.tmpfiles.rules = [
"d /var/lib/headscale 0750 headscale headscale -"
"f /var/lib/headscale/extra_records.json 0640 headscale headscale - []"
];
services.headscale.settings.dns.extra_records_path = "/var/lib/headscale/extra_records.json";
services.headplane = {
enable = true;
settings.headscale.dns_records_path = "/var/lib/headscale/extra_records.json";
settings.server = {
cookie_secret_path = config.sops.secrets.headplane_cookie_secret.path;
cookie_secure = true; # served over HTTPS via Caddy
base_url = "https://vpn.mgaction.town";
};
settings.oidc = {
issuer = "https://auth.mgaction.town/application/o/headplane/";
# Not a secret — Authentik hands the client ID out at the authorize
# endpoint. Regenerating the provider in Authentik changes it.
client_id = "NNzYUrSBlCqxyCTfxlEJRpT5v5EQWHLpfOsXBine";
client_secret_path = config.sops.secrets.headplane_oidc_client_secret.path;
headscale_api_key_path = config.sops.secrets.headplane_headscale_api_key.path;
};
};
}