From 48ef6d34cdfc2bc656800ad820b09dfa64915fa7 Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 2 Nov 2026 02:55:55 -0800 Subject: [PATCH] docs(audiobookshelf): the first run is a script, and its re-run is the scan trigger MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The page claimed the account can only be created from a LAN address; measured, the door is POST /init and it answers plain text. Replaced with the bootstrap the deployer now runs, and with the doors a consumer actually needs: /status reports isInit with no /api prefix, the item count rides /api/libraries/{id}/items -> total (the library DTO has no totals), and the list DTO carries media.numTracks while /api/items/{id} returns media.audioFiles. New law: the folder watcher cannot cross the rootless podman bind — the same gap as Jellyfin's auto-scan note — so files added on the host stay invisible until a scan runs, and the deployer's re-run of the bootstrapper is the cheap trigger. Testing Policy lists the new seam; index.md regenerated from frontmatter. --- .../Architecture/Testing Policy.md | 2 +- .../Dankserver/Audiobookshelf.md | 60 ++++++++++++++----- serverAppConfigs-docs/index.md | 2 +- 3 files changed, 48 insertions(+), 16 deletions(-) diff --git a/serverAppConfigs-docs/Architecture/Testing Policy.md b/serverAppConfigs-docs/Architecture/Testing Policy.md index 7f132ce..69ce731 100644 --- a/serverAppConfigs-docs/Architecture/Testing Policy.md +++ b/serverAppConfigs-docs/Architecture/Testing Policy.md @@ -23,7 +23,7 @@ This repository tests its configuration at defined seams. The tests run with `ba | Deployment flow | `tests/deployment_flow.bats`, `tests/deployment_scoping.bats` | scoped dispatcher exists, targets are known, service plans isolate inference, Caddy ownership is guarded, and SSH reachability is checked (skips offline) | | Quadlet units | `tests/*quadlet*.bats` | every unit is boot-persistent, self-healing, repo-provenanced, secret-free | | E2E (browser) | `tests/e2e/` + `tests/e2e_scaffold.bats` | Playwright drives the live guard chain; the bats seam asserts structure and credential hygiene. See [[E2E Suite]] | -| Per-service / unit seams | `tests/llm_units.bats`, `tests/frank_{archon,dragoon,zealot}_unit.bats`, `tests/llm_secrets.bats`, `tests/homepage_{caddy,checker,config,deploy,pages}.bats`, `tests/{anubis,authentik,hermes_webui,pihole,mount_entropy,searxng_limiter,autoresearch_measure,changed_tests}.bats`, `tests/{caddy,dankserver,frankbook,marreta,observability,open_webui,searxng}_quadlet.bats`, `tests/comfyui_rocm.bats` | the ~30 named seams the summary rows above do not list: LLM systemd-unit shape and env hygiene, homepage config/pages/deploy behaviour, and one detail-seam per deployed service | +| Per-service / unit seams | `tests/llm_units.bats`, `tests/frank_{archon,dragoon,zealot}_unit.bats`, `tests/llm_secrets.bats`, `tests/homepage_{caddy,checker,config,deploy,pages}.bats`, `tests/{audiobookshelf_init,anubis,authentik,hermes_webui,pihole,mount_entropy,searxng_limiter,autoresearch_measure,changed_tests}.bats`, `tests/{caddy,dankserver,frankbook,marreta,observability,open_webui,searxng}_quadlet.bats`, `tests/comfyui_rocm.bats` | the ~30 named seams the summary rows above do not list: LLM systemd-unit shape and env hygiene, homepage config/pages/deploy behaviour, and one detail-seam per deployed service | ## Focused pre-commit testing diff --git a/serverAppConfigs-docs/Dankserver/Audiobookshelf.md b/serverAppConfigs-docs/Dankserver/Audiobookshelf.md index e856ca0..e079d20 100644 --- a/serverAppConfigs-docs/Dankserver/Audiobookshelf.md +++ b/serverAppConfigs-docs/Dankserver/Audiobookshelf.md @@ -1,6 +1,6 @@ --- type: reference -description: "Audiobookshelf on Dankserver: quadlet shape, the read-only array bind, state on the SSD, first-run account, laws." +description: "Audiobookshelf on Dankserver: quadlet shape, the read-only array bind, state on the SSD, the API bootstrap, laws." tags: - dankserver - audiobooks @@ -37,19 +37,32 @@ manager) — **Internal-only:** LAN address, no vhost, no DNS name. | Deploy | `./scripts/deploy.sh dankserver --service audiobookshelf` | | Handler | `deploy_audiobookshelf` in `scripts/deploy-remote.sh` | -## First run +## Bootstrap -1. Open `http://192.168.0.235:8000` **from the LAN**. ABS accepts the initial - account creation only from a local-network address; a remote address gets a - refusal, not a form. -2. Create the owner account, then add one **Audi Book** library with folder - `/audiobooks`. The path is the in-container name of the read-only bind. -3. The first scan reads the whole tree once. It runs in the background — the - deploy gate is already green by then, and `IOWeight=10` keeps it off the - critical path. +`scripts/audiobookshelf-init.sh` — run by the deployer after the health gate, so a +deployed ABS is a *usable* ABS: `/healthcheck` answers 200 on a server with no +user, no library and no scan, and nothing else in the deploy notices the +difference. The script is idempotent; re-running it costs one incremental scan. -Nothing in this repo holds an ABS credential: the account is created in the -browser and lives in the `config` bind on the SSD. +| Step | Door | Printed | +|------|------|---------| +| credential | `~/.config/secrets/audiobookshelf` (mode 600, generated once, host-side) | `credential: created …` | +| account | `POST /init` with `{"newRoot":{"username","password"}}` | `account: created (frank)` / `already registered` | +| session | `POST /login` → bearer token | `token: obtained (root)` | +| library | `POST /api/libraries`, folder `/audiobooks`, provider `audiobook` | `library: created Audiobooks ()` / `exists` | +| scan | `POST /api/libraries//scan` | `scan: accepted` | +| proof | `GET /api/libraries//items?limit=1` | `items: ` | + +Sign in at `http://192.168.0.235:8000` as `frank`; the password is +`awk -F= '/^ABS_ADMIN_PASSWORD=/{print $2}' ~/.config/secrets/audiobookshelf` on +Dankserver. Change it in **Settings → Users → frank → Password**; the secret file +then holds a stale value and only matters again if the `config` bind is ever +destroyed (a fresh DB makes the script create a new owner from it). + +The scan of 1,103 shelves is slow and background; watch `items:` climb from 0, +never `/healthcheck`. It also reads files that are genuinely broken and logs +`[AudioFileScanner] Invalid data found when processing input` per file — that is +the file, not the bind: the same line count grows while the total climbs. ## Laws @@ -95,10 +108,29 @@ browser and lives in the `config` bind on the SSD. 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`). +- **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 + bearer every `/api/*` route wants. `POST /setup` answers `Unauthorized` — that + is a missing route wearing an auth message. Read off `dist-server/server/Server.js` + inside the pinned digest rather than from a forum. +- **A 200 may carry plain text.** `res.sendStatus(200)` answers the body `OK`, and + `/init` and the scan route use it; a client that parses every success as JSON + crashes on the one call that matters (`tests/audiobookshelf_init.bats` pins it). +- **The item count is not a field on the library DTO.** The door is + `GET /api/libraries//items?limit=1` → `total`. A poller that guesses a + plausible name (`numItems`) prints `0` forever beside a scan that is working. +- **A fresh scan is the only thing that notices new audio.** `inotify` does not + cross a rootless podman bind — measured for [[Jellyfin]], whose page owns that + law — so the in-container watcher sees nothing a host process files onto the + array, and the deployer's re-run of the bootstrapper is the cheap scan trigger. ## Verification `./tests/run.sh` covers this unit through `tests/dankserver_quadlet.bats` (boot persistence, the SSD state roots, the `:ro` array bind, the mount gate, the -`wget` health check) and `tests/media_overlay.bats` (only [[Plex]] and -[[Jellyfin]] may reference the metadata view). +`wget` health check), `tests/audiobookshelf_init.bats` (the bootstrapper against a +stub of the API above: create-once, idempotent re-run, no secret in a URL, refusal +by URL when the server does not answer, and the deployer calling it only after the +health gate) and `tests/media_overlay.bats` (only [[Plex]] and [[Jellyfin]] may +reference the metadata view). diff --git a/serverAppConfigs-docs/index.md b/serverAppConfigs-docs/index.md index 20673f6..44e044e 100644 --- a/serverAppConfigs-docs/index.md +++ b/serverAppConfigs-docs/index.md @@ -44,7 +44,7 @@ is a locator: one row, one clause, no numbers. | Page | Owns | |---|---| -| [[Audiobookshelf]] | Audiobookshelf on Dankserver: quadlet shape, the read-only array bind, state on the SSD, first-run account, laws. | +| [[Audiobookshelf]] | Audiobookshelf on Dankserver: quadlet shape, the read-only array bind, state on the SSD, the API bootstrap, laws. | | [[Calibre]] | Calibre is the e-book library manager that backs Calibre-Web. | | [[Calibre-Web]] | Calibre-Web is a web application that browses, reads, and downloads eBooks from an existing Calibre library database. | | [[Copyparty]] | Copyparty is a lightweight file server and media streamer that runs as a single Python binary. |