Blame
|
1 | # Media Stack |
||||||
| 2 | ||||||||
| 3 | ← [[Home]] |
|||||||
| 4 | ||||||||
| 5 | Services running on PCT 102 (media-core, 192.168.2.191). Stack: `media-core` (compose 26). |
|||||||
| 6 | ||||||||
| 7 | --- |
|||||||
| 8 | ||||||||
| 9 | ## Service URLs |
|||||||
| 10 | ||||||||
| 11 | | Service | URL | Port | |
|||||||
| 12 | |---------|-----|------| |
|||||||
| 13 | | Jellyfin | `jellyfin.carr-family.org` | 8096 (standalone, static route) | |
|||||||
| 14 | | Sonarr | `sonarr.carr-family.org` | 8989 | |
|||||||
| 15 | | Radarr | `radarr.carr-family.org` | 7878 | |
|||||||
| 16 | | Prowlarr | `prowlarr.carr-family.org` | 9696 | |
|||||||
| 17 | | Jellyseerr | `seerr.carr-family.org` | 5055 | |
|||||||
| 18 | | Calibre-Web Automated | `book.carr-family.org` | 8083 | |
|||||||
| 19 | | Audiobookshelf | `audiobook.carr-family.org` | 13378 | |
|||||||
| 20 | | Audiobookrequest | `abr.carr-family.org` | 8000 | |
|||||||
| 21 | | Shelfmark | `shelfmark.carr-family.org` | 8084 | |
|||||||
| 22 | | OpenBooks | `openbooks.carr-family.org` | 8875 | |
|||||||
| 23 | | Komga | `komga.carr-family.org` | 25600 | |
|||||||
| 24 | | RomM | `romm.carr-family.org` | 8984 | |
|||||||
|
25 | | FlareSolverr | (internal only) | 8191 | |
||||||
|
26 | |||||||
| 27 | --- |
|||||||
| 28 | ||||||||
| 29 | ## Storage Layout |
|||||||
| 30 | ||||||||
| 31 | | Path | Contents | |
|||||||
| 32 | |------|----------| |
|||||||
| 33 | | `/mnt/tank/media/content/movies` | Movies | |
|||||||
| 34 | | `/mnt/tank/media/content/tv` | TV shows | |
|||||||
|
35 | | `/mnt/tank/media/content/books` | Calibre library (managed by CWA — never move files manually) | |
||||||
|
36 | | `/mnt/tank/media/content/books-seeds` | MAM torrent seeding copies (never touch) | |
||||||
| 37 | | `/mnt/tank/media/content/audiobooks` | Audiobookshelf + Shelfmark audiobook downloads | |
|||||||
| 38 | | `/mnt/tank/media/content/comics` | Komga library | |
|||||||
| 39 | | `/mnt/tank/media/content/roms` | RomM ROM library (organize by platform subfolder) | |
|||||||
| 40 | | `/mnt/tank/media/bookingest` | CWA ingest folder + Shelfmark ebook downloads | |
|||||||
| 41 | ||||||||
| 42 | --- |
|||||||
| 43 | ||||||||
| 44 | ## Calibre-Web Automated (CWA) |
|||||||
| 45 | ||||||||
| 46 | Volumes: `/mnt/tank/media/bookingest:/cwa-book-ingest`, `/mnt/tank/media/content/books:/calibre-library` |
|||||||
| 47 | ||||||||
| 48 | Config: `NETWORK_SHARE_MODE=true`, `CWA_WATCH_MODE=poll` |
|||||||
| 49 | ||||||||
|
50 | **Ingest mount gotcha** — verify with `docker inspect <container> | grep Mounts`. If CWA sees an empty ingest folder after a compose update, the container wasn't redeployed. Fix: |
||||||
| 51 | ```bash |
|||||||
| 52 | pct exec 108 -- docker stack deploy -c /mnt/tank/appdata/portainer/compose/26/docker-compose.yml media-core |
|||||||
| 53 | ``` |
|||||||
| 54 | ||||||||
| 55 | **root:root ownership breaks deletes** — CWA web server runs as `abc` (uid 1000) but the ingest processor runs as root, and `NETWORK_SHARE_MODE=true` skips the post-import chown. Files land as `root:root` and `abc` gets `[Errno 13] Permission denied` on delete. Fix existing library: |
|||||||
| 56 | ```bash |
|||||||
| 57 | pct exec 102 -- chown -R 1000:1000 /mnt/tank/media/content/books |
|||||||
| 58 | ``` |
|||||||
| 59 | The MAM hardlink script runs a background chown 5 minutes after each torrent completes to keep future imports clean. |
|||||||
|
60 | |||||||
|
61 | **Bulk-delete always shows success (CWA bug)** — `/ajax/deleteselectedbooks` always returns `{"success": true}`. Check container logs for `Deleting book X failed` if books reappear. `config_calibre_split` must be `0` in `app.db` — if `1` with wrong dir, all cover lookups silently fail. |
||||||
|
62 | |||||||
|
63 | **Stale thumbnails:** `DELETE FROM thumbnail;` in `app.db` to force regeneration. Cover generation runs nightly at 04:00. |
||||||
|
64 | |||||||
|
65 | **Don't bulk-delete duplicates while ingest is running** — drop files into bookingest, wait for CWA to finish all of them, then delete. Deleting mid-ingest causes silent EPERM failures. |
||||||
|
66 | |||||||
| 67 | ### Calibre Email Setup |
|||||||
| 68 | ||||||||
|
69 | - SMTP hostname: `192.168.2.83` (Proton Bridge on PCT 109) |
||||||
|
70 | - Port: `1025`, Encryption: None |
||||||
| 71 | ||||||||
| 72 | ||||||||
|
73 | --- |
||||||
| 74 | ||||||||
| 75 | ## calibredb Commands |
|||||||
|
76 | |||||||
| 77 | ```bash |
|||||||
| 78 | # Get CWA container name |
|||||||
| 79 | pct exec 102 -- docker ps --format '{{.Names}}' | grep calibre |
|||||||
| 80 | ||||||||
| 81 | # List all books as JSON |
|||||||
| 82 | pct exec 102 -- docker exec <cwa-container> calibredb list \ |
|||||||
|
83 | --fields=id,title,authors,series,series_index,tags,publisher,formats \ |
||||||
| 84 | --sort-by=title -s '' --library-path=/calibre-library --for-machine |
|||||||
|
85 | |||||||
|
86 | # Remove by ID (deletes metadata + files; does NOT touch books-seeds) |
||||||
|
87 | pct exec 102 -- docker exec <cwa-container> calibredb remove \ |
||||||
| 88 | --library-path=/calibre-library <id1>,<id2> |
|||||||
| 89 | ``` |
|||||||
| 90 | ||||||||
| 91 | --- |
|||||||
| 92 | ||||||||
|
93 | ## Calibre Deduplication |
||||||
| 94 | ||||||||
| 95 | Always keep the entry with the most metadata (series, series_index, tags, publisher). Never touch `/mnt/tank/media/content/books-seeds`. |
|||||||
| 96 | ||||||||
| 97 | **Known duplicate patterns (exact-title matching misses these):** |
|||||||
| 98 | ||||||||
| 99 | | Pattern | Example | |
|||||||
| 100 | |---------|---------| |
|||||||
| 101 | | Series prefix | `"Mistborn 02 - The Well of Ascension"` vs `"The Well of Ascension"` | |
|||||||
| 102 | | Subtitle append | `"After the Funeral: A Hercule Poirot Mystery"` vs `"After the Funeral"` | |
|||||||
| 103 | | Author punctuation | `"J. K. Rowling"` vs `"J.K. Rowling"` | |
|||||||
| 104 | | Series tag in title | `"Famine (The Four Horsemen Book 3)"` vs `"Famine"` | |
|||||||
| 105 | ||||||||
| 106 | Calibre has a large Agatha Christie / Hercule Poirot set where every book exists twice: once as `"Title"` (bare) and once as `"Title: A Hercule Poirot Mystery"` (with series + series_index). Always keep the latter. |
|||||||
| 107 | ||||||||
| 108 | --- |
|||||||
| 109 | ||||||||
| 110 | ## Calibre Library Cleanup |
|||||||
| 111 | ||||||||
| 112 | **Junk patterns to watch for:** |
|||||||
| 113 | ||||||||
| 114 | | Pattern | Search term | Notes | |
|||||||
| 115 | |---------|-------------|-------| |
|||||||
| 116 | | Comics — Spider-Man | `spider-man` | Title starts with `Amazing Spider-Man` | |
|||||||
| 117 | | Comics — Bone | `bone` + author `Jeff Smith` | Individual chapter volumes | |
|||||||
| 118 | | Lonely Planet (long) | `lonely planet` | Author is `Lonely Planet` or `Planet, Lonely & ...` | |
|||||||
| 119 | | Lonely Planet (short) | `LP ` | Title starts with `LP `, author `Unknown` | |
|||||||
| 120 | | D&D — Forgotten Realms | `forgotten realms` | Title starts with `Forgotten Realms -` | |
|||||||
| 121 | | Rough Guides | `rough guide` | Travel guides | |
|||||||
| 122 | | Torrent metadata | `anonamouse` | Titled `Torrent_downloaded_from_anonamouse.net` | |
|||||||
| 123 | | RPG character sheets | `character sheet` | e.g. Lone Wolf character sheets | |
|||||||
| 124 | | Bare index files | `index` | Title is exactly `index` | |
|||||||
| 125 | ||||||||
| 126 | **Bulk-remove by keyword** (filters precisely before deleting): |
|||||||
| 127 | ```bash |
|||||||
| 128 | IDS=$(pct exec 102 -- docker exec <cwa-container> calibredb list \ |
|||||||
| 129 | --fields=id,title --sort-by=title -s '<keyword>' \ |
|||||||
| 130 | --library-path=/calibre-library --for-machine | python3 -c " |
|||||||
| 131 | import json,sys |
|||||||
| 132 | data=json.load(sys.stdin) |
|||||||
| 133 | ids=[str(b['id']) for b in data if '<keyword>'.lower() in b['title'].lower()] |
|||||||
| 134 | print(','.join(ids)) |
|||||||
| 135 | ") |
|||||||
| 136 | pct exec 102 -- docker exec <cwa-container> calibredb remove --library-path=/calibre-library ${IDS} |
|||||||
| 137 | ``` |
|||||||
| 138 | ||||||||
| 139 | --- |
|||||||
| 140 | ||||||||
|
141 | ## Shelfmark |
||||||
| 142 | ||||||||
|
143 | Book search & request at `shelfmark.carr-family.org`. Image: `ghcr.io/calibrain/shelfmark:latest` (full, with browser automation). Config: `/mnt/tank/appdata/shelfmark/plugins/`. |
||||||
| 144 | ||||||||
| 145 | After any config edit: |
|||||||
| 146 | ```bash |
|||||||
| 147 | pct exec 108 -- docker service update --force media-core_shelfmark |
|||||||
| 148 | ``` |
|||||||
| 149 | ||||||||
| 150 | Auth: mounts `/mnt/tank/appdata/calibre-web-automated/app.db` read-only to `/auth/app.db` — reuses Calibre-Web logins. |
|||||||
|
151 | |||||||
|
152 | **Volumes:** |
||||||
| 153 | - `/mnt/tank/media/bookingest:/books` — ebook download destination |
|||||||
| 154 | - `/mnt/tank/media/content/audiobooks:/audiobooks` — audiobook download destination |
|||||||
|
155 | |||||||
|
156 | **qBittorrent connection** (`prowlarr_clients.json`): |
||||||
| 157 | - URL: `192.168.2.190:8080`, creds: `admin / 32Ab0321!!` |
|||||||
| 158 | - Ebook category: `books-shelfmark`, Audiobook category: `audiobooks` |
|||||||
| 159 | - `DOWNLOAD_PROGRESS_UPDATE_INTERVAL=10` (was 1 — caused login spam in qbt logs) |
|||||||
| 160 | - `HARDLINK_TORRENTS=false`, `HARDLINK_TORRENTS_AUDIOBOOK=false` |
|||||||
|
161 | |||||||
|
162 | **Remote path mappings** (`advanced.json`) — must use camelCase keys and `host: "qbittorrent"` (snake_case silently ignored): |
||||||
|
163 | ```json |
||||||
| 164 | [ |
|||||||
| 165 | {"host": "qbittorrent", "remotePath": "/data/bookingest", "localPath": "/books"}, |
|||||||
| 166 | {"host": "qbittorrent", "remotePath": "/data/content/audiobooks", "localPath": "/audiobooks"} |
|||||||
| 167 | ] |
|||||||
| 168 | ``` |
|||||||
| 169 | ||||||||
| 170 | --- |
|||||||
| 171 | ||||||||
| 172 | ## Komga |
|||||||
| 173 | ||||||||
| 174 | Comics & manga at `komga.carr-family.org`. Library: `/mnt/tank/media/content/comics`. |
|||||||
| 175 | ||||||||
| 176 | `CONVERT_TO_CBZ=false`, `REPAIR_EXTENSIONS=false` — do not re-enable (was renaming .cbr → .cbz, breaking seeding). |
|||||||
| 177 | ||||||||
| 178 | --- |
|||||||
| 179 | ||||||||
| 180 | ## RomM (compose 32) |
|||||||
| 181 | ||||||||
| 182 | ROM manager with in-browser EmulatorJS at `romm.carr-family.org`. Port 8984, host-mode on 192.168.2.191. Auth: RomM built-in (no Authentik — would break EmulatorJS API calls). |
|||||||
| 183 | ||||||||
|
184 | - **ROMs:** `/mnt/tank/media/content/roms:/romm/library` — organize by platform subfolder (e.g. `roms/gba/`, `roms/n64/`) |
||||||
| 185 | - **Config:** `/mnt/tank/appdata/romm/config/config.yml` — **must exist before first start** or settings won't persist |
|||||||
| 186 | - **DB:** `/mnt/tank/appdata/romm/db` |
|||||||
| 187 | - **Assets/covers:** `/mnt/tank/appdata/romm/assets`, `/mnt/tank/appdata/romm/resources` |
|||||||
| 188 | - **Metadata:** IGDB client ID `u5audru2zn9na5a0x3wq26yslmk1s5`, RetroAchievements key `UV9Xf6VdH9D8N1bpzuePqAAUkwGErZW5` |
|||||||
|
189 | |||||||
| 190 | ```bash |
|||||||
| 191 | pct exec 108 -- docker stack deploy -c /mnt/tank/appdata/portainer/compose/32/docker-compose.yml romm |
|||||||
| 192 | ``` |
|||||||
| 193 | ||||||||
| 194 | --- |
|||||||
| 195 | ||||||||
| 196 | ## Jellyfin (PCT 101 — standalone, not Swarm) |
|||||||
| 197 | ||||||||
|
198 | Media server at `jellyfin.carr-family.org`. Static route in Traefik → `192.168.2.191:8096`. |
||||||
| 199 | ||||||||
| 200 | --- |
|||||||
| 201 | ||||||||
| 202 | ## Audio Recorder (PCT 107 — stack: `audiorecorder`) |
|||||||
| 203 | ||||||||
| 204 | Browser-based system audio capture at `audiorecorder.carr-family.org`. Chrome/Edge only — enable "Share system audio" in the share dialog. Transcription via Whisper on PCT 109. |
|||||||
| 205 | ||||||||
| 206 | - **Image:** `audiorecorder:latest` — built locally on PCT 107 from `/tank/appdata/audiorecorder/app/` |
|||||||
| 207 | - **Recordings:** `/tank/appdata/audiorecorder/recordings/` — `.webm` files with `.txt` transcripts alongside |
|||||||
| 208 | - **Compose:** `/tank/appdata/audiorecorder/docker-compose.yml` (must be piped to PCT 108 for deploy) |
|||||||
| 209 | ||||||||
| 210 | **Rebuild & redeploy:** |
|||||||
| 211 | ```bash |
|||||||
| 212 | tar -cf - -C /tank/appdata/audiorecorder/app . | pct exec 107 -- bash -c 'mkdir -p /tmp/audiorecorder-build && tar -xf - -C /tmp/audiorecorder-build' |
|||||||
| 213 | pct exec 107 -- docker build -t audiorecorder:latest /tmp/audiorecorder-build/ |
|||||||
| 214 | pct exec 108 -- docker service update --force audiorecorder_audiorecorder |
|||||||
| 215 | ``` |
|||||||
|
216 | |||||||
| 217 | --- |
|||||||
| 218 | ||||||||
| 219 | ## FlareSolverr |
|||||||
| 220 | ||||||||
|
221 | Cloudflare bypass for Prowlarr indexers. Internal only at `192.168.2.191:8191`. |
||||||
