Overview
The book stack provides automated ebook and audiobook management with library organization, metadata management, and user request interfaces.Audiobookshelf
Audiobook server and player
Calibre-Web-Automated
Ebook library with metadata automation
Shelfmark
Book request interface
/opt/stacks/books/compose.yaml
Audiobookshelf
Purpose: Audiobook server and streaming player Access:- Internal:
https://audiobooks.giohosted.com - External:
https://audiobooks.giohosted.com(via Cloudflare Tunnel)
Features
- Web and mobile apps (iOS/Android)
- Progress tracking across devices
- Playback speed control and sleep timer
- Podcast support
- User management with individual libraries
- Chapter navigation
- OIDC authentication via Authentik
Library Configuration
Library path:/data/media/books/audiobooks/
Folder structure:
- M4B (preferred - single file with chapters)
- MP3
- M4A
- FLAC
- OGG
SSO Configuration
Authentication: OIDC via Authentik Known issue: Sub (subject) unlink fix required- Audiobookshelf may create duplicate users if sub claim changes
- Documented fix available in Authentik configuration
External Access
Protection:- Cloudflare Access with Authentik OIDC
- Access restricted to
usersandadminsgroups - Valid authentication required for streaming
- iOS: Audiobookshelf app
- Android: Audiobookshelf app
- Configure server URL:
https://audiobooks.giohosted.com
Calibre-Web-Automated (CWA)
Purpose: Ebook library management with automated metadata fetching and format conversion Access:https://calibre.giohosted.com
Features
- Web-based ebook reader
- Metadata management via Calibre backend
- Automatic metadata fetching from multiple sources
- Format conversion (EPUB, MOBI, AZW3, PDF)
- User libraries with individual bookshelves
- Send to Kindle integration
- OAuth2 authentication via Authentik
Library Configuration
Calibre library path:/data/media/books/ebooks/
This is the canonical source of truth for all ebooks. When ebooks are imported, they are:
- Organized by author and title
- Metadata fetched and applied
- Stored in Calibre database structure
Automated Workflow
Ingest process:- Shelfmark hardlinks downloaded ebook to
/data/downloads/books/ebooks/ingest/ - CWA detects new file in ingest directory
- Fetches metadata from configured sources
- Imports to Calibre library at
/data/media/books/ebooks/ - Deletes hardlink from ingest directory
- Original file in
/data/downloads/books/ebooks/downloads/continues seeding
Hardlink workflow ensures seeding continues after CWA import. v2 had a bug where CWA deleted the original file, breaking torrent seeding.
SSO Configuration
Authentication: OAuth2 via Authentik User linking: Manual per user- Users must link their Authentik account on first login
- Admin can pre-create accounts and users link during SSO
Shelfmark
Purpose: Book request interface for ebooks and audiobooks Access:- Internal:
https://books.giohosted.com - External:
https://books.giohosted.com(via Cloudflare Tunnel)
Features
- Search interface for ebooks and audiobooks
- User requests sent to configured sources
- Status tracking for requests
- Integration with private trackers (MAM)
- Automatic download via qBittorrent
- Hardlink creation for library import
Integration
Connected services:- qBittorrent (via Gluetun VPN)
- Calibre-Web-Automated (ebook import)
- Audiobookshelf (audiobook import)
- User requests ebook via Shelfmark
- Shelfmark searches indexers (e.g., MAM)
- Best match sent to qBittorrent
- qBittorrent downloads to
/data/downloads/books/ebooks/downloads/ - Shelfmark creates hardlink to
/data/downloads/books/ebooks/ingest/ - CWA imports from ingest directory
- CWA deletes hardlink after import
- Original file continues seeding in downloads directory
- User requests audiobook via Shelfmark
- Shelfmark searches indexers
- Download sent to qBittorrent
- qBittorrent downloads to
/data/downloads/books/audiobooks/downloads/ - Shelfmark creates hardlink to
/data/media/books/audiobooks/ - Audiobookshelf scans and adds to library
- Original file continues seeding in downloads directory
External Access
Protection:- Cloudflare Access with Authentik OIDC
- Access restricted to authorized users
- Request submissions logged and tracked
Storage Architecture
Directory Structure
On docker-prod-01 (/data = NFS mount to nas-prod-01):
Hardlink Workflow Explained
Ebook hardlink flow:| Step | Actor | Action | File Locations |
|---|---|---|---|
| 1 | qBittorrent | Downloads ebook | /data/downloads/books/ebooks/downloads/book.epub |
| 2 | Shelfmark | Creates hardlink | downloads/book.epub + ingest/book.epub (same inode) |
| 3 | CWA | Imports file | Reads from ingest/book.epub |
| 4 | CWA | Adds to library | Copies to /data/media/books/ebooks/Author/Book/ |
| 5 | CWA | Deletes ingest file | Removes ingest/book.epub hardlink |
| 6 | qBittorrent | Continues seeding | downloads/book.epub still exists (untouched) |
| 7 | qBitrr | Stops after 14 days | Enforces MAM 14-day minimum seed time |
- Hardlink = same file with two directory entries
- Deleting one directory entry doesn’t delete the file
- File only deleted when all hardlinks removed
- qBit sees original download path unchanged
MAM Seeding Requirements
Private tracker: MyAnonamouse (MAM) Minimum seed time: 14 days for ebooks and audiobooks Enforcement: qBitrr configured with category-specific rules- qBitrr automatically stops seeding
- Torrent removed from qBittorrent
- File remains in downloads directory (manual cleanup or automated script)
SSO and External Access
Audiobookshelf
OIDC via Authentik:- Users authenticate with Authentik credentials
- Accounts automatically created on first login
- Access controlled via Cloudflare Access policies
- Open Audiobookshelf app
- Enter server URL:
https://audiobooks.giohosted.com - Tap “Login with SSO”
- Authenticate via Authentik
- Mobile app receives session token
Calibre-Web-Automated
OAuth2 via Authentik:- Manual user linking required
- Admin creates user accounts in CWA
- Users link Authentik account on first OAuth login
Shelfmark
Cloudflare Access:- Protected via Cloudflare Access policies
- Authentik OIDC for user authentication
- Access restricted to
usersandadminsgroups
Backup Strategy
Docker appdata:/opt/appdata/audiobookshelf//opt/appdata/calibre-web-automated//opt/appdata/shelfmark/- Backed up nightly to NAS
/backupsvia rsync script
- Calibre library:
/data/media/books/ebooks/(on NAS parity array) - Audiobook library:
/data/media/books/audiobooks/(on NAS parity array) - Protected by Unraid dual parity
- Included in nightly Synology ABB cold copy
Troubleshooting Book Stack
Troubleshooting Book Stack
CWA not importing from ingest:
- Check ingest directory permissions: Should be
2000:2000 - Verify CWA ingest path configured:
/data/downloads/books/ebooks/ingest/ - Check CWA logs for import errors
- Manually trigger library scan in CWA settings
- Verify downloads and ingest are on same filesystem:
df -h - Both must be on Unraid parity array (not cache)
- Test manually:
ln /data/downloads/books/ebooks/downloads/test.epub /data/downloads/books/ebooks/ingest/test.epub - Check inode:
statboth files - should match
- Check Shelfmark configuration for hardlink support
- Verify paths are correct in Shelfmark settings
- Review Shelfmark logs for errors
- Ensure file permissions allow hardlink creation
- This indicates CWA deleted the original file, not the hardlink
- Verify Shelfmark created hardlink (not copy): Check with
stat - Confirm CWA ingest path points to ingest directory, not downloads
- Check qBittorrent for “missing files” errors
- Verify qBittorrent is “Fully Connectable” (check port forwarding)
- Confirm VPN not blocking incoming connections
- Check qBitrr not stopping seeds too early (14 day minimum)
- Review MAM freeleech events for bonus upload