feat(audiobookshelf): the audiobook server as a boot-persistent quadlet
Audiobookshelf serves /mnt/entropy/Audiobooks to browsers and phones with its own library and per-listener positions. The design point of the unit: every byte this server writes is its own state - SQLite library, accounts, progress, cached covers - and all of it lands on the NVMe under /home/dank/Containers/audiobookshelf. The audio bind is :ro, so a stray sidecar write onto the spindle is structurally impossible rather than merely discouraged, and the array keeps exactly one sanctioned write path (scripts/media-view.sh). Three failure modes the unit is shaped around: - Bind-source ordering. In a rootless unit RequiresMountsFor= is inert and ConditionPathIsMountPoint= is fail-dead, so a missing array does not stop the start: podman invents an empty directory and the first scan records an empty library as truth. ExecStartPre=wait-for-mount.sh is the gate that works here, the same one copyparty uses. - The health binary. The image is Alpine: wget, no curl. A curl HealthCmd reads a healthy server as unhealthy and Restart=always turns that into a loop. - The image pin. podman records the linux/amd64 manifest for what it pulled, not the manifest-list digest `latest` points at, so the amd64 digest is the pin that names the build this host runs. Upstream version tags stop at 2.19.0 while `latest` is newer, so latest@digest is the only form that is both current and exact - the jellyfin/romm precedent. The first-boot scan reads the whole tree once, so IOWeight=10 keeps it behind playback; that weight is only honoured because the user.slice accounting drop-in and the user@1000 delegation are installed by the deployer. Deployer registration is the standard four touch-points (client gate, remote allowlist + handler + dispatcher, planner order/set/path map, test routing). The handler creates both state roots before the first start - letting podman invent them leaves the SQLite file unwritable by the container user under a rootless first boot - and gates on /healthcheck.
This commit is contained in:
1 parent
29aefc3b6f
commit
511d8dca93
6 files changed
+133
-1
No files matched your search
@@ -60,6 +60,7 @@ SERVICE_ORDER = (
|
||||
"calibre-server",
|
||||
"plex",
|
||||
"tautulli",
|
||||
"audiobookshelf",
|
||||
"prowlarr",
|
||||
"qbittorrent",
|
||||
"radarr",
|
||||
@@ -123,6 +124,7 @@ DANKSERVER_SERVICES = {
|
||||
"calibre-server",
|
||||
"plex",
|
||||
"tautulli",
|
||||
"audiobookshelf",
|
||||
"prowlarr",
|
||||
"qbittorrent",
|
||||
"radarr",
|
||||
@@ -420,6 +422,7 @@ def services_for_path(path: str, target: str) -> set[str]:
|
||||
"copyparty": {"copyparty"},
|
||||
"plex": {"plex"},
|
||||
"tautulli": {"tautulli"},
|
||||
"audiobookshelf": {"audiobookshelf"},
|
||||
}
|
||||
_add(services, *quadlet_services.get(parts[1], set()))
|
||||
if len(parts) >= 2 and parts[1] == "host":
|
||||
|
||||
Reference in new issue
Block a user