Authentik SSO + homepage naked domain
Auth layer (Authentik 2026.8.0): - compose: 3-service stack (authentik-postgres, authentik-server, authentik-worker), postgres health-gated, bind mounts under /home/dank/authentik - Caddyfile: auth.franksdank.cloud vhost; forward_auth on the no-auth vhosts (homepage, SearXNG, Hermes, Prometheus, RomM, Copyparty); outpost path proxied first in a route block - .env.example: AUTHENTIK_PG_PASS + AUTHENTIK_SECRET_KEY - deploy.sh: render only CADDY_BASICAUTH_HASH (the homepage per-user hashes are retired; grafana + pihole keep basicauth) - tests/authentik.bats (new) + updated homepage/caddyfile/pihole seams - docs: Authentik.md + per-service pages; Caddy.md + Homepage.md updated Homepage (naked domain): - Caddyfile: dankclaw.franksdank.cloud -> franksdank.cloud www.franksdank.cloud; dankclaw + dankserver redirect to it; bare-IP catch-all updated - addn-hosts: A records for the naked domain, www, and auth (all -> 143) - tests/pihole.bats: vhost_names handles multi-address blocks; TLD check allows the naked domain All 146 tests pass.
This commit is contained in:
1 parent
7ba1acdbc5
commit
3cc99d81cc
17 files changed
+634
-137
No files matched your search
+14
-8
@@ -25,16 +25,14 @@ GRAFANA_ADMIN_USER=admin
|
||||
GRAFANA_ADMIN_PASSWORD=changeme
|
||||
|
||||
# Caddy (the homelab's single reverse proxy, on this host)
|
||||
# bcrypt hashes (caddy hash-password). deploy.sh renders them into
|
||||
# bcrypt hash (caddy hash-password). deploy.sh renders it into
|
||||
# /home/dank/Containers/caddy/Caddyfile.
|
||||
# CADDY_BASICAUTH_HASH: the shared family password (the other vhosts: gitea,
|
||||
# grafana, search, ...). Username: dank.
|
||||
# CADDY_BASICAUTH_HASH: the shared family password, still used by the
|
||||
# vhosts that carry basicauth rather than Authentik forward_auth
|
||||
# (grafana, pihole). Username: dankuser.
|
||||
CADDY_BASICAUTH_HASH=changeme
|
||||
# CADDY_ADMIN_HASH: the operator-only password (the homepage /admin/ page).
|
||||
# Username: frank.
|
||||
CADDY_ADMIN_HASH=changeme
|
||||
# CADDY_HOMEPAGE_HASH: the homepage user-page password. Username: dankuser.
|
||||
CADDY_HOMEPAGE_HASH=changeme
|
||||
# (The homepage's per-user hashes were retired: the homepage and its /admin/
|
||||
# page are now behind Authentik forward_auth, not basicauth.)
|
||||
|
||||
# Hermes -> Signal (signal-cli daemon on the host, port 8080)
|
||||
# E.164 number of the linked Signal account (your primary number, for Note-to-Self).
|
||||
@@ -48,3 +46,11 @@ SIGNAL_ALLOWED_USERS=+15551234567
|
||||
# PORKBUN_API_SECRET_KEY is the name the caddy-dns/porkbun module expects.
|
||||
PORKBUN_API_KEY=changeme
|
||||
PORKBUN_API_SECRET_KEY=changeme
|
||||
|
||||
# Authentik (SSO / OIDC provider)
|
||||
# AUTHENTIK_PG_PASS: the PostgreSQL password for authentik-postgres.
|
||||
# Generate: openssl rand -base64 32
|
||||
AUTHENTIK_PG_PASS=changeme
|
||||
# AUTHENTIK_SECRET_KEY: the server's secret key (64+ chars).
|
||||
# Generate: openssl rand -base64 48
|
||||
AUTHENTIK_SECRET_KEY=changeme
|
||||
+93
-55
@@ -11,16 +11,19 @@
|
||||
# (dankclaw/pihole/addn-hosts — every name resolves to 192.168.0.143)
|
||||
# and handed to LAN clients by the SURFboard G36 router.
|
||||
#
|
||||
# The basicauth hash below is a placeholder. deploy.sh renders the real
|
||||
# hash (CADDY_BASICAUTH_HASH from the host .env, git-ignored) into
|
||||
# /home/dank/Containers/caddy/Caddyfile, which the container mounts.
|
||||
# Authentication: Caddy forward_auth (Authentik SSO) protects the vhosts that
|
||||
# do not have their own application-level auth (homepage, SearXNG, Hermes,
|
||||
# Prometheus, RomM, Copyparty). The basicauth hash is a placeholder; deploy.sh
|
||||
# renders the real hash (CADDY_BASICAUTH_HASH from the host .env, git-ignored)
|
||||
# into /home/dank/Containers/caddy/Caddyfile, which the container mounts.
|
||||
# Apps with their own auth (Grafana, Gitea, OpenWebUI, Jellyfin, *arr, Pi-hole)
|
||||
# are not wrapped in forward_auth — they handle their own login.
|
||||
#
|
||||
# TLS: real Let's Encrypt certs. Site addresses carry no http:// prefix,
|
||||
# so Caddy serves HTTPS (and auto-redirects HTTP). Certs are wildcard
|
||||
# (dns-01) via the global acme_dns block below, using the Porkbun module
|
||||
# built into the franksdank/caddy-porkbun image (caddy/Containerfile).
|
||||
# That needs no inbound ports and works from the LAN, where dnsmasq
|
||||
# resolves these names to 192.168.0.143.
|
||||
# so Caddy serves HTTPS (and auto-redirects HTTP). Certs are issued via
|
||||
# dns-01 through the Porkbun module built into the franksdank/caddy-porkbun
|
||||
# image (caddy/Containerfile). That needs no inbound ports and works from the
|
||||
# LAN, where Pi-hole resolves these names to 192.168.0.143.
|
||||
|
||||
{
|
||||
# Admin stays ON: it binds to 127.0.0.1:2019 inside the container and the
|
||||
@@ -41,51 +44,61 @@
|
||||
|
||||
# --- Bare-IP access (health checks, convenience) ---
|
||||
:80 {
|
||||
redir https://dankclaw.franksdank.cloud/ 302
|
||||
redir https://franksdank.cloud/ 302
|
||||
}
|
||||
|
||||
# --- Homepage (password protected) ---
|
||||
dankclaw.franksdank.cloud {
|
||||
# --- Homepage (Authentik SSO) ---
|
||||
franksdank.cloud www.franksdank.cloud {
|
||||
root * /srv/homepage
|
||||
|
||||
# Operator-only admin page (Q19). Auth is checked on the original path,
|
||||
# before any rewrite, so the path matcher is reliable. Caddy sorts a
|
||||
# top-level 'rewrite' before 'basicauth'; that would change the path and
|
||||
# break the path-based auth matchers. 'handle' preserves order.
|
||||
handle /admin* {
|
||||
basicauth {
|
||||
frank REPLACE_WITH_ADMIN_HASH
|
||||
}
|
||||
try_files /admin.html
|
||||
file_server
|
||||
}
|
||||
route {
|
||||
# Authentik outpost (login UI + session management).
|
||||
reverse_proxy /outpost.goauthentik.io/* authentik-server:9000
|
||||
|
||||
# Shared data + assets: the family OR the operator, so both pages can load
|
||||
# them (the browser reuses the page's credential for these fetches). A
|
||||
# 'handle' inline matcher is a single token, so the multi-path match is a
|
||||
# named matcher.
|
||||
@data path /services.json /status.json /app.js /style.css /deployed.js
|
||||
handle @data {
|
||||
basicauth {
|
||||
dankuser REPLACE_WITH_HOMEPAGE_HASH
|
||||
frank REPLACE_WITH_ADMIN_HASH
|
||||
# Forward auth: check the Authentik session before serving the page.
|
||||
forward_auth authentik-server:9000 {
|
||||
uri /outpost.goauthentik.io/auth/caddy
|
||||
copy_headers X-Authentik-Username X-Authentik-Groups X-Authentik-Email X-Authentik-Name
|
||||
trusted_proxies private_ranges
|
||||
}
|
||||
file_server
|
||||
}
|
||||
|
||||
# Everything else (the user page): the homepage login (dankuser).
|
||||
handle {
|
||||
basicauth {
|
||||
dankuser REPLACE_WITH_HOMEPAGE_HASH
|
||||
# Operator-only admin page. Auth is checked on the original path,
|
||||
# before any rewrite, so the path matcher is reliable. 'handle'
|
||||
# preserves order.
|
||||
handle /admin* {
|
||||
try_files /admin.html
|
||||
file_server
|
||||
}
|
||||
|
||||
# Shared data + assets: fetched by both the user page and the admin
|
||||
# page. With forward_auth, any authenticated user can access them
|
||||
# (the browser's Authentik session covers these fetches).
|
||||
@data path /services.json /status.json /app.js /style.css /deployed.js
|
||||
handle @data {
|
||||
file_server
|
||||
}
|
||||
|
||||
# Everything else (the user page).
|
||||
handle {
|
||||
try_files /index.html
|
||||
file_server
|
||||
}
|
||||
try_files /index.html
|
||||
file_server
|
||||
}
|
||||
}
|
||||
|
||||
# --- Legacy name: the old homepage name redirects to the naked domain ---
|
||||
dankclaw.franksdank.cloud {
|
||||
redir https://franksdank.cloud/ 302
|
||||
}
|
||||
|
||||
# --- Legacy name: the media host has no Caddy of its own anymore ---
|
||||
dankserver.franksdank.cloud {
|
||||
redir https://dankclaw.franksdank.cloud/ 302
|
||||
redir https://franksdank.cloud/ 302
|
||||
}
|
||||
|
||||
# --- Authentik (SSO / OIDC provider) ---
|
||||
auth.franksdank.cloud {
|
||||
reverse_proxy authentik-server:9000
|
||||
}
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
@@ -94,10 +107,15 @@ dankserver.franksdank.cloud {
|
||||
|
||||
# --- SearXNG (quadlet; behind the Gluetun tunnel, published 8081) ---
|
||||
search.franksdank.cloud {
|
||||
basicauth {
|
||||
dankuser REPLACE_WITH_HASH
|
||||
route {
|
||||
reverse_proxy /outpost.goauthentik.io/* authentik-server:9000
|
||||
forward_auth authentik-server:9000 {
|
||||
uri /outpost.goauthentik.io/auth/caddy
|
||||
copy_headers X-Authentik-Username X-Authentik-Groups X-Authentik-Email X-Authentik-Name
|
||||
trusted_proxies private_ranges
|
||||
}
|
||||
reverse_proxy 192.168.0.143:8081
|
||||
}
|
||||
reverse_proxy 192.168.0.143:8081
|
||||
}
|
||||
|
||||
# --- ComfyUI ---
|
||||
@@ -112,10 +130,15 @@ chat.franksdank.cloud {
|
||||
|
||||
# --- Hermes ---
|
||||
hermes.franksdank.cloud {
|
||||
basicauth {
|
||||
dankuser REPLACE_WITH_HASH
|
||||
route {
|
||||
reverse_proxy /outpost.goauthentik.io/* authentik-server:9000
|
||||
forward_auth authentik-server:9000 {
|
||||
uri /outpost.goauthentik.io/auth/caddy
|
||||
copy_headers X-Authentik-Username X-Authentik-Groups X-Authentik-Email X-Authentik-Name
|
||||
trusted_proxies private_ranges
|
||||
}
|
||||
reverse_proxy hermes:8090
|
||||
}
|
||||
reverse_proxy hermes:8090
|
||||
}
|
||||
|
||||
# --- Gitea (quadlet; published 3000) ---
|
||||
@@ -133,10 +156,15 @@ grafana.franksdank.cloud {
|
||||
|
||||
# --- Prometheus ---
|
||||
prometheus.franksdank.cloud {
|
||||
basicauth {
|
||||
dankuser REPLACE_WITH_HASH
|
||||
route {
|
||||
reverse_proxy /outpost.goauthentik.io/* authentik-server:9000
|
||||
forward_auth authentik-server:9000 {
|
||||
uri /outpost.goauthentik.io/auth/caddy
|
||||
copy_headers X-Authentik-Username X-Authentik-Groups X-Authentik-Email X-Authentik-Name
|
||||
trusted_proxies private_ranges
|
||||
}
|
||||
reverse_proxy prometheus:9090
|
||||
}
|
||||
reverse_proxy prometheus:9090
|
||||
}
|
||||
|
||||
# --- Loki ---
|
||||
@@ -176,18 +204,28 @@ books.franksdank.cloud {
|
||||
|
||||
# --- RomM ---
|
||||
romm.franksdank.cloud {
|
||||
basicauth {
|
||||
dankuser REPLACE_WITH_HASH
|
||||
route {
|
||||
reverse_proxy /outpost.goauthentik.io/* authentik-server:9000
|
||||
forward_auth authentik-server:9000 {
|
||||
uri /outpost.goauthentik.io/auth/caddy
|
||||
copy_headers X-Authentik-Username X-Authentik-Groups X-Authentik-Email X-Authentik-Name
|
||||
trusted_proxies private_ranges
|
||||
}
|
||||
reverse_proxy 192.168.0.235:8085
|
||||
}
|
||||
reverse_proxy 192.168.0.235:8085
|
||||
}
|
||||
|
||||
# --- Copyparty ---
|
||||
files.franksdank.cloud {
|
||||
basicauth {
|
||||
dankuser REPLACE_WITH_HASH
|
||||
route {
|
||||
reverse_proxy /outpost.goauthentik.io/* authentik-server:9000
|
||||
forward_auth authentik-server:9000 {
|
||||
uri /outpost.goauthentik.io/auth/caddy
|
||||
copy_headers X-Authentik-Username X-Authentik-Groups X-Authentik-Email X-Authentik-Name
|
||||
trusted_proxies private_ranges
|
||||
}
|
||||
reverse_proxy 192.168.0.235:3923
|
||||
}
|
||||
reverse_proxy 192.168.0.235:3923
|
||||
}
|
||||
|
||||
# --- Jellyfin ---
|
||||
|
||||
@@ -207,6 +207,71 @@ services:
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
# --- Authentik (SSO / OIDC provider) -------------------------------------
|
||||
# 3-service stack: PostgreSQL + server + worker. Redis was removed in
|
||||
# 2025.10 (sessions/caching moved to PostgreSQL). The Caddyfile's
|
||||
# forward_auth vhosts proxy to authentik-server:9000 (compose network).
|
||||
# See ../docs/Infrastructure/Authentik.md.
|
||||
authentik-postgres:
|
||||
image: docker.io/library/postgres:16-alpine
|
||||
container_name: authentik-postgres
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_DB: authentik
|
||||
POSTGRES_USER: authentik
|
||||
POSTGRES_PASSWORD: ${AUTHENTIK_PG_PASS}
|
||||
volumes:
|
||||
- /home/dank/authentik/postgres:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -d authentik -U authentik"]
|
||||
interval: 30s
|
||||
retries: 5
|
||||
start_period: 20s
|
||||
|
||||
authentik-server:
|
||||
image: ghcr.io/goauthentik/server:2026.8.0
|
||||
container_name: authentik-server
|
||||
restart: unless-stopped
|
||||
command: server
|
||||
environment:
|
||||
AUTHENTIK_POSTGRESQL__HOST: authentik-postgres
|
||||
AUTHENTIK_POSTGRESQL__NAME: authentik
|
||||
AUTHENTIK_POSTGRESQL__USER: authentik
|
||||
AUTHENTIK_POSTGRESQL__PASSWORD: ${AUTHENTIK_PG_PASS}
|
||||
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY}
|
||||
ports:
|
||||
- "9000:9000"
|
||||
volumes:
|
||||
- /home/dank/authentik/data:/data
|
||||
shm_size: 512mb
|
||||
depends_on:
|
||||
authentik-postgres:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:9000/-/health/')"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
start_period: 30s
|
||||
|
||||
authentik-worker:
|
||||
image: ghcr.io/goauthentik/server:2026.8.0
|
||||
container_name: authentik-worker
|
||||
restart: unless-stopped
|
||||
command: worker
|
||||
environment:
|
||||
AUTHENTIK_POSTGRESQL__HOST: authentik-postgres
|
||||
AUTHENTIK_POSTGRESQL__NAME: authentik
|
||||
AUTHENTIK_POSTGRESQL__USER: authentik
|
||||
AUTHENTIK_POSTGRESQL__PASSWORD: ${AUTHENTIK_PG_PASS}
|
||||
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY}
|
||||
user: "0:0"
|
||||
volumes:
|
||||
- /home/dank/authentik/data:/data
|
||||
depends_on:
|
||||
authentik-postgres:
|
||||
condition: service_healthy
|
||||
|
||||
# --- Reverse proxy + homepage (the homelab's single Caddy) ---------------
|
||||
# The Caddyfile mounted here is the RENDERED copy (basicauth hash filled in
|
||||
# from the host .env by scripts/deploy.sh); the repo copy carries the
|
||||
@@ -235,6 +300,7 @@ services:
|
||||
# lacks the generated files.)
|
||||
- /srv/homepage:/srv/homepage:ro
|
||||
depends_on:
|
||||
- authentik-server
|
||||
- comfyui
|
||||
- hermes
|
||||
- grafana
|
||||
|
||||
@@ -10,10 +10,15 @@
|
||||
# 192.168.0.235 = dankserver (media stack; reached by Caddy via published
|
||||
# LAN ports — no DNS records point here)
|
||||
|
||||
# --- Homepage + legacy name ---
|
||||
# --- Homepage (naked domain + www) + legacy names ---
|
||||
192.168.0.143 franksdank.cloud
|
||||
192.168.0.143 www.franksdank.cloud
|
||||
192.168.0.143 dankclaw.franksdank.cloud
|
||||
192.168.0.143 dankserver.franksdank.cloud
|
||||
|
||||
# --- Authentik (SSO / OIDC provider) ---
|
||||
192.168.0.143 auth.franksdank.cloud
|
||||
|
||||
# --- Media services (dankserver, proxied) ---
|
||||
192.168.0.143 plex.franksdank.cloud
|
||||
192.168.0.143 plex.franksdank.casa
|
||||
|
||||
Reference in new issue
Block a user