diff --git a/dankclaw/Caddyfile b/dankclaw/Caddyfile index 3c64fcd..3cc0532 100644 --- a/dankclaw/Caddyfile +++ b/dankclaw/Caddyfile @@ -55,24 +55,33 @@ # --- Bare-IP access (health checks, convenience) --- :80 { - redir https://franksdank.cloud/ 302 + redir https://www.franksdank.cloud/ 308 } # --- Homepage (Anubis → Authentik SSO) --- -franksdank.cloud www.franksdank.cloud { +# www holds the homepage; the naked domain redirects onto it at the TLS edge. +# A redirect needs no challenge, so unauthenticated naked traffic never enters +# the auth chain. The proxy provider's external_host must match the host that +# actually carries traffic (https://www.franksdank.cloud) or Authentik matches +# zero providers and answers its error page instead. +franksdank.cloud { + redir https://www.franksdank.cloud/ 308 +} + +www.franksdank.cloud { reverse_proxy anubis:8923 { header_up X-Real-Ip {remote_host} } } -# --- Legacy name: the old homepage name redirects to the naked domain --- +# --- Legacy name: the old homepage name redirects straight onto www --- dankclaw.franksdank.cloud { - redir https://franksdank.cloud/ 302 + redir https://www.franksdank.cloud/ 308 } # --- Legacy name: the media host has no Caddy of its own anymore --- dankserver.franksdank.cloud { - redir https://franksdank.cloud/ 302 + redir https://www.franksdank.cloud/ 308 } # --- Authentik (SSO / OIDC provider) --- @@ -234,7 +243,7 @@ qbittorrent.franksdank.cloud { # in place, authenticated traffic is forwarded back to http://:8017 and # routed straight to the app. Exempt vhosts never enter this block. http://:8016 { - @home host franksdank.cloud www.franksdank.cloud + @home host www.franksdank.cloud handle @home { reverse_proxy authentik-server:9000 { header_up X-authentik-meta-outpost 7e39655b-58d3-4fe6-a80a-513f31051217 @@ -295,7 +304,7 @@ http://:8016 { # straight to its app. This is the end of the chain — nothing dials back # into Authentik from here, so the chain cannot loop. http://:8017 { - @home host franksdank.cloud www.franksdank.cloud + @home host www.franksdank.cloud handle @home { root * /srv/homepage handle /admin* { diff --git a/tests/authentik.bats b/tests/authentik.bats index 49763ee..42ccb28 100644 --- a/tests/authentik.bats +++ b/tests/authentik.bats @@ -111,26 +111,36 @@ UNIT_DIR="$REPO_ROOT/dankclaw/authentik/quadlet" grep -qE '^http://:8017 \{' "$CADDYFILE" } -# --- Homepage: moved to naked domain + www --- +# --- Homepage: www holds the homepage; naked redirects onto it --- -@test "the homepage vhost is the naked domain + www" { - grep -qE '^franksdank\.cloud[[:space:]]+www\.franksdank\.cloud[[:space:]]*\{' "$CADDYFILE" +@test "the homepage vhost is www, guarded by Anubis" { + local block + block=$(awk '/^www\.franksdank\.cloud[[:space:]]*\{/,/^}/' "$CADDYFILE") + echo "$block" | grep -q 'reverse_proxy anubis:8923' } -@test "dankclaw.franksdank.cloud redirects to the naked domain" { +@test "the naked domain redirects to www at the TLS edge" { + local block + block=$(awk '/^franksdank\.cloud[[:space:]]*\{/,/^}/' "$CADDYFILE") + echo "$block" | grep -q 'redir https://www.franksdank.cloud/ 308' +} + +@test "dankclaw.franksdank.cloud redirects straight onto www" { local block block=$(awk '/^dankclaw\.franksdank\.cloud/,/^}/' "$CADDYFILE") - echo "$block" | grep -q 'redir https://franksdank.cloud/' + echo "$block" | grep -q 'redir https://www.franksdank.cloud/ 308' } -@test "dankserver.franksdank.cloud redirects to the naked domain" { +@test "dankserver.franksdank.cloud redirects straight onto www" { local block block=$(awk '/^dankserver\.franksdank\.cloud/,/^}/' "$CADDYFILE") - echo "$block" | grep -q 'redir https://franksdank.cloud/' + echo "$block" | grep -q 'redir https://www.franksdank.cloud/ 308' } -@test "the bare-IP catch-all redirects to the naked domain" { - grep -q 'redir https://franksdank.cloud/' "$CADDYFILE" +@test "the bare-IP catch-all redirects onto www" { + local block + block=$(awk '/^:80 \{/,/^}/' "$CADDYFILE") + echo "$block" | grep -q 'redir https://www.franksdank.cloud/ 308' } # --- .env.example: authentik placeholders --- diff --git a/tests/caddyfile.bats b/tests/caddyfile.bats index 8498972..a7697de 100644 --- a/tests/caddyfile.bats +++ b/tests/caddyfile.bats @@ -139,8 +139,10 @@ CADDYFILE="$REPO_ROOT/dankclaw/Caddyfile" grep -q '{env.PORKBUN_API_SECRET_KEY}' "$CADDYFILE" } -@test "the bare-IP catch-all redirects to the HTTPS homepage" { - grep -q 'redir https://franksdank.cloud/' "$CADDYFILE" +@test "the bare-IP catch-all redirects onto the HTTPS homepage at www" { + local block + block=$(awk '/^:80 \{/,/^}/' "$CADDYFILE") + echo "$block" | grep -q 'redir https://www.franksdank.cloud/ 308' } @test "the caddy quadlet uses the locally built porkbun image without pulling" { diff --git a/tests/homepage_caddy.bats b/tests/homepage_caddy.bats index a848a01..c5f791b 100644 --- a/tests/homepage_caddy.bats +++ b/tests/homepage_caddy.bats @@ -1,10 +1,11 @@ #!/usr/bin/env bats # Seam: the homepage site block in dankclaw/Caddyfile. Since the Anubis -# rollout the naked domain + www are guarded vhosts: the public listener -# proxies everything through Anubis (bot challenge), and the Authentik -# forward_auth for SSO runs on Caddy's internal listener (http://:8016) -# after Anubis has verified the client. caddy is not on the dev box, so the -# tests assert the structure statically. +# rollout, www holds the homepage as a guarded vhost (the naked domain +# redirects onto it at the TLS edge): the public listener proxies www +# through Anubis (bot challenge), and the Authentik SSO flow runs on +# Caddy's internal listener (http://:8016) after Anubis has verified the +# client. caddy is not on the dev box, so the tests assert the structure +# statically. load helpers @@ -33,11 +34,11 @@ setup() { } @test "the homepage vhost chains Anubis (public) before Authentik SSO (internal listener)" { - # Public listener: the naked domain proxies everything through Anubis. + # Public listener: www proxies everything through Anubis; the naked + # domain never enters the chain (it redirects at the TLS edge first). local block - block=$(awk '/^franksdank\.cloud[[:space:]]/,/^}/' "$CADDY") + block=$(awk '/^www\.franksdank\.cloud[[:space:]]*\{/,/^}/' "$CADDY") echo "$block" | grep -q 'reverse_proxy anubis:8923' - # The SSO check itself runs on the internal listener that Anubis # forwards accepted traffic to; Host survives the hop, so per-vhost # routing resumes there — guarded vhosts proxy the whole request # through Authentik, which forwards authenticated traffic back on the