Two censuses this session reported exactly 1,500 and exactly 4,000 books for one
library mid-scan. Cause: an out-of-range offset returns the last item instead of an
empty page, so a loop written "until the page is empty" manufactures its own
total, and total itself moves while a scan is running. Stop on total; count after
the pass settles.
The first full scan logged "Invalid data found when processing input" per file and
carried on. That line is about the file: the scanner skips it, so the book never
appears in the library at all. Census of the 43 refused files: 38 were 0 bytes, 3
held WAV bytes behind a .mp3 name, 2 were unreadable. The check to run first is a
byte count, not a server investigation.
Full suite: 818 tests, exit 0.
The page claimed the account can only be created from a LAN address; measured,
the door is POST /init and it answers plain text. Replaced with the bootstrap the
deployer now runs, and with the doors a consumer actually needs: /status reports
isInit with no /api prefix, the item count rides /api/libraries/{id}/items ->
total (the library DTO has no totals), and the list DTO carries media.numTracks
while /api/items/{id} returns media.audioFiles.
New law: the folder watcher cannot cross the rootless podman bind — the same gap
as Jellyfin's auto-scan note — so files added on the host stay invisible until a
scan runs, and the deployer's re-run of the bootstrapper is the cheap trigger.
Testing Policy lists the new seam; index.md regenerated from frontmatter.
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.
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.