fix(audiobookshelf): set the port with the env names the image actually reads

The first deploy of the unit failed its own health gate in the worst possible shape:
systemd said active (running), the engine logged a clean database init, and the
server then listened on :80 under base path /audiobookshelf - so every probe of
:8000 was refused and wait_for_health timed out at 180s beside a container that
looked healthy.

Cause: ABS_PORT / ABS_CONFIG_DIR / ABS_METADATA_DIR / ABS_PUID are the npm
package's names, printed by the server README; this image never reads them. Its own
contract, read off the pinned digest with podman inspect, is PORT=80
CONFIG_PATH=/config METADATA_PATH=/metadata, and the server greps for exactly PORT,
CONFIG_PATH, METADATA_PATH, ROUTER_BASE_PATH. The two path variables appeared to
work only because the image already exports the same values - the coincidence that
made the inert lines look correct.

The unit now speaks the image's names, adds ROUTER_BASE_PATH=/ so the service is
the plain host:port the docs and the homepage card promise, and the dead ABS_*
lines are deleted instead of kept as decoration. Proven against the same digest in
a throwaway container (temp state dirs, loopback-only mapping, removed after):
base path "/", listening on :8000, GET /healthcheck -> 200, and the old prefixed
path -> 404.

Guards: the quadlet seam now refuses an Environment=ABS_* line and requires
PORT/ROUTER_BASE_PATH; the vault page gains the law with its failure signature and
the two-second image-inspect check. Suite: ALL TESTS PASSED, 812 ok.
This commit is contained in:
Frank committed 2026-11-02 02:28:37 -08:00
1 parent a6ec644ecb
commit 3c035ccef3
3 files changed
+34 -6

No files matched your search

@@ -26,11 +26,18 @@ After=network.target
ContainerName=audiobookshelf ContainerName=audiobookshelf
Image=ghcr.io/advplyr/audiobookshelf:latest@sha256:37c2057e150694970442579815c646fe6bdc01c95b9d1547c4c9068be8844ef3 Image=ghcr.io/advplyr/audiobookshelf:latest@sha256:37c2057e150694970442579815c646fe6bdc01c95b9d1547c4c9068be8844ef3
Network=pasta Network=pasta
Environment=ABS_CONFIG_DIR=/config # The names are the image's own: the server reads PORT, CONFIG_PATH, METADATA_PATH
Environment=ABS_METADATA_DIR=/metadata # and ROUTER_BASE_PATH (measured against the digest below — `podman inspect` shows
Environment=ABS_PGID=1000 # PORT=80 CONFIG_PATH=/config METADATA_PATH=/metadata in the image ENV, and the
Environment=ABS_PORT=8000 # server's source greps for exactly these). The ABS_* family printed by the server
Environment=ABS_PUID=1000 # README is the npm package's naming and is INERT here: with ABS_PORT=8000 the
# server listened on :80 under base path /audiobookshelf, so the unit sat
# `active (running)` while every probe to :8000 was refused (measured 2026-11-02,
# first deploy). ROUTER_BASE_PATH=/ is what makes the URL the plain host:port.
Environment=PORT=8000
Environment=ROUTER_BASE_PATH=/
Environment=CONFIG_PATH=/config
Environment=METADATA_PATH=/metadata
Environment=TZ=America/Vancouver Environment=TZ=America/Vancouver
PublishPort=8000:8000 PublishPort=8000:8000
Volume=/home/dank/Containers/audiobookshelf/config:/config Volume=/home/dank/Containers/audiobookshelf/config:/config
@@ -25,7 +25,8 @@ manager) — **Internal-only:** LAN address, no vhost, no DNS name.
|------|-------| |------|-------|
| Unit file | `dankserver/audiobookshelf/quadlet/audiobookshelf.container` (the authority for every number here) | | 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 | | Image | `ghcr.io/advplyr/audiobookshelf` pinned by **amd64 manifest digest** — see Laws |
| Port | `PublishPort=8000:8000` — web UI and API on the same port | | Port | `Environment=PORT=8000` + `PublishPort=8000:8000` — web UI and API on one port |
| Base path | `Environment=ROUTER_BASE_PATH=/` — the URL is the plain `host:port`, no `/audiobookshelf` prefix |
| Network | `pasta` (standalone; publishes nothing to other containers) | | Network | `pasta` (standalone; publishes nothing to other containers) |
| Audio bind | `/mnt/entropy/Audiobooks:/audiobooks:ro` — read-only, by design | | Audio bind | `/mnt/entropy/Audiobooks:/audiobooks:ro` — read-only, by design |
| State | `/home/dank/Containers/audiobookshelf/config` → `/config`, `…/metadata` → `/metadata` (both NVMe) | | State | `/home/dank/Containers/audiobookshelf/config` → `/config`, `…/metadata` → `/metadata` (both NVMe) |
@@ -52,6 +53,14 @@ browser and lives in the `config` bind on the SSD.
## Laws ## Laws
- **Speak the image's env names, not the README's.** The server reads `PORT`,
`CONFIG_PATH`, `METADATA_PATH` and `ROUTER_BASE_PATH`; the `ABS_*` family printed
by the server's own README is the npm package's naming and is **inert** in this
image. Measured 2026-11-02 on the first deploy: with `ABS_PORT=8000` the server
listened on `:80` under base path `/audiobookshelf`, so `systemctl` said
`active (running)` while every probe of `:8000` was refused and the deploy gate
failed at 180 s. Two seconds with the pinned digest settles it:
`podman inspect --format '{{.Config.Env}}' <image>`.
- **The audio bind stays `:ro`.** ABS writes nothing that belongs beside the - **The audio bind stays `:ro`.** ABS writes nothing that belongs beside the
audio: its library, covers, progress and sidecar metadata all land in 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 `/metadata` on the SSD. A writable bind would put stray writes on the spindle
+12
View File
@@ -175,3 +175,15 @@ load helpers
printf '%s\n' "$body" | grep -q 'audiobookshelf/metadata' || return 1 printf '%s\n' "$body" | grep -q 'audiobookshelf/metadata' || return 1
printf '%s\n' "$body" | grep -q 'wait_for_health audiobookshelf' || return 1 printf '%s\n' "$body" | grep -q 'wait_for_health audiobookshelf' || return 1
} }
@test "audiobookshelf sets its port and base path with the names the image reads" {
# Measured 2026-11-02 on the first deploy: ABS_PORT / ABS_CONFIG_DIR are the npm
# package's names and this image ignores them, so the server came up on :80
# under base path /audiobookshelf and the deploy gate timed out beside a unit
# that was active (running). PORT and ROUTER_BASE_PATH are what the image's own
# ENV block and the server source actually use.
unit="$REPO_ROOT/dankserver/audiobookshelf/quadlet/audiobookshelf.container"
grep -q '^Environment=PORT=8000$' "$unit" || return 1
grep -q '^Environment=ROUTER_BASE_PATH=/$' "$unit" || return 1
! grep -qE '^Environment=ABS_' "$unit" || return 1
}