media: add audiobook support (bookshelf-audio, audiobookshelf) #376

Merged
mysticalsoap merged 2 commits from feat/audiobooks into trunk 2026-10-07 16:50:48 -04:00
Owner

Problem

No audiobook support in the stack. Bookshelf only holds one format per book, so the ebook instance can't also collect audio, and nothing serves audiobooks with chapters and resume: Jellyfin 12 plays them but has no FOSS phone app that tracks progress, and Calibre-Web-NextGen is ebook-only.

Fix

  • bookshelf-audio: second Bookshelf on the same image and rreading-glasses, own config dir, root folder /data/library/audiobooks, torrent category bookshelf-audio; unpackerr gets a READARR_1 entry.
  • audiobookshelf 2.37.1 at audiobooks.mysticalsoap.com, public and Authelia-bypassed like music. Runs as apps through user: (the image has no privilege-drop init), PORT moved off 80 for the unprivileged bind, config/metadata dirs pre-created with tracked .gitkeep. Accounts come from Authelia OIDC (audiobookshelf client, client_secret_basic + PKCE per Authelia's integration doc, audiobookshelf_users policy = one_factor for group:user, auto-register on, no group claim so everyone lands as a plain user); the official app, Lissen, Storii and Plappa all do the flow and return through the server's /auth/openid/mobile-redirect. Root stays local as break-glass. Secret file infra/secrets/authelia_oidc_audiobookshelf_client_secret.txt, user-created.
  • Authelia rules for both domains, gitignore, restic includes plus excludes for cache/streams/logs/backups, docs rows, setup/Media.md §7a/§8a, Adding-a-New-User.md §7a, README and topology regenerated.

Follow-ups filed separately: #378 CrowdSec detection for the native (root) login, #379 nightly quick-match job. #377 (knock provisioning) is superseded by auto-register.

Second commit, separable: www.hardcover.app stopped resolving, so rreading-glasses failed every upstream call; the binary's own default api.hardcover.app is right, so the --upstream override is dropped. Found while testing search on the new instance.

Verification

On the live stack from this branch:

  • just up media: bookshelf-audio and audiobookshelf both healthy; every other media container stayed healthy.
  • docker top audiobookshelf: tini and node run as apps; log shows Listening on port :13378.
  • https://audiobooks.mysticalsoap.com/ → 200 with the Audiobookshelf UI; /healthcheck → 200; /status reports serverVersion 2.37.1, authMethods ["local"], isInit false (root account not claimed yet).
  • https://bookshelf-audio.mysticalsoap.com/ → 302 to auth.mysticalsoap.com after just recreate authelia.
  • unpackerr reports one Readarr server until BOOKSHELF_AUDIO_API_KEY is filled in (key exists only after first start; §7a covers it).
  • docker compose config --quiet on media: clean with the three new .env variables set.
  • rreading-glasses after the upstream fix: /search?q=project+hail+mary answers 200 in 0.26s; both Bookshelf instances, pointed at http://rreading-glasses:8788 (they were on the public hardcover.bookinfo.pro, which was timing out at 100s), return results through their own /api/v1/search in 2 to 4s.
  • Ebook instance's torrent category renamed readarr → bookshelf (save path /data/torrents/bookshelf, matching UN_READARR_0_PATH); the four seeding torrents moved in place.
  • OIDC: Authelia starts clean with the hashed client secret (no deprecation warning); the browser flow's authorize request (redirect URI under /audiobookshelf) gets a 303 into Authelia's login flow; first "Login with Authelia" as mysticalsoap auto-registered the account (server log: openid: Auto-registering user, matched on preferred_username).
  • Phone flow: Lissen (F-Droid) logged in through the same Authelia client via lissen://oauth; a second LLDAP account auto-registered the same way. just deploy --dry-run reports no changed stacks, so live already runs the branch.

Manual steps from setup/Media.md (root account, library, OIDC settings, Bookshelf-audio client/Prowlarr/root folder/profile) are all done on the live instance; the audiobooks A record is in and the healthcheck answers through the VPS relay.

🤖 Generated with Claude Code

## Problem No audiobook support in the stack. Bookshelf only holds one format per book, so the ebook instance can't also collect audio, and nothing serves audiobooks with chapters and resume: Jellyfin 12 plays them but has no FOSS phone app that tracks progress, and Calibre-Web-NextGen is ebook-only. ## Fix - `bookshelf-audio`: second Bookshelf on the same image and rreading-glasses, own config dir, root folder `/data/library/audiobooks`, torrent category `bookshelf-audio`; unpackerr gets a `READARR_1` entry. - `audiobookshelf` 2.37.1 at `audiobooks.mysticalsoap.com`, public and Authelia-bypassed like music. Runs as `apps` through `user:` (the image has no privilege-drop init), `PORT` moved off 80 for the unprivileged bind, config/metadata dirs pre-created with tracked `.gitkeep`. Accounts come from Authelia OIDC (`audiobookshelf` client, `client_secret_basic` + PKCE per Authelia's integration doc, `audiobookshelf_users` policy = `one_factor` for `group:user`, auto-register on, no group claim so everyone lands as a plain user); the official app, Lissen, Storii and Plappa all do the flow and return through the server's `/auth/openid/mobile-redirect`. Root stays local as break-glass. Secret file `infra/secrets/authelia_oidc_audiobookshelf_client_secret.txt`, user-created. - Authelia rules for both domains, gitignore, restic includes plus excludes for cache/streams/logs/backups, docs rows, `setup/Media.md` §7a/§8a, `Adding-a-New-User.md` §7a, README and topology regenerated. Follow-ups filed separately: #378 CrowdSec detection for the native (root) login, #379 nightly quick-match job. #377 (knock provisioning) is superseded by auto-register. Second commit, separable: `www.hardcover.app` stopped resolving, so rreading-glasses failed every upstream call; the binary's own default `api.hardcover.app` is right, so the `--upstream` override is dropped. Found while testing search on the new instance. ## Verification On the live stack from this branch: - `just up media`: `bookshelf-audio` and `audiobookshelf` both `healthy`; every other media container stayed healthy. - `docker top audiobookshelf`: tini and node run as `apps`; log shows `Listening on port :13378`. - `https://audiobooks.mysticalsoap.com/` → 200 with the Audiobookshelf UI; `/healthcheck` → 200; `/status` reports `serverVersion 2.37.1`, `authMethods ["local"]`, `isInit false` (root account not claimed yet). - `https://bookshelf-audio.mysticalsoap.com/` → 302 to `auth.mysticalsoap.com` after `just recreate authelia`. - unpackerr reports one Readarr server until `BOOKSHELF_AUDIO_API_KEY` is filled in (key exists only after first start; §7a covers it). - `docker compose config --quiet` on media: clean with the three new `.env` variables set. - rreading-glasses after the upstream fix: `/search?q=project+hail+mary` answers 200 in 0.26s; both Bookshelf instances, pointed at `http://rreading-glasses:8788` (they were on the public `hardcover.bookinfo.pro`, which was timing out at 100s), return results through their own `/api/v1/search` in 2 to 4s. - Ebook instance's torrent category renamed `readarr` → `bookshelf` (save path `/data/torrents/bookshelf`, matching `UN_READARR_0_PATH`); the four seeding torrents moved in place. - OIDC: Authelia starts clean with the hashed client secret (no deprecation warning); the browser flow's authorize request (redirect URI under `/audiobookshelf`) gets a 303 into Authelia's login flow; first "Login with Authelia" as `mysticalsoap` auto-registered the account (server log: `openid: Auto-registering user`, matched on `preferred_username`). - Phone flow: Lissen (F-Droid) logged in through the same Authelia client via `lissen://oauth`; a second LLDAP account auto-registered the same way. `just deploy --dry-run` reports no changed stacks, so live already runs the branch. Manual steps from `setup/Media.md` (root account, library, OIDC settings, Bookshelf-audio client/Prowlarr/root folder/profile) are all done on the live instance; the `audiobooks` A record is in and the healthcheck answers through the VPS relay. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
media: add audiobook support (bookshelf-audio, audiobookshelf)
All checks were successful
validate / validate (pull_request) Successful in 22s
0b703b2102
A Bookshelf instance holds one format per book, so audiobooks get a
second instance on the same image and rreading-glasses, with its own
root folder and torrent category.

Audiobookshelf serves the library at heed.* (verb name, friend-facing
like music/listen). The image runs as root with no privilege-drop init,
hence `user:` plus pre-created config/metadata dirs, and PORT moved off
80 for the unprivileged bind. Its OIDC stays off: an OIDC-created
account has no local password and most phone clients only do password
login, so it keeps local accounts like Navidrome. Knock provisioning
and CrowdSec login detection are follow-ups.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
mysticalsoap force-pushed feat/audiobooks from 0b703b2102
All checks were successful
validate / validate (pull_request) Successful in 22s
to 6ffe41c62c
All checks were successful
validate / validate (pull_request) Successful in 23s
2026-10-07 14:17:19 -04:00
Compare
media: drop the rreading-glasses upstream override
All checks were successful
validate / validate (pull_request) Successful in 26s
3abe806093
www.hardcover.app no longer resolves; the binary's own default is
api.hardcover.app, where Hardcover's GraphQL API now lives. Without
this every upstream call fails and Bookshelf falls back to prompting
for Goodreads IDs.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
mysticalsoap force-pushed feat/audiobooks from 3abe806093
All checks were successful
validate / validate (pull_request) Successful in 26s
to bc7b1dd93f
All checks were successful
validate / validate (pull_request) Successful in 25s
2026-10-07 15:14:42 -04:00
Compare
mysticalsoap force-pushed feat/audiobooks from bc7b1dd93f
All checks were successful
validate / validate (pull_request) Successful in 25s
to fe44ac9ba4
All checks were successful
validate / validate (pull_request) Successful in 25s
2026-10-07 15:55:42 -04:00
Compare
mysticalsoap force-pushed feat/audiobooks from fe44ac9ba4
All checks were successful
validate / validate (pull_request) Successful in 25s
to 33cd1001e6
All checks were successful
validate / validate (pull_request) Successful in 25s
2026-10-07 16:11:10 -04:00
Compare
mysticalsoap force-pushed feat/audiobooks from 33cd1001e6
All checks were successful
validate / validate (pull_request) Successful in 25s
to 4f8caa63c1
All checks were successful
validate / validate (pull_request) Successful in 24s
2026-10-07 16:14:21 -04:00
Compare
mysticalsoap force-pushed feat/audiobooks from 4f8caa63c1
All checks were successful
validate / validate (pull_request) Successful in 24s
to ff5b9a14b0
All checks were successful
validate / validate (pull_request) Successful in 23s
2026-10-07 16:24:58 -04:00
Compare
mysticalsoap force-pushed feat/audiobooks from ff5b9a14b0
All checks were successful
validate / validate (pull_request) Successful in 23s
to ce71dc6d7a
All checks were successful
validate / validate (pull_request) Successful in 25s
2026-10-07 16:31:24 -04:00
Compare
mysticalsoap deleted branch feat/audiobooks 2026-10-07 16:50:48 -04:00
Sign in to join this conversation.
No description provided.