Files
serverAppConfigs/dankserver/audiobookshelf/quadlet/audiobookshelf.container
T
Frank 3c035ccef3 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.
2026-11-02 02:28:37 -08:00

67 lines
3.3 KiB
INI

# Quadlet unit: Audiobookshelf audiobook server (rootless, boot-persistent).
#
# Install path on dankserver: ~/.config/containers/systemd/audiobookshelf.container
# Unit name: audiobookshelf.service (podman 5.x drops the container- prefix)
#
# Everything this server writes is its own state — the SQLite library, accounts,
# playback progress, cached covers — and it lives under
# /home/dank/Containers/audiobookshelf on the NVMe, so no metadata byte ever
# lands on the spindle. The audio itself is bound READ-ONLY off the array: the
# only sanctioned write path into /mnt/entropy is the overlay view
# (scripts/media-view.sh), Audiobookshelf has no need of it, and a read-only bind
# makes a stray sidecar write structurally impossible rather than discouraged.
#
# The digest is the linux/amd64 manifest of the tag, which is what podman
# verifies and what RepoDigests records on this single-arch host. Pinning the
# manifest-list digest instead would let a re-pull resolve to a different image.
# The upstream version tags stop at 2.19.0 (built 2025-02-02) while `latest` is
# newer by a year and a half, so `latest@digest` is the only pin that names the
# build actually running — same reasoning as the jellyfin and romm units.
[Unit]
Description=Audiobookshelf audiobook server container
After=network.target
[Container]
ContainerName=audiobookshelf
Image=ghcr.io/advplyr/audiobookshelf:latest@sha256:37c2057e150694970442579815c646fe6bdc01c95b9d1547c4c9068be8844ef3
Network=pasta
# The names are the image's own: the server reads PORT, CONFIG_PATH, METADATA_PATH
# and ROUTER_BASE_PATH (measured against the digest below — `podman inspect` shows
# PORT=80 CONFIG_PATH=/config METADATA_PATH=/metadata in the image ENV, and the
# server's source greps for exactly these). The ABS_* family printed by the server
# 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
PublishPort=8000:8000
Volume=/home/dank/Containers/audiobookshelf/config:/config
Volume=/home/dank/Containers/audiobookshelf/metadata:/metadata
Volume=/mnt/entropy/Audiobooks:/audiobooks:ro
# The image is Alpine: it ships wget and no curl, so the health check must name
# the binary that exists or the container is unhealthy while serving perfectly.
HealthCmd=wget -q -O /dev/null http://127.0.0.1:8000/healthcheck
HealthInterval=30s
HealthTimeout=10s
HealthRetries=3
[Service]
# Refuse to start until the array is a real mount — with the bind source missing,
# podman invents an empty directory and Audiobookshelf records an empty library as
# truth. See the header of scripts/wait-for-mount.sh for the blind-boot incident.
ExecStartPre=/home/dank/serverAppConfigs/scripts/wait-for-mount.sh /mnt/entropy
Restart=always
RestartSec=5
# The first boot scans the whole audiobook tree. Weighted LOW (default 100) so a
# scan loses to playback when the array is contended. Proportional share, not a
# cap: inert while the disk is idle.
IOWeight=10
[Install]
WantedBy=default.target