|
|
|
@@ -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).
|