feat(caddy): give Audiobookshelf a guarded abs.franksdank.cloud address #130

Merged
dank merged 5 commits from feat/abs-vhost into main 2026-11-03 00:57:47 -08:00
Owner

Symptom

Audiobookshelf had no franksdank.cloud address: http://192.168.0.235:8000/audiobookshelf was the only door (the page recorded it as internal-only, "LAN-only until Frank says otherwise"). So browser use away from the LAN was impossible, and the OIDC round trip could only ever complete where an RFC1918 callback is reachable — the IdP would authenticate you and then hand the browser a private address.

Ordering matters here (external-exposure policy): the outpost proxy provider has to exist before the route flips. That half is already live and is not in this diff — proxy provider abs-proxy (external_host https://abs.franksdank.cloud/, internal_host http://caddy:8017) + application "Audiobookshelf Guard", claimed by the embedded outpost (10 → 11 providers).

Change

File What
dankclaw/Caddyfile guarded site block (reverse_proxy anubis:8923 with header_up X-Real-Ip {remote_host}), plus the post-auth @abs handler on http://:8017 → 192.168.0.235:8000. The container carries ROUTER_BASE_PATH=/audiobookshelf, so the app has no root: the bare / is redirected onto the base path inside the post-auth handler, never pre-auth
dankclaw/pihole/addn-hosts LAN record for the vhost (the pihole seam fails the build without it)
scripts/authentik-audiobookshelf-sso.sh ABS_EXTRA_BASES (default the vhost): the browser callback is registered per origin, because ABS derives redirect_uri from the origin of the request. The mobile-redirect URI stays on the LAN origin only — the phone is a non-browser client that cannot solve the guard challenge, so it is configured with the LAN address
shared/homepage/services.json tile points at the vhost; probe stays the LAN host:port so the checker measures the app, not the chain
tests/e2e/specs/14-guard.spec.ts abs joins GUARDED, leak marker "Sign in with Authentik" — a string the Audiobookshelf login renders and the IdP gate never prints
docs Infrastructure/Caddy.md gains Guard a new vhost (the provider recipe that nobody recreates); Dankserver/Audiobookshelf.md swaps its Internal-only row and law for Two doors, one app; index.md regenerated

Evidence

  • scripts/check-okf.py → 0 violations; gen-vault-index.py is a no-op against the tree.
  • ./tests/run.sh → ALL TESTS PASSED (824 tests), including the caddyfile and pihole seams that cross-check the new vhost against addn-hosts.
  • Provider script re-run after the change: uris ['http://192.168.0.235:8000/audiobookshelf/auth/openid/callback', 'https://abs.franksdank.cloud/audiobookshelf/auth/openid/callback', 'http://192.168.0.235:8000/audiobookshelf/auth/openid/mobile-redirect'], grants ['authorization_code', 'refresh_token'], scopes ['abs_groups', 'email', 'openid', 'profile'] unchanged.
  • Post-merge/deploy verification is deliberately not claimed here: DNS, TLS, the gate answering an anonymous client, the SPA booting through the vhost, and the SSO round trip over the public name are measured after the deploy, and 14-guard re-proves the chain on every subsequent deploy.

Rollback

Revert the five commits. The IdP rows are additive (abs-proxy and one callback URI); removing the Caddyfile block takes the name away and the LAN door never changed, so nothing on Dankserver moves.


cwd: /home/blank/.omp/wt/wt-20261030-163847-6d1fc1f · session: 01a18f2e-13b1-7573-bcd5-ac99636f8bcb

## Symptom Audiobookshelf had no `franksdank.cloud` address: `http://192.168.0.235:8000/audiobookshelf` was the only door (the page recorded it as internal-only, "LAN-only until Frank says otherwise"). So browser use away from the LAN was impossible, and the OIDC round trip could only ever complete where an RFC1918 callback is reachable — the IdP would authenticate you and then hand the browser a private address. Ordering matters here (external-exposure policy): the outpost proxy provider has to exist **before** the route flips. That half is already live and is not in this diff — proxy provider `abs-proxy` (`external_host https://abs.franksdank.cloud/`, `internal_host http://caddy:8017`) + application "Audiobookshelf Guard", claimed by the embedded outpost (10 → 11 providers). ## Change | File | What | |---|---| | `dankclaw/Caddyfile` | guarded site block (`reverse_proxy anubis:8923` with `header_up X-Real-Ip {remote_host}`), plus the post-auth `@abs` handler on `http://:8017` → `192.168.0.235:8000`. The container carries `ROUTER_BASE_PATH=/audiobookshelf`, so the app has no root: the bare `/` is redirected onto the base path **inside the post-auth handler**, never pre-auth | | `dankclaw/pihole/addn-hosts` | LAN record for the vhost (the `pihole` seam fails the build without it) | | `scripts/authentik-audiobookshelf-sso.sh` | `ABS_EXTRA_BASES` (default the vhost): the browser callback is registered **per origin**, because ABS derives `redirect_uri` from the origin of the request. The `mobile-redirect` URI stays on the LAN origin only — the phone is a non-browser client that cannot solve the guard challenge, so it is configured with the LAN address | | `shared/homepage/services.json` | tile points at the vhost; `probe` stays the LAN `host:port` so the checker measures the app, not the chain | | `tests/e2e/specs/14-guard.spec.ts` | `abs` joins `GUARDED`, leak marker `"Sign in with Authentik"` — a string the Audiobookshelf login renders and the IdP gate never prints | | docs | `Infrastructure/Caddy.md` gains **Guard a new vhost** (the provider recipe that nobody recreates); `Dankserver/Audiobookshelf.md` swaps its Internal-only row and law for **Two doors, one app**; `index.md` regenerated | ## Evidence - `scripts/check-okf.py` → 0 violations; `gen-vault-index.py` is a no-op against the tree. - `./tests/run.sh` → **ALL TESTS PASSED** (824 tests), including the `caddyfile` and `pihole` seams that cross-check the new vhost against `addn-hosts`. - Provider script re-run after the change: `uris ['http://192.168.0.235:8000/audiobookshelf/auth/openid/callback', 'https://abs.franksdank.cloud/audiobookshelf/auth/openid/callback', 'http://192.168.0.235:8000/audiobookshelf/auth/openid/mobile-redirect']`, `grants ['authorization_code', 'refresh_token']`, `scopes ['abs_groups', 'email', 'openid', 'profile']` unchanged. - Post-merge/deploy verification is deliberately not claimed here: DNS, TLS, the gate answering an anonymous client, the SPA booting through the vhost, and the SSO round trip over the public name are measured after the deploy, and `14-guard` re-proves the chain on every subsequent deploy. ## Rollback Revert the five commits. The IdP rows are additive (`abs-proxy` and one callback URI); removing the Caddyfile block takes the name away and the LAN door never changed, so nothing on Dankserver moves. --- cwd: /home/blank/.omp/wt/wt-20261030-163847-6d1fc1f · session: 01a18f2e-13b1-7573-bcd5-ac99636f8bcb
dank added 5 commits 2026-11-03 00:55:26 -08:00
Provider-first exposure: the IdP row (proxy provider abs-proxy + application
"Audiobookshelf Guard", claimed by the embedded outpost) already exists, so the
route can ship. The site block proxies everything to anubis:8923 like every other
guarded vhost, and the post-auth :8017 handler routes the host to
192.168.0.235:8000. The container carries ROUTER_BASE_PATH=/audiobookshelf, so
the app has no root: the bare / is redirected onto the base path inside the
post-auth handler, never pre-auth, so an anonymous client only ever meets the
gate.
The tile carried the bare LAN IP. The probe stays the LAN host:port so the
homepage checker keeps measuring the app rather than the auth chain in front of
it.
ABS builds redirect_uri from the origin of the request, so a second door is a
second URI: ABS_EXTRA_BASES (default the guarded vhost) now contributes a
callback each, while the mobile-redirect URI stays on the LAN origin - the phone
is a non-browser client and cannot solve the guard's challenge, so it is
configured with the LAN address. Measured on the live provider: three URIs,
grants and scopes unchanged.
The guard spec's GUARDED list is the lockstep with the Caddyfile; abs joins it
with the app's own SSO-button label as the leak marker - a string the Audiobookshelf
login renders and the IdP gate never prints.
Infrastructure/Caddy.md now owns the order of operations for exposing a vhost
(proxy provider + application + embedded-outpost claim, then the route, then the
name and the chain audit), because the IdP row is invisible state that nobody
recreates. Audiobookshelf's Internal-only row and law become Two doors, one app;
the homepage link and index.md follow.
dank merged commit 17ea2f4110 into main 2026-11-03 00:57:47 -08:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: dank/serverAppConfigs#130