docs: the guard-a-new-vhost procedure and Audiobookshelf's second door

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.
This commit is contained in:
Frank committed 2026-11-03 00:55:02 -08:00
1 parent 5e0592381a
commit 603513a15e
3 files changed
+60 -16

No files matched your search

@@ -1,11 +1,11 @@
---
type: reference
description: "Audiobookshelf on Dankserver: quadlet shape, the read-only array bind, SSD state, API bootstrap, authentik SSO, laws."
description: "Audiobookshelf on Dankserver: quadlet shape, array bind, SSD state, API bootstrap, authentik SSO, guarded vhost, laws."
tags:
- dankserver
- audiobooks
- quadlet
timestamp: 2026-11-02
timestamp: 2026-11-03
---
# Audiobookshelf
@@ -16,8 +16,8 @@ per-listener playback positions, and streams the audio. It is **not** a media
server — it shares nothing with [[Plex]] or [[Jellyfin]] and owns no files on the
array.
**Host:** Dankserver — **Unit:** `audiobookshelf.service` (rootless Quadlet, user
manager) — **Internal-only:** LAN address `http://192.168.0.235:8000/audiobookshelf`, no vhost, no DNS name.
**Host:** Dankserver — **Unit:** `audiobookshelf.service` (rootless Quadlet, user manager) —
**Doors:** LAN `http://192.168.0.235:8000/audiobookshelf` (direct, what the phone app uses) and guarded `https://abs.franksdank.cloud/audiobookshelf/`.
## Shape
@@ -75,8 +75,8 @@ second host 2026-11-02 and re-measured on both base paths: `/ping` (`{"success":
`/healthcheck` (`OK`) answer at the ROOT whatever `ROUTER_BASE_PATH` says, so the app's
connectivity probe works before it resolves the base path at all, and `iptables -S INPUT`
carries no rule for 8000 — a phone on `192.168.0.0/24` connects with no further setup, and
`download: true` on the root account is what makes offline listening work. Away from the LAN
— see the LAN-only law, and note that the guard chain is not the remedy there.
`download: true` on the root account is what makes offline listening work. Away
from the LAN, see the two-doors law in *Laws* — the guarded vhost is not the remedy.
**The login token is at `user.token`, not top level.** `POST /login` answers
`{ user, serverSettings, ereaderDevices, Source, userDefaultLibraryId }` and the bearer sits
@@ -98,6 +98,7 @@ the local form, and the phone app offers SSO on its server-address screen. The l
| The ABS half: issuer, the four IdP URLs, claim, auto-register | `scripts/audiobookshelf-init.sh`, armed automatically when that secret exists in the credential file |
| Role at first SSO sign-in | the `abs_groups` claim: `audiobookshelf_admins` → `admin`, `audiobookshelf_users` → `user`, in neither → denied (fail closed) |
| Admin group members | `akadmin` (the operator's own authentik admin account — sign in as it and the claim grants site admin) and `qa-e2e-admin` (the identity the deploy gate signs in as, so the script re-adds it every run; drop it and the gate fails, not a phone) |
| The guard in front of the vhost | proxy provider `abs-proxy` (external_host `https://abs.franksdank.cloud/`, internal_host `http://caddy:8017`) + application "Audiobookshelf Guard", claimed by the embedded outpost — the ak-shell recipe in [[Caddy]] *Guard a new vhost* creates it; without that row the chain answers authentik's error page, not the login |
The IdP saying yes is not the same as being allowed in: with `authOpenIDAutoRegister` on,
a first sign-in creates the account, and the claim alone decides the type. A user in
@@ -186,13 +187,13 @@ Both halves — authentik's provider row and ABS's settings — are asserted end
- **No `systemctl --user enable`.** Generated Quadlet units cannot be enabled on
this host; `WantedBy=default.target` in the unit file is what carries the
service across a reboot ([[Quadlet Lifecycle]]).
- **LAN-only until Frank says otherwise.** `0.0.0.0:8000` with no reverse proxy
in front. Any public vhost requires the Anubis + Authentik chain first, and the
outpost proxy provider has to exist before the vhost flips (external exposure
policy in `AGENTS.md`). That chain serves browsers: the phone app is a non-browser
client and cannot solve a challenge, so guarding a vhost does not make remote
listening work — remote listening wants a VPN into the LAN, after which the LAN
address in *Phone app* is correct unchanged.
- **Two doors, one app.** `http://192.168.0.235:8000/audiobookshelf` is the raw
LAN address — plain HTTP, nothing in front, what the phone app is configured
with; `https://abs.franksdank.cloud/audiobookshelf/` is the guarded vhost
(Caddy → Anubis → the Authentik outpost → Caddy `:8017` → this container),
the house chain for browser traffic — [[Caddy]] *Guard a new vhost*. That
chain serves browsers only: the phone app is a non-browser client and solves
no challenge, so remote listening wants a VPN into the LAN, not the vhost.
- **There is no `/setup` route; the creation door is `POST /init`.** `/status`
(no `/api` prefix) reports `isInit` = "a root user exists", `POST /init` with
`{"newRoot":{"username","password"}}` creates it, `POST /login` returns the