Commit Graph
1693 Commits
Author SHA1 Message Date
dank cae283047f Merge pull request 'docs(authentik,openwebui): the roles claim mints the OpenWebUI role; jason joins the census' (#144) from docs/authentik-jason-provision into main 2026-11-04 12:57:07 -08:00
dank 51007ac604 Merge pull request 'fix(homepage): Jellyfin takes the family login; document the voice models' (#145) from docs/homepage-jellyfin-login-and-tts into main 2026-11-04 12:56:44 -08:00
Frank f7c41966b0 fix(homepage): Jellyfin takes the family login; document the voice models
Jellyfin authenticates against Authentik over LDAP, so telling the household
that it "will not take the family password" sent people to the wrong door. The
tile's provider id (alex -> LdapAuthenticationProviderPlugin) and a live bind +
memberOf search against the running outpost are the proof; six places repeated
the claim, and all six now say to sign in with the franksdank.cloud username and
password. Added the failure mode that actually happens: a password that works on
the homepage but is refused here means the name is not in jellyfin_users yet.
Plex and the e-book door stay the two genuinely separate accounts.

Marreta's tile loses its VPN/headless-browser mechanics and keeps one honest
clause: "does not work on all sites".

The models page gains the TTS pair the household keeps meeting: frank-tts
(Qwen3 talker + 12 Hz codec on the iGPU, five cloned voices, live list at
/v1/audio/voices) and Kokoro (82M, stock presets, runs in the user's own
browser) - which is why the AI chat's speaker list never matches the server's.

Verified in a browser against an isolated copy of the tree: both tiles render
the new copy, #models carries both tables, zero broken images, and every
retired phrase is absent from the rendered text. ./tests/run.sh: 841 pass.
2026-11-04 12:49:42 -08:00
Frank 25114ca622 Merge remote-tracking branch 'origin/main' into wt/20261104-104444 2026-11-04 12:42:19 -08:00
Frank a07f492b89 docs(authentik,openwebui): the roles claim mints the OpenWebUI role
Provisioned jason (pk 24, human-user template) and drove his first SSO
login in a clean browser: the row appeared as role='user' with chat and
wilwheaton TTS working, no manual step. The pending-then-flip law is
therefore wrong for a claim-carrying account - ENABLE_OAUTH_ROLE_MANAGEMENT
+ the roles scope decide it, and 0.12.5 refuses a claim-less login with 403
instead of falling back to ui.default_user_role. Census gains jason.
2026-11-04 12:42:19 -08:00
dank 9961ed62c0 Merge pull request 'docs(jellyfin,authentik): the LDAP admin filter is inert; the auth door lives in Users' (#143) from docs/jellyfin-ldap-sso-laws into main 2026-11-04 12:27:00 -08:00
Frank 70be24e9d5 docs(jellyfin,authentik): the LDAP admin filter is inert; the auth door lives in Users
Diagnosed 'authentik login does not work in Jellyfin' 2026-11-04: nothing was
broken. The Jellyfin admin account 'dank' is a LOCAL-provider account, and the
operator's authentik identity (akadmin) was not in jellyfin_users, so the plugin
search found nothing and Jellyfin fell back to local auth - same UI message,
opposite fix. akadmin is now in jellyfin_users (directory data, DB-owned).

- Jellyfin: the admin-filter law (DN match, so a group-OU base grants nothing;
  memberOf shape is the working one), the AuthenticationProviderId diagnostic,
  re-verified LDAP path (bind + memberOf search from Dankserver).
- Authentik Outpost Reference: settle the token-intent argument (the plugin
  sends a password, never a token), verify the bind DN live, and record the
  instruments/traps: the outpost journal never shows a bind, Event is keyed by
  created/action, Outpost has no apps, and piping ak shell steals its heredoc.
- Authentik: stop restating the LDAP outpost digest - the unit file owns it.
2026-11-04 12:25:57 -08:00
dank 70aa40a24a Merge pull request 'The --after help names the trigger’s epoch, not a file mtime' (#142) from fix/after-epoch-help into main 2026-11-04 12:25:46 -08:00
dank 8f2c3bb378 docs(scripts): --after names the trigger's epoch, never a file mtime
Both drivers' help text recommended `--after "$(stat -c %Y <journal>)"`. Measured
2026-11-04 that epoch is answered by whatever scan finished before the question was
asked: on Jellyfin it reads an earlier task's EndTimeUtc, and the Plex door cannot be
a waiter at all, since PMS stamps section_locations.scanned_at when it accepts the
request. The laws are on the vault pages (main 146c28d); this puts the warning at
point of use, in the text an agent reads before calling the door.
2026-11-04 12:24:56 -08:00
dank 146c28da9f docs(plex,jellyfin): the scan doors are not waiters; supersedes b82655d
`status` compares a recorded timestamp with `--after`. Measured 2026-11-04: Plex
answered `settled` in the same second its own trigger was accepted (stamp
19:46:59Z for a request accepted 19:46:58Z) and still answered `settled` from that
one stamp 30 minutes later while the walk ran. So no epoch survives it — a journal
mtime fails and the trigger's epoch fails, because the trigger satisfies the door it
was asked to wait for. That is the correction of b82655d, which prescribed the
trigger's epoch for this door.

Jellyfin's `EndTimeUtc` only advances when a scan finishes, so there the trigger's
epoch is sound and a file mtime is not; its `wait` already refuses an epoch an
earlier task passes. The proof a scan did its work stays `media-verify.py`
replaying the journal: a scan can clear rows but cannot invent one.
2026-11-04 12:20:59 -08:00
dank b82655d30a docs(plex): --after takes the trigger's epoch; a file mtime reports a false settle
The page's own example passed the journal mtime. Measured 2026-11-04: a chain
using it fell straight through its wait loop, because status only asks whether
the section has scanned since that epoch and the section had been walked earlier
the same day. The verify that followed read a database that had not yet processed
that day's renames — a false success, which is the direction that gets believed.
2026-11-04 12:17:20 -08:00
dank 0161ab2aa8 Merge pull request 'Media refresh drivers: the two scan doors become commands' (#140) from feat/media-refresh-drivers into main 2026-11-04 12:15:38 -08:00
dank 085a5d570c Merge pull request 'docs(openwebui): the browser-kokoro switch is the user's settings row, not browser storage' (#141) from docs/openwebui-tts-kokoro-user-row into main 2026-11-04 12:14:35 -08:00
Frank 9ca69d4e7e Merge remote-tracking branch 'origin/main' into wt/20261104-104444 2026-11-04 12:09:57 -08:00
Frank 64b06b9426 docs(openwebui): the browser-kokoro switch is the user's settings row, not browser storage
`wilwheaton` kept failing in OpenWebUI with the 29-name Kokoro enum even
after the dropdown was set to Default. The page's 2026-10-18 conclusion —
"no user.settings row carries an audio override, so the choice lives in that
browser's own storage" — was a probe-depth bug: the personal audio settings
live in table `user`, and the 0.12.x front end nests them under `ui`
(`settings.ui.audio.tts.engine`). The row did carry `browser-kokoro`, and the
SPA re-applies the server copy on every load, so the UI fix never stuck.

Corrected facts on the owner page:
- where the override really lives, and the version-dependent nesting;
- repair BOTH engine and voice: engine "" still sends the stored voice, so a
  Kokoro-era `voice: "alloy"` returns as frank-tts's own 400;
- the one-line tells: an `/wasm/ort-wasm-simd-threaded.jsep.wasm` fetch means
  Kokoro.js is live in that browser, and uvicorn logs every request so a
  missing `/api/v1/audio/speech` line is evidence;
- deployed build is 0.12.5 (pin `0b399a8b`, bumped 2026-10-30) — the page
  still claimed 0.11.4 — and the 401 paragraph's duplicated sentence is gone.

Verified 2026-11-04: with the row repaired, `POST /api/v1/audio/speech` as a
real user over the vhost returns 200 audio/mpeg and frank-tts logs
`speech voice=wilwheaton`.
2026-11-04 12:09:57 -08:00
dank e519f6b554 docs(vault): the promo-clip hold is purged; merge main's purge record 2026-11-04 11:59:00 -08:00
Frank 1e0247f29e Merge main into feat/media-refresh-drivers (main moved: the purge record) 2026-11-04 11:58:36 -08:00
dank 71e7e94b94 docs(media): the 2026-11-04 purge, and the gate that proves a hold is safe to delete
The duplicate-prune, TV-duplicate, Ghibli and promo-clip holds went out of the
array trash on Frank's word: ~1.2 TB back. This replaces the three "purge pending
Frank's word" rows with what happened.

Two procedures the page did not carry:

- The purge gate. `REAL: 0` in both databases only says no row points at a moved
  path; it says nothing about whether the film survived. Every held file needs a
  same-work carrier in the live library at >= its size, re-derived at purge time,
  because the relayout renames folders AND keeper files, accents defeat ASCII
  matching (Nausicaä), and a carrier is usually another container at another
  size. That supersedes the 2026-11-01 "re-verify by size" law, which is exactly
  the test that reports survivors as lost.
- The purge mechanics. The repo purger acts on a root's own .AGENTTRASH and
  nothing else, so a partial purge is staged into a scratch root while the
  unapproved buckets sit parked at the array root as .HOLD-<date>-*.

./tests/run.sh: 824 assertions, all pass.
2026-11-04 11:58:21 -08:00
Frank 24d793eb7d docs(vault): the promo-clip hold is 31 clips and one sidecar 2026-11-04 11:30:07 -08:00
Frank 95375683d5 docs(vault): the scan pages name their drivers, and a class no screen sees
Plex.md and Jellyfin.md keep every law and point at the driver that now carries
them, so the procedure has one owner and the pages keep the failure signatures.
Jellyfin.md also records where `jellyfin.db` actually lives on this host (the
quadlet's config bind, not the stock path) and demotes `immutable=1` on the
`ApiKeys` read to the `-shm` fallback, because the database is WAL and
`immutable` skips the log. Plex.md gains the live section keys and the
`scanned_at == scannedAt` fact.

Media Relayout.md's hand-off after an apply names both drivers and states the
ordering law: Jellyfin first and alone, since its one door walks every library
while Plex forks a scanner per section, and the array is what both share.

Media Duplicate Prune.md gains the guard the 2026-11-04 hold taught: 32 files
carrying real film titles were 8-20-second h264 clips (49 MiB, 25 of them in a
`_release-group watermarks/` folder), invisible to every duplicate screen
because nothing shares their size and the feature-length guard hides them. The
census that finds the next batch is a duration floor, not a hash.
2026-11-04 11:29:03 -08:00
Frank e7d7c6eea2 feat(scripts): the two media scan doors become commands
Both media servers' scan procedure lived only as a hand-written curl block in
the vault, and that block has bitten this fleet twice: the escaped-quote form
that reads exactly like a dead API key, and a config `url` line meeting a
command-line URL so curl quietly performs a second transfer and dumps the whole
task table on your stdout. Jellyfin's door is now
`scripts/media-refresh-jellyfin.py` (trigger / status / wait) and Plex's is
`scripts/media-refresh-plex.sh` (trigger / status);
`tests/media_refresh.bats` pins the contracts an operator observes, against a
stub and with no live server.

The Jellyfin `wait` encodes the settle law instead of a poll count: the Scan
Media Library task must be seen Running — or finish while watching, or already
pass the `--after` it is given — before any quiet read counts toward settled.
A watcher that saw no Running otherwise reports the pre-scan library as the
fresh one, which is how a rename looks applied while nothing has rescanned.
Plex's `status` needs no token at all: `section_locations.scanned_at` carries
the same epoch the API prints as `scannedAt` (measured on section 1 today).

Neither driver lets a secret reach argv or a URL: the Jellyfin key rides a
mode-600 curl config as the quoted MediaBrowser form, and the Plex token is
read inside the container, where the HTTPD actually answers, into a config file
that is shredded there.
2026-11-04 11:29:03 -08:00
dank e1bf1d6e80 Merge pull request 'Household docs: rewrite the family guide, and make the model cards tell the truth' (#139) from docs/household-guide-refresh into main 2026-11-03 04:08:09 -08:00
Frank ef306397a5 docs(vault): the four-slot archon contract, and pages that contradicted themselves
frank-archon.md's intro promised 2 conversations while its own shape table said
MAX_SEQS=4; AGENTS.md carried --max-num-seqs 2 and a 44,928 MiB host pool against
a live 4 and 49,152. Both now quote the env.

dankclaw-iommu-translation-off.md re-transcribed frank-dragoon's boot ledger,
including a pool figure that was superseded the same day, and asserted in the
present tense that no NPU endpoint is deployed - falsified by the XDNA2 embedding
lane on 2026-10-19. Both facts now belong to [[frank-dragoon]] alone.

Deployment Flow.md had a sentence broken by an earlier link removal.

Census: every tunable in the three scripts/<svc>.env against its endpoint page
(only truncated-sha and elided-path forms, which the pages omit on purpose), live
/health + /v1/models on all three endpoints, check-okf.py clean at 125 pages, no
duplicate titles, no page unlinked from an index.
2026-11-03 04:07:46 -08:00
Frank 3896dce364 fix(homepage): the model cards speak the live shape
Three numbers on the family-facing model cards had drifted from scripts/*.env:
archon said three submissions where FRANK_ARCHON_MAX_SEQS=4 (1bb6399), zealot
said 262k context where the contract is 200,704 a slot (CTX_SIZE 602112 / PARALLEL
3, and llama.cpp 400s over the slot rather than truncating), and zealot's warning
still promised censored-base refusals after the Uncensored-LoRA went into the load
line (LORA_FILE set, LORA_SCALE=1). archon's role dropped an unverifiable
"fastest"; zealot's card dropped a tokens/s figure nobody is comparing.

Deletes the assertion that zealot's warning must not claim uncensored output. It
was written 2026-10-17, when the censored base was live; the adapter landed after
it, so the pin now enforces a false safety claim on the one surface the household
reads. The general invariant survives and still fires: a card whose spec says
uncensored or abliterated must carry the legal warning (same seam, lines 100-104).
2026-11-03 04:07:46 -08:00
Frank 3f82c4d5d8 docs(homepage): rewrite the household guide for the people who use it
Task-first instead of app-first: start here, pick what you want to do, open the
door. Every door gets its exact address, the button to press, and the failure the
household actually hits.

Screenshots are files now (img/onboarding/*.webp, 14 of them, all captured logged
in as a standard user) instead of inline data URIs: the page is 23 KB of prose
instead of 30 KB of base64, and the images cache on their own. Zero-JS kept.

Facts re-checked against the live services rather than against the old page: the
front door is www.franksdank.cloud (the apex 308s there); the standard user's
Open WebUI account sees all three models and opens on frank-dragoon (read from
/api/models as that user); WebDAV answers only on the house network (207 on
192.168.0.235:3923, a guard challenge through the public door); Audiobookshelf
iOS is a limited-seat TestFlight beta, Android is the Play release.
2026-11-03 04:07:39 -08:00
Frank 5030f83c55 docs(jellyfin): one URL per curl config read, or the second transfer dumps the body 2026-11-03 03:42:21 -08:00
dank b875256500 Merge pull request 'feat(jellyfin): big-screen theme for the TV - repo CSS in the Media Center slot' (#138) from feat/jellyfin-big-screen-theme into main 2026-11-03 03:40:04 -08:00
Frank 51abc6550d feat(jellyfin): big-screen theme for the TV, mounted into the wmc slot 2026-11-03 03:38:15 -08:00
dank 64f489023e Merge pull request 'feat(homepage): Jellyfin goes live, Audiobookshelf joins the household, Marreta says what it is' (#137) from feat/homepage-media-cards into main 2026-11-03 03:32:49 -08:00
Frank 3511e04ae9 docs(jellyfin): record the API-key ledger and the rotation door 2026-11-03 03:27:48 -08:00
Frank 21b7ad15d9 chore(homepage): retire the unreferenced dankserver registry copy
shared/homepage/dankserver.services.json was read by nothing: no reference
anywhere in the repo, and deploy_homepage never copies it. It sat beside the
real registry looking like a second live card set, which is exactly how a
future agent gets misled about what the page renders. Moved to the gitignored
shared/homepage/.AGENTTRASH/ beside it, so it stays recoverable.
2026-11-03 03:23:13 -08:00
Frank 4ad55c28a0 feat(homepage): Jellyfin live, Audiobookshelf to the household page, Marreta says what it is
Jellyfin's card still read "Work in progress: not open to family users yet"
with no link; the server answers 12.4.0 on :8096 and carries a second
(non-admin) account, so the card flips to live status with its Open link on
http://jelly.franksdank.cloud. It has no Authentik link, and the copy says so.

Audiobookshelf moves category admin -> user. A card renders on exactly one
page, so it leaves /admin: Authentik SSO made it a household app, and the
household page is the door that matters.

Marreta's description now names what it is - the content-cleaning proxy in the
Gluetun netns with a headless companion - and its honest limit.

Also in this change:
- docs.html: a "Media & files" line for the audiobook door and its SSO, plus
  the pre-existing unclosed <ul> under frank-dragoon, which made the whole
  frank-archon block parse inside the dragoon bullet.
- Caddyfile: the jelly. vhost gains the exemption reason the External Exposure
  Policy requires (comment only - Caddy strips comments, so no proxy bounce).
- Homepage.md: classification list gains Audiobookshelf, and the section now
  states that services.json is the authority for placement/WIP/link.
2026-11-03 03:20:08 -08:00
dank c9a8469d04 Merge pull request 'docs(audiobookshelf): the wide listener default is a decision, not a defect' (#136) from docs/abs-listener-default-decision into main 2026-11-03 03:09:19 -08:00
Frank 5794b07c32 docs(audiobookshelf): the wide listener default is a decision, not a defect
The SSO paragraph left "whether an access policy should be bound to the
audiobookshelf application" open, which invites the next agent to relitigate
it. Frank's answer of 2026-11-03: accepted on purpose — every signer becomes a
listener with all libraries and downloads, no access policy on the application,
narrowing done per account in Settings -> Users. What that makes load-bearing is
authentik's own signup flow, now named in the same paragraph as the thing that
must stay operator-only.
2026-11-03 03:08:36 -08:00
dank 1a47b831ee Merge pull request 'docs(audiobookshelf): /api/login is not a route — and what auto-registration grants' (#135) from docs/abs-login-path-and-listener-shape into main 2026-11-03 03:04:05 -08:00
Frank 5be66df2bb docs(audiobookshelf): /api/login is not a route
A wrong path was mistaken for a dead break-glass credential. Local login is
POST <base>/login; /api/* is behind the bearer guard, which answers a bare 401
and logs nothing, so a bad path looks exactly like a bad password. The recorded
value bcrypt-matches users.pash, so local really is the door when authentik is
down, and the rate-limit line was protecting a path that cannot carry a login.

Also records what auto-registration actually grants, measured from the state DB
because GET /api/users returns the limited shape: every SSO signer arrives with
accessAllLibraries and download already on, and carries no local password.
2026-11-03 02:28:11 -08:00
dank d3aedd1837 Merge pull request 'Every authentik account that signs in to Audiobookshelf becomes a listener' (#134) from feat/audiobookshelf-sso-default-listener into main 2026-11-03 02:19:14 -08:00
Frank 66766f23ef docs(audiobookshelf): the IdP is the library gate now
Rewrite the listener onboarding, the role row, and the fail-closed law: the
claim grants every signer, revocation happens on the ABS side (disable or
delete the user), and what actually guards the library is who can reach the
audiobookshelf application in authentik. Name both e2e gates in Verification.
2026-11-03 02:06:20 -08:00
Frank 5cf5416471 test(e2e): pin the Audiobookshelf default listener grant
spec 24- signs in through SSO as qa-e2e — an identity in no Audiobookshelf
group — and must land inside the app with type "user". It is the counterpart
to 23-, which pins the admin escalation: together they catch both directions
of a scope-mapping regression (household locked out, or household made site
admin). Verified red against the pre-change mapping — the identity never
received a token — and green after re-provisioning: 4 passed.

Joined to the standard project, which is the only project whose identity
observes the default.
2026-11-03 02:06:20 -08:00
Frank 30186d8365 feat(authentik): make every Audiobookshelf SSO signer a listener
The abs_groups claim decided the listener role from membership of
audiobookshelf_users, a group nobody was ever added to, while the ABS
application carries no access policy of its own. The result: seventeen
household accounts authenticated at the IdP successfully and were then
refused by the app — a policy decision that reads as a broken login.

The claim now answers ["user"] for every signer and appends "admin" only
for audiobookshelf_admins, so the IdP login is the gate and onboarding a
listener is exactly "create the authentik account". Admins keep the exact
claim they always received, ["admin","user"], in that order: ABS's
group-tag matching has never been measured for order-sensitivity and the
working admin login is not where to find out. audiobookshelf_users is
deleted rather than left empty — a group that decides nothing is a trap
for the next reader, who will assume it gates something.

Two side fixes found by running the script:
 - Two comments inside the unquoted PYEOF heredoc carried backticks, so
   bash command-substituted them locally on EVERY run ("audiobookshelf_users:
   command not found", "user: command not found") and spliced the output
   into the python program. Names in that body are plain now, with a note
   on why. A quoted heredoc is not an option: the body interpolates.
 - The default-grant probe picked authentik's AnonymousUser row, then its
   admin superuser. It now walks real non-superuser accounts, preferring
   the fleet's plain-user marker group, and prints up to three claims.
2026-11-03 02:06:20 -08:00
dank 3a6f7c7280 Merge pull request 'feat(caddy): Audiobookshelf outside the guard chain, so the phone app works from anywhere' (#133) from feat/audiobookshelf-public-app-access into main 2026-11-03 01:56:51 -08:00
dank c7643fb4d0 docs: regenerate the vault index row for the new Audiobookshelf description 2026-11-03 01:55:06 -08:00
dank 882a9f6808 docs: own the un-guard procedure and the browser-only law
AGENTS.md states the three exposure postures and why the guard chain cannot front a
non-browser client. Infrastructure/Caddy.md gains *Unguard a vhost* — route first,
IdP rows last, audit lists after — since deleting the proxy provider while traffic
still enters :8016 hands browsers authentik's error page. The Audiobookshelf page
retires its two-doors law (the guarded vhost as a browser-only door, remote
listening via VPN) for one address every client carries, adds how a listener gets an
account, and says plainly what the exemption costs.
2026-11-03 01:54:08 -08:00
dank aa57c1a894 fix(authentik): register the mobile OIDC redirect on every ABS origin
The mobile-redirect URI was registered on the LAN origin only, because the phone
had to be configured with the LAN address while the vhost was guarded. Now that the
app rides the public name it sends the public mobile-redirect URI, and the RP's
STRICT matching answers an unregistered one with "Redirect URI Error" — a phone
locked out at the last step of a login that looked healthy. Both callback URIs are
now emitted for every entry in BASES.
2026-11-03 01:54:08 -08:00
dank 2a48751283 feat(caddy): serve Audiobookshelf outside the guard chain
The Audiobookshelf phone app could never sign in over the public name. The guard
chain answered its /ping and /socket.io/ handshake with Anubis challenge HTML, so
the app evaluated 'isInit' in e.data on a non-object and reported "Failed to
contact server" while the container's request log recorded nothing at all. Anubis
demands a JavaScript proof-of-work and the authentik outpost works from a session
cookie: the chain is browser-only by construction, and a native client, a podcast
RSS reader or an OPDS client cannot pass it.

Audiobookshelf authenticates itself (local accounts plus authentik OIDC), so it
joins jelly/plex/*arr in the exempt set: the public site block dials the container
directly, keeping the X-Forwarded-Proto pin its OIDC origin check depends on, and
the @audiobooks route leaves the post-auth listener so one host has one owner. The
IdP rows that guarded the name go after this deploys, never before.

tests/e2e/specs/14-guard.spec.ts loses audiobooks as a guarded target and gains the
exemption assertion (app bytes to an anonymous client, the app's own 401, never
challenge HTML); tests/anubis.bats pins the route shape.
2026-11-03 01:54:08 -08:00
dank 6083903ccb Merge pull request 'fix(e2e): judge only body-carrying script responses in the ABS SSO spec' (#132) from fix/e2e-mime-304 into main 2026-11-03 01:42:28 -08:00
dank ee9eb353fb fix(e2e): judge only body-carrying script responses in the ABS SSO spec
A 304 carries no Content-Type by spec, and the OIDC bounce revalidates the entry script (Cache-Control: max-age=0 + ETag), so the blanket MIME assertion failed on a healthy bundle. The HTML-stub class is still caught: 200s are still judged, and the count floor now runs on the same filtered set.
2026-11-03 01:39:39 -08:00
dank df597bb1b1 Merge pull request 'fix(abs-guard): audiobooks.franksdank.cloud completes OIDC (forwarded scheme + provider OAuth defaults)' (#131) from fix/abs-guard-oauth-defaults into main 2026-11-03 01:26:32 -08:00
Frank 23ad0ca317 chore(abs): name the Audiobookshelf door audiobooks.franksdank.cloud
`abs.` was a three-letter abbreviation of the container; the library it serves is
/mnt/entropy/Audiobooks, and `audiobooks.` says what the door is. Renamed in one
commit on purpose: the pihole seam fails the suite if a Caddyfile vhost has no
addn-hosts record, so any split would leave a red commit, and the `abs` name was
never deployed at the IdP with this fix, so nothing has to keep answering it —
no alias, no redirect, no stale URI.

Moves together: the Caddyfile site block, its matcher (@abs -> @audiobooks), the
LAN record, ABS_EXTRA_BASES in authentik-audiobookshelf-sso.sh (that script
REPLACES the provider's redirect-URI list, so the old origin's callback leaves the
RP when it is next run), the homepage admin tile, the guard spec's GUARDED list and
leak marker, and the three vault pages that name the door. authentik's provider row
follows in the deploy window (external_host + name), which re-derives its
redirect_uris through scripts/authentik-proxy-provider-defaults.sh; the Application
row keeps its own name, "Audiobookshelf Guard" — it names the app, not the vhost.
2026-11-03 01:23:54 -08:00
Frank 71d5804eaf docs(caddy): put both auth walls in the guard-a-new-vhost procedure
Step 1 gains the closing sentence the abs-proxy incident demands: the ak-shell
snippet that creates a ProxyProvider with objects.create() must be followed by
scripts/authentik-proxy-provider-defaults.sh, or the guard fails before any
credential. Step 2 gains the forwarded-scheme clause for any app that derives its
own origin from the request. Each fact keeps one owner — the laws stay on
[[Authentik Outpost Reference]] (provisioning rule 3) and [[Audiobookshelf]]; this
page only says where in the procedure they bite. Outpost page timestamp bumped to
match its substantive edit.
2026-11-03 01:17:12 -08:00