docs(audiobookshelf): the first run is a script, and its re-run is the scan trigger
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.
This commit is contained in:
1 parent
317f36b456
commit
48ef6d34cd
3 files changed
+48
-16
No files matched your search
@@ -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
|
||||
|
||||
|
||||
@@ -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 (<id>)` / `exists` |
|
||||
| scan | `POST /api/libraries/<id>/scan` | `scan: accepted` |
|
||||
| proof | `GET /api/libraries/<id>/items?limit=1` | `items: <total>` |
|
||||
|
||||
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/<id>/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).
|
||||
@@ -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. |
|
||||
|
||||
Reference in new issue
Block a user