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.
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. |
|
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
- Open
http://192.168.0.235:8000from the LAN. ABS accepts the initial account creation only from a local-network address; a remote address gets a refusal, not a form. - 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. - The first scan reads the whole tree once. It runs in the background — the
deploy gate is already green by then, and
IOWeight=10keeps 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/metadataon 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/.nfosidecars 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 andRequiresMountsFor=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.shis the only gate that works here — same law the media servers obey (Quadlet Lifecycle). - The
HealthCmdmust usewget. The image is Alpine and shipswget, notcurl; acurlhealth command reads a perfectly healthy server as unhealthy andRestart=alwaysturns that into a loop. - The image pin is the amd64 manifest digest, not the index digest.
podman inspectreports the platform manifest (RepoDigests[0]) for what was pulled, which differs from the manifest-list digestlatestpoints at. Pin the one this host runs.latestis newer than the newest version tag: upstream stopped publishing version tags after2.19.0whilelatestkept moving, so "highest version tag" is stale here — read the index, don't guess from tags. IOWeight=10is inert without the host drop-ins.dankserver/host/systemd/carriesuser.slice.d/io-accounting.confand theuser@1000delegate, 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.targetin the unit file is what carries the service across a reboot (Quadlet Lifecycle). - LAN-only until Frank says otherwise.
0.0.0.0:8000with 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 inAGENTS.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).