Files
serverAppConfigs/serverAppConfigs-docs/Dankserver/Audiobookshelf.md
T
Frank b7f4b7e148 docs(audiobookshelf): one page for the endpoint, and the rescan law at the relayout
The page owns the shape, the first-run account step (ABS accepts the initial
account only from a local-network address) and the laws: why the array bind is
read-only and what the one-line rollback is if on-disk sidecars are ever wanted;
why the metadata view is not that rollback; why the mount gate, the wget health
check and the amd64 digest pin are each mandatory; and that LAN-only is a posture,
not an accident.

Media Audiobook Relayout gains the third reader: ABS stores absolute paths, so the
relayout procedure has to reach its library too or it keeps rows for folders that
are gone. The vault index is regenerated, not hand-carved.
2026-11-02 02:19:29 -08:00

5.0 KiB

type, description, tags, timestamp
type description tags timestamp
reference Audiobookshelf on Dankserver: quadlet shape, the read-only array bind, state on the SSD, first-run account, laws.
dankserver
audiobooks
quadlet
2026-11-02

Audiobookshelf

Audiobookshelf (ABS) serves the Entropy Volume audiobook library to browsers and phones: it scans /mnt/entropy/Audiobooks, keeps its own SQLite library with 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, no vhost, no DNS name.

Shape

Item Value
Unit file dankserver/audiobookshelf/quadlet/audiobookshelf.container (the authority for every number here)
Image ghcr.io/advplyr/audiobookshelf pinned by amd64 manifest digest — see Laws
Port PublishPort=8000:8000 — web UI and API on the same port
Network pasta (standalone; publishes nothing to other containers)
Audio bind /mnt/entropy/Audiobooks:/audiobooks:ro — read-only, by design
State /home/dank/Containers/audiobookshelf/config → /config, …/metadata → /metadata (both NVMe)
Mount gate ExecStartPre=…/wait-for-mount.sh /mnt/entropy
Health wget against /healthcheck inside the container, 30 s / 3 retries
I/O share IOWeight=10 — a scan loses the array to every other tenant
Self-healing Restart=always, RestartSec=5, WantedBy=default.target
Deploy ./scripts/deploy.sh dankserver --service audiobookshelf
Handler deploy_audiobookshelf in scripts/deploy-remote.sh

First run

  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.

Nothing in this repo holds an ABS credential: the account is created in the browser and lives in the config bind on the SSD.

Laws

  • The audio bind stays :ro. ABS writes nothing that belongs beside the audio: its library, covers, progress and sidecar metadata all land in /metadata on the SSD. A writable bind would put stray writes on the spindle and hand the array to a service with no reason to hold it. If per-book .opf/.nfo sidecars on disk are ever wanted, the rollback is one line — point the audio bind at the Media Metadata View the media servers already use — but that view's upper layer exists for those two servers' sidecars, so a third writer needs a reason first (Media Metadata View).
  • Do not bind the raw array without the mount gate. In a rootless unit ConditionPathIsMountPoint= is fail-dead and RequiresMountsFor= is inert, so a missing array does not stop the start: podman invents an empty directory, ABS indexes it, and an empty library becomes recorded truth. wait-for-mount.sh is the only gate that works here — same law the media servers obey (Quadlet Lifecycle).
  • The HealthCmd must use wget. The image is Alpine and ships wget, not curl; a curl health command reads a perfectly healthy server as unhealthy and Restart=always turns that into a loop.
  • The image pin is the amd64 manifest digest, not the index digest. podman inspect reports the platform manifest (RepoDigests[0]) for what was pulled, which differs from the manifest-list digest latest points at. Pin the one this host runs. latest is newer than the newest version tag: upstream stopped publishing version tags after 2.19.0 while latest kept moving, so "highest version tag" is stale here — read the index, don't guess from tags.
  • IOWeight=10 is inert without the host drop-ins. dankserver/host/systemd/ carries user.slice.d/io-accounting.conf and the user@1000 delegate, and the deployer installs both; without them systemd silently drops the weight (Quadlet Lifecycle).
  • 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).

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