fix(audiobookshelf): mount the app at the base path its SPA was built for
ROUTER_BASE_PATH=/ served a healthy API and a web UI that could never boot. The index.html in the pinned image is built with routerBasePath:/audiobookshelf, so every script request fell through to the SPA fallback and came back as text/html, which Chrome refuses to execute. healthcheck, the API and the phone app all stayed fine, which is exactly how this hid behind an API-only deploy check. The deployer now reads the mounted base path out of the unit and hands it to the bootstrapper as ABS_URL, so the API door, the login route and the OIDC redirect subfolder cannot drift from the unit. healthcheck answers at the root under either base path, so the health gate is unchanged.
This commit is contained in:
1 parent
f3a6b4f715
commit
cfdd3435bc
3 files changed
+25
-5
No files matched your search
@@ -33,9 +33,16 @@ Network=pasta
|
|||||||
# README is the npm package's naming and is INERT here: with ABS_PORT=8000 the
|
# 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
|
# 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,
|
# `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.
|
# first deploy).
|
||||||
|
# ROUTER_BASE_PATH MUST be /audiobookshelf: the SPA in /app/client/dist/index.html is
|
||||||
|
# built with `routerBasePath:"/audiobookshelf"` baked in, so it asks for its assets under
|
||||||
|
# that prefix no matter what the server is mounted at. With `/` the server answers the
|
||||||
|
# page but every script request falls through to the SPA fallback and comes back as
|
||||||
|
# text/html — Chrome refuses to execute it and the UI never leaves "Loading…"
|
||||||
|
# (measured 2026-11-03; the API and /healthcheck were fine, which is why an API-only
|
||||||
|
# verification missed it).
|
||||||
Environment=PORT=8000
|
Environment=PORT=8000
|
||||||
Environment=ROUTER_BASE_PATH=/
|
Environment=ROUTER_BASE_PATH=/audiobookshelf
|
||||||
Environment=CONFIG_PATH=/config
|
Environment=CONFIG_PATH=/config
|
||||||
Environment=METADATA_PATH=/metadata
|
Environment=METADATA_PATH=/metadata
|
||||||
Environment=TZ=America/Vancouver
|
Environment=TZ=America/Vancouver
|
||||||
@@ -45,6 +52,8 @@ Volume=/home/dank/Containers/audiobookshelf/metadata:/metadata
|
|||||||
Volume=/mnt/entropy/Audiobooks:/audiobooks:ro
|
Volume=/mnt/entropy/Audiobooks:/audiobooks:ro
|
||||||
# The image is Alpine: it ships wget and no curl, so the health check must name
|
# 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.
|
# the binary that exists or the container is unhealthy while serving perfectly.
|
||||||
|
# /healthcheck answers `OK` at the root AND under the base path (measured on both
|
||||||
|
# base paths), so the probe below is correct whatever ROUTER_BASE_PATH says.
|
||||||
HealthCmd=wget -q -O /dev/null http://127.0.0.1:8000/healthcheck
|
HealthCmd=wget -q -O /dev/null http://127.0.0.1:8000/healthcheck
|
||||||
HealthInterval=30s
|
HealthInterval=30s
|
||||||
HealthTimeout=10s
|
HealthTimeout=10s
|
||||||
|
|||||||
@@ -1315,7 +1315,8 @@ deploy_audiobookshelf() {
|
|||||||
mkdir -p /home/dank/Containers/audiobookshelf/config \
|
mkdir -p /home/dank/Containers/audiobookshelf/config \
|
||||||
/home/dank/Containers/audiobookshelf/metadata
|
/home/dank/Containers/audiobookshelf/metadata
|
||||||
systemctl --user restart audiobookshelf.service
|
systemctl --user restart audiobookshelf.service
|
||||||
# /healthcheck answers once the HTTP server is bound; the library scan that
|
# /healthcheck answers once the HTTP server is bound, at the root whatever
|
||||||
|
# ROUTER_BASE_PATH says (measured on both base paths); the library scan that
|
||||||
# follows runs in the background and must not gate the deploy.
|
# follows runs in the background and must not gate the deploy.
|
||||||
wait_for_health audiobookshelf http://127.0.0.1:8000/healthcheck 90
|
wait_for_health audiobookshelf http://127.0.0.1:8000/healthcheck 90
|
||||||
# A server with no root user and no library is "healthy" and useless, so the
|
# A server with no root user and no library is "healthy" and useless, so the
|
||||||
@@ -1323,7 +1324,12 @@ deploy_audiobookshelf() {
|
|||||||
# it creates the account (credential in the mode-600 host secret file), the
|
# it creates the account (credential in the mode-600 host secret file), the
|
||||||
# Audiobooks library over the /audiobooks bind, and fires the scan. Every
|
# Audiobooks library over the /audiobooks bind, and fires the scan. Every
|
||||||
# step is idempotent, so a re-deploy costs one incremental scan.
|
# step is idempotent, so a re-deploy costs one incremental scan.
|
||||||
scripts/audiobookshelf-init.sh
|
# The bootstrapper has to speak the same base path the unit mounts (the API, the
|
||||||
|
# login route and the OIDC redirect subfolder all hang off it), so the URL is read
|
||||||
|
# out of the unit file here instead of being restated in the script's default.
|
||||||
|
abs_base=$(awk -F= '/^Environment=ROUTER_BASE_PATH=/{print $3}' \
|
||||||
|
dankserver/audiobookshelf/quadlet/audiobookshelf.container)
|
||||||
|
ABS_URL="http://127.0.0.1:8000${abs_base}" scripts/audiobookshelf-init.sh
|
||||||
}
|
}
|
||||||
|
|
||||||
# The content server's single admin account and the desktop's basic-auth
|
# The content server's single admin account and the desktop's basic-auth
|
||||||
|
|||||||
@@ -182,8 +182,13 @@ load helpers
|
|||||||
# under base path /audiobookshelf and the deploy gate timed out beside a unit
|
# 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
|
# that was active (running). PORT and ROUTER_BASE_PATH are what the image's own
|
||||||
# ENV block and the server source actually use.
|
# ENV block and the server source actually use.
|
||||||
|
# The base path is not a free choice either: /app/client/dist/index.html inside the
|
||||||
|
# pinned image carries routerBasePath:"/audiobookshelf", so a server mounted at / hands
|
||||||
|
# out a page whose every script request falls through to the SPA fallback and comes back
|
||||||
|
# text/html — the UI never leaves "Loading…" while the API stays healthy (measured
|
||||||
|
# 2026-11-02, which is exactly how it hid behind an API-only verification).
|
||||||
unit="$REPO_ROOT/dankserver/audiobookshelf/quadlet/audiobookshelf.container"
|
unit="$REPO_ROOT/dankserver/audiobookshelf/quadlet/audiobookshelf.container"
|
||||||
grep -q '^Environment=PORT=8000$' "$unit" || return 1
|
grep -q '^Environment=PORT=8000$' "$unit" || return 1
|
||||||
grep -q '^Environment=ROUTER_BASE_PATH=/$' "$unit" || return 1
|
grep -q '^Environment=ROUTER_BASE_PATH=/audiobookshelf$' "$unit" || return 1
|
||||||
! grep -qE '^Environment=ABS_' "$unit" || return 1
|
! grep -qE '^Environment=ABS_' "$unit" || return 1
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user