diff --git a/dankserver/.env.example b/dankserver/.env.example
index d5fe9bc..6a6f4d1 100644
--- a/dankserver/.env.example
+++ b/dankserver/.env.example
@@ -4,4 +4,21 @@
# RomM admin credentials (first boot)
ROMM_USERNAME=changeme
ROMM_PASSWORD=changeme
-ROMM_SECRET_KEY=changeme
+ROMM_AUTH_SECRET_KEY=changeme
+
+# RomM database (MariaDB)
+ROMM_DB_USER=romm
+ROMM_DB_PASSWD=changeme
+MARIADB_ROOT_PASSWORD=changeme
+
+# RomM Redis
+# (no password needed for local Redis)
+
+# Copyparty
+COPAY_PASS=changeme
+
+# Jellyfin
+JELLYFIN_PUBLISH_SERVER_URL=http://192.168.0.235:8096
+
+# *arr Stack
+ARR_TZ=America/New_York
diff --git a/dankserver/Caddyfile b/dankserver/Caddyfile
index f0d9969..6248a43 100644
--- a/dankserver/Caddyfile
+++ b/dankserver/Caddyfile
@@ -1,7 +1,6 @@
# Dankserver Caddyfile
-# Migrated from the live /etc/caddy/Caddyfile on dankserver, extended with the
-# new stack services. Native caddy (systemd) on this host; upstreams are host
-# ports because the services publish to the host.
+# Deploy: copy to /etc/caddy/Caddyfile on dankserver
+# See ../docs/Dankserver/ for service documentation.
{
admin off
@@ -54,3 +53,47 @@ files.dankserver.local {
}
reverse_proxy localhost:3923
}
+
+# --- Jellyfin ---
+jellyfin.dankserver.local {
+ reverse_proxy 127.0.0.1:8096
+
+ transport http {
+ versions h2c 1.1
+ }
+}
+
+# --- *arr Stack ---
+radarr.dankserver.local {
+ reverse_proxy localhost:7878
+}
+
+sonarr.dankserver.local {
+ reverse_proxy localhost:8989
+}
+
+lidarr.dankserver.local {
+ reverse_proxy localhost:8686
+}
+
+readarr.dankserver.local {
+ reverse_proxy localhost:8787
+}
+
+prowlarr.dankserver.local {
+ reverse_proxy localhost:9696
+}
+
+qbittorrent.dankserver.local {
+ reverse_proxy localhost:8080
+}
+
+# --- Optional: Jackett ---
+# jackett.dankserver.local {
+# reverse_proxy localhost:9117
+# }
+
+# --- Optional: Whisparr ---
+# whisparr.dankserver.local {
+# reverse_proxy localhost:6969
+# }
diff --git a/dankserver/compose.yaml b/dankserver/compose.yaml
index 5cced50..d5f2c84 100644
--- a/dankserver/compose.yaml
+++ b/dankserver/compose.yaml
@@ -2,7 +2,43 @@
# Deploy: cd /home/dank/serverAppConfigs && podman-compose -f dankserver/compose.yaml up -d
# See ../docs/Dankserver/ for service documentation.
+networks:
+ appnet:
+ driver: bridge
+
services:
+ # ═══════════════════════════════════════════════
+ # RomM — retro game manager (requires MariaDB + Redis)
+ # ═══════════════════════════════════════════════
+ mariadb:
+ image: docker.io/mariadb:11
+ container_name: romm-mariadb
+ restart: unless-stopped
+ environment:
+ MARIADB_ROOT_PASSWORD: "${MARIADB_ROOT_PASSWORD}"
+ MARIADB_DATABASE: romm
+ MARIADB_USER: "${ROMM_DB_USER}"
+ MARIADB_PASSWORD: "${ROMM_DB_PASSWD}"
+ volumes:
+ - /opt/romm/mariadb:/var/lib/mysql
+ healthcheck:
+ test: ["CMD", "mariadb-admin", "ping", "-h", "localhost"]
+ interval: 10s
+ timeout: 5s
+ retries: 3
+
+ redis:
+ image: docker.io/redis:7-alpine
+ container_name: romm-redis
+ restart: unless-stopped
+ volumes:
+ - /opt/romm/redis:/data
+ healthcheck:
+ test: ["CMD", "redis-cli", "ping"]
+ interval: 10s
+ timeout: 5s
+ retries: 3
+
romm:
image: ghcr.io/rommapp/romm:latest
container_name: romm
@@ -10,22 +46,40 @@ services:
environment:
ROMM_USERNAME: "${ROMM_USERNAME}"
ROMM_PASSWORD: "${ROMM_PASSWORD}"
- ROMM_SECRET_KEY: "${ROMM_SECRET_KEY}"
+ ROMM_AUTH_SECRET_KEY: "${ROMM_AUTH_SECRET_KEY}"
+ DB_HOST: mariadb
+ DB_NAME: romm
+ DB_USER: "${ROMM_DB_USER}"
+ DB_PASSWD: "${ROMM_DB_PASSWD}"
+ REDIS_HOST: redis
+ HASHEOUS_API_ENABLED: "true"
ports:
- "8085:8080"
volumes:
- /opt/romm/library:/romm/library
- /opt/romm/resources:/romm/resources
+ - /opt/romm/config:/romm/config
+ depends_on:
+ mariadb:
+ condition: service_healthy
+ redis:
+ condition: service_healthy
healthcheck:
- test: ["CMD", "curl", "-f", "http://localhost:8080/"]
+ test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
+ # ═══════════════════════════════════════════════
+ # Copyparty — file sharing and streaming
+ # ═══════════════════════════════════════════════
copyparty:
image: ghcr.io/9001/copyparty:latest
container_name: copyparty
restart: unless-stopped
+ environment:
+ COPAY_PASS: "${COPAY_PASS}"
+ COPAY_MODE: rw
ports:
- "3923:3923"
volumes:
@@ -36,14 +90,49 @@ services:
timeout: 10s
retries: 3
+ # ═══════════════════════════════════════════════
+ # Jellyfin — media server (movies, TV, music)
+ # ═══════════════════════════════════════════════
+ jellyfin:
+ image: jellyfin/jellyfin:latest
+ container_name: jellyfin
+ restart: unless-stopped
+ user: "1000:1000"
+ ports:
+ - "8096:8096/tcp"
+ - "8920:8920/tcp"
+ - "7359:7359/udp"
+ - "1900:1900/udp"
+ environment:
+ - JELLYFIN_PublishedServerUrl=http://192.168.0.235:8096
+ - DOTNET_SYSTEM_IO_DISABLEFILELOCKING=true
+ volumes:
+ - /opt/jellyfin/config:/config
+ - /opt/jellyfin/cache:/cache
+ - /mnt/media/movies:/data/movies:ro
+ - /mnt/media/shows:/data/shows:ro
+ devices:
+ - /dev/dri:/dev/dri
+ group_add:
+ - "992"
+ healthcheck:
+ test: ["CMD", "curl", "-f", "http://localhost:8096/health"]
+ interval: 30s
+ timeout: 10s
+ retries: 3
+
+ # ═══════════════════════════════════════════════
+ # Calibre-Web — e-book web frontend
+ # ═══════════════════════════════════════════════
calibre-web:
- image: lscr.io/linuxserver/calibre-web:latest
+ image: lscr.io/linuxserver/calibre-web:0.6.27-ls397
container_name: calibre-web
restart: unless-stopped
environment:
PUID: "1000"
PGID: "1000"
TZ: Etc/UTC
+ OAUTHLIB_RELAX_TOKEN_SCOPE: "1"
ports:
- "8083:8083"
volumes:
@@ -55,6 +144,113 @@ services:
timeout: 10s
retries: 3
+ # ═══════════════════════════════════════════════
+ # *arr Stack — automated media download
+ # ═══════════════════════════════════════════════
+ prowlarr:
+ image: lscr.io/linuxserver/prowlarr:latest
+ container_name: prowlarr
+ restart: unless-stopped
+ environment:
+ - PUID=1000
+ - PGID=1000
+ - TZ=America/New_York
+ - UMASK_SET=022
+ volumes:
+ - /opt/prowlarr/config:/config
+ - /mnt/media/downloads:/downloads:ro
+ ports:
+ - "9696:9696"
+ networks: [appnet]
+
+ qbittorrent:
+ image: lscr.io/linuxserver/qbittorrent:latest
+ container_name: qbittorrent
+ restart: unless-stopped
+ environment:
+ - PUID=1000
+ - PGID=1000
+ - TZ=America/New_York
+ - UMASK_SET=022
+ volumes:
+ - /opt/qbittorrent/config:/config
+ - /mnt/media/downloads:/downloads
+ - /mnt/media/downloads/incomplete:/downloads/incomplete
+ ports:
+ - "8080:8080"
+ networks: [appnet]
+
+ radarr:
+ image: lscr.io/linuxserver/radarr:latest
+ container_name: radarr
+ restart: unless-stopped
+ environment:
+ - PUID=1000
+ - PGID=1000
+ - TZ=America/New_York
+ - UMASK_SET=022
+ volumes:
+ - /opt/radarr/config:/config
+ - /mnt/media/movies:/movies
+ - /mnt/media/downloads:/downloads
+ ports:
+ - "7878:7878"
+ networks: [appnet]
+
+ sonarr:
+ image: lscr.io/linuxserver/sonarr:latest
+ container_name: sonarr
+ restart: unless-stopped
+ environment:
+ - PUID=1000
+ - PGID=1000
+ - TZ=America/New_York
+ - UMASK_SET=022
+ volumes:
+ - /opt/sonarr/config:/config
+ - /mnt/media/tv:/tv
+ - /mnt/media/downloads:/downloads
+ ports:
+ - "8989:8989"
+ networks: [appnet]
+
+ lidarr:
+ image: lscr.io/linuxserver/lidarr:latest
+ container_name: lidarr
+ restart: unless-stopped
+ environment:
+ - PUID=1000
+ - PGID=1000
+ - TZ=America/New_York
+ - UMASK_SET=022
+ volumes:
+ - /opt/lidarr/config:/config
+ - /mnt/media/music:/music
+ - /mnt/media/downloads:/downloads
+ ports:
+ - "8686:8686"
+ networks: [appnet]
+
+ readarr:
+ image: lscr.io/linuxserver/readarr:latest
+ container_name: readarr
+ restart: unless-stopped
+ environment:
+ - PUID=1000
+ - PGID=1000
+ - TZ=America/New_York
+ - UMASK_SET=022
+ volumes:
+ - /opt/readarr/config:/config
+ - /mnt/media/books:/books
+ - /mnt/media/downloads:/downloads
+ ports:
+ - "8787:8787"
+ networks: [appnet]
+
+ # ═══════════════════════════════════════════════
+ # Node Exporter — host metrics
+ # ═══════════════════════════════════════════════
node-exporter:
image: prom/node-exporter:latest
container_name: node-exporter
diff --git a/docs/Dankserver/ARR-Stack.md b/docs/Dankserver/ARR-Stack.md
new file mode 100644
index 0000000..8b2620d
--- /dev/null
+++ b/docs/Dankserver/ARR-Stack.md
@@ -0,0 +1,369 @@
+# *arr Stack
+
+The *arr suite is a collection of automated media management tools for Usenet and BitTorrent. For Dankserver — which runs Copyparty, Calibre-Web, RomM, and Jellyfin — the *arr stack provides automatic downloading and library management that Copyparty alone does not cover: quality profile enforcement, series/movie discovery, metadata enrichment, and automated file organization.
+
+The core chain is: **Prowlarr** (indexer API aggregator) → **Sonarr/Radarr/Lidarr/Readarr** (media-specific managers) → **qBittorrent** (download client) → **Jellyfin** (media server scan → auto-refresh).
+
+## Service Overview
+
+| Service | Role | Image | Port | Min RAM |
+|---------|------|-------|------|---------|
+| Prowlarr | Indexer management | `lscr.io/linuxserver/prowlarr:latest` | 9696 | 256 MB |
+| Radarr | Movie management | `lscr.io/linuxserver/radarr:latest` | 7878 | 512 MB |
+| Sonarr | TV show management | `lscr.io/linuxserver/sonarr:latest` | 8989 | 512 MB |
+| Lidarr | Music management | `lscr.io/linuxserver/lidarr:latest` | 8686 | 256 MB |
+| Readarr | Ebook/audiobook management | `lscr.io/linuxserver/readarr:latest` | 8787 | 256 MB |
+| qBittorrent | Torrent download client | `lscr.io/linuxserver/qbittorrent:latest` | 8080 | 512 MB |
+| Jackett | Indexer proxy (optional) | `lscr.io/linuxserver/jackett:latest` | 9117 | 256 MB |
+| Whisparr | NSFW movie management (optional) | `lscr.io/linuxserver/whisparr:latest` | 6969 | 256 MB |
+
+**Minimum total RAM:** ~3 GB for full stack (headroom recommended).
+
+## Architecture
+
+```mermaid
+graph LR
+ I[Indexers
Torrents, Usenet] --> P[Prowlarr
Indexer Manager]
+ P --> S[Sonarr
TV]
+ P --> R[Radarr
Movies]
+ P --> L[Lidarr
Music]
+ P --> Rd[Readarr
Books]
+ S --> Q[qBittorrent]
+ R --> Q
+ L --> Q
+ Rd --> Q
+ Q --> M1[/tv/]
+ Q --> M2[/movies/]
+ Q --> M3[/music/]
+ Q --> M4[/books/]
+ M1 --> J[Jellyfin]
+ M2 --> J
+ M3 --> J
+ M4 --> CW[Calibre-Web]
+```
+
+## Docker Compose
+
+```yaml
+networks:
+ appnet:
+ driver: bridge
+
+services:
+ prowlarr:
+ image: lscr.io/linuxserver/prowlarr:latest
+ container_name: prowlarr
+ restart: unless-stopped
+ environment:
+ - PUID=1000
+ - PGID=1000
+ - TZ=America/New_York
+ - UMASK_SET=022
+ volumes:
+ - /opt/prowlarr/config:/config
+ - /mnt/media/downloads:/downloads:ro
+ ports:
+ - "9696:9696"
+ networks: [appnet]
+
+ radarr:
+ image: lscr.io/linuxserver/radarr:latest
+ container_name: radarr
+ restart: unless-stopped
+ environment:
+ - PUID=1000
+ - PGID=1000
+ - TZ=America/New_York
+ - UMASK_SET=022
+ volumes:
+ - /opt/radarr/config:/config
+ - /mnt/media/movies:/movies
+ - /mnt/media/downloads:/downloads
+ ports:
+ - "7878:7878"
+ networks: [appnet]
+
+ sonarr:
+ image: lscr.io/linuxserver/sonarr:latest
+ container_name: sonarr
+ restart: unless-stopped
+ environment:
+ - PUID=1000
+ - PGID=1000
+ - TZ=America/New_York
+ - UMASK_SET=022
+ volumes:
+ - /opt/sonarr/config:/config
+ - /mnt/media/tv:/tv
+ - /mnt/media/downloads:/downloads
+ ports:
+ - "8989:8989"
+ networks: [appnet]
+
+ lidarr:
+ image: lscr.io/linuxserver/lidarr:latest
+ container_name: lidarr
+ restart: unless-stopped
+ environment:
+ - PUID=1000
+ - PGID=1000
+ - TZ=America/New_York
+ - UMASK_SET=022
+ volumes:
+ - /opt/lidarr/config:/config
+ - /mnt/media/music:/music
+ - /mnt/media/downloads:/downloads
+ ports:
+ - "8686:8686"
+ networks: [appnet]
+
+ readarr:
+ image: lscr.io/linuxserver/readarr:latest
+ container_name: readarr
+ restart: unless-stopped
+ environment:
+ - PUID=1000
+ - PGID=1000
+ - TZ=America/New_York
+ - UMASK_SET=022
+ volumes:
+ - /opt/readarr/config:/config
+ - /mnt/media/books:/books
+ - /mnt/media/downloads:/downloads
+ ports:
+ - "8787:8787"
+ networks: [appnet]
+
+ qbittorrent:
+ image: lscr.io/linuxserver/qbittorrent:latest
+ container_name: qbittorrent
+ restart: unless-stopped
+ environment:
+ - PUID=1000
+ - PGID=1000
+ - TZ=America/New_York
+ - UMASK_SET=022
+ volumes:
+ - /opt/qbittorrent/config:/config
+ - /mnt/media/downloads:/downloads
+ - /mnt/media/downloads/incomplete:/downloads/incomplete
+ ports:
+ - "8080:8080"
+ networks: [appnet]
+
+ # Optional: Jackett for niche indexers
+ jackett:
+ image: lscr.io/linuxserver/jackett:latest
+ container_name: jackett
+ restart: unless-stopped
+ environment:
+ - PUID=1000
+ - PGID=1000
+ - TZ=America/New_York
+ - UMASK_SET=022
+ volumes:
+ - /opt/jackett/config:/config
+ - /opt/jackett/torrences:/torrences
+ ports:
+ - "9117:9117"
+ networks: [appnet]
+
+ # Optional: Whisparr for adult content
+ whisparr:
+ image: lscr.io/linuxserver/whisparr:latest
+ container_name: whisparr
+ restart: unless-stopped
+ environment:
+ - PUID=1000
+ - PGID=1000
+ - TZ=America/New_York
+ - UMASK_SET=022
+ volumes:
+ - /opt/whisparr/config:/config
+ - /mnt/media/whisparr-movies:/whisparr-movies
+ - /mnt/media/downloads:/downloads
+ ports:
+ - "6969:6969"
+ networks: [appnet]
+```
+
+## Caddy Configuration
+
+```caddy
+radarr.dankserver.local {
+ reverse_proxy localhost:7878
+}
+
+sonarr.dankserver.local {
+ reverse_proxy localhost:8989
+}
+
+lidarr.dankserver.local {
+ reverse_proxy localhost:8686
+}
+
+readarr.dankserver.local {
+ reverse_proxy localhost:8787
+}
+
+prowlarr.dankserver.local {
+ reverse_proxy localhost:9696
+}
+
+qbittorrent.dankserver.local {
+ reverse_proxy localhost:8080
+}
+
+# Optional: Jackett
+jackett.dankserver.local {
+ reverse_proxy localhost:9117
+}
+
+# Optional: Whisparr
+whisparr.dankserver.local {
+ reverse_proxy localhost:6969
+}
+```
+
+## Configuration Steps
+
+### Phase 1: Download Client
+
+1. Deploy qBittorrent first (or Transmission).
+2. In the qBittorrent Web UI, set **No auth mode** for LAN (Settings → Web UI).
+3. Create download categories: `movies`, `tv`, `music`, `books`.
+4. Change the default Web UI password.
+
+### Phase 2: Indexer Management
+
+1. Deploy Prowlarr.
+2. Add indexers (3–5 recommended for testing). Use the **Test** button on each.
+3. Add qBittorrent as a download client in Prowlarr.
+4. Verify search from the Prowlarr web UI.
+
+### Phase 3: Arr Apps (one at a time)
+
+1. Deploy Sonarr first (TV shows are the most common use case).
+2. Add Prowlarr as the indexer source (Settings → Indexers → Add Prowlarr).
+3. Add qBittorrent as the download client with categories.
+4. Add a test series. Wait for the episode to download. Verify in `/tv/`.
+5. Repeat for Radarr, Lidarr, Readarr.
+
+### Phase 4: Media Server Integration
+
+1. Configure Jellyfin libraries pointing to `/movies`, `/tv`, `/music`, `/books`.
+2. Set **On Download** notifications in each arr app → Jellyfin webhook URL.
+3. Test end-to-end: Add a movie in Radarr → download → verify in Jellyfin.
+
+## Quality Profiles
+
+Each arr app uses a hierarchical quality system:
+
+1. **Quality Profile** — Defines tiers (e.g., 2160p → 1080p BluRay → 720p).
+2. **Cutoff** — The quality target. Downloads at or above cutoff are accepted.
+3. **Custom Formats** — Additional filters (e.g., "Has HDR", "Is Director's Cut").
+4. **Search on Grab** — When content becomes available, all indexers are queried.
+
+### Radarr (Movies)
+
+```
+Profile: Movies 4K
+ Tiers:
+ 1. 2160p BluRay REMUX
+ 2. 2160p BluRay
+ 3. 2160p WEB-DL
+ 4. 1080p BluRay
+ 5. 1080p WEB-DL
+ 6. 720p
+ Cutoff: 1080p BluRay
+```
+
+### Sonarr (TV)
+
+```
+Profile: TV - Standard
+ Tiers: 2160p → 1080p → 720p
+ Min Size: 500 MB
+ Max Size: 50 GB
+ Cutoff: 1080p
+ Language Profile: English audio preferred
+```
+
+### Lidarr (Music)
+
+```
+Profile: Music - Standard
+ Tiers: Lossless (FLAC) → 320 kbps → 192 kbps
+ Cutoff: 320 kbps (or Lossless)
+```
+
+## Metadata Sources
+
+| App | Primary Source | Secondary |
+|-----|---------------|-----------|
+| Radarr | TMDB | TheMovieDB API |
+| Sonarr | TMDB | TVDB |
+| Lidarr | MusicBrainz | — |
+| Readarr | Goodreads, StoryGraph | — |
+| Whisparr | XH | TMDB |
+
+## *arr Stack vs Copyparty
+
+| Feature | Copyparty | *arr Stack |
+|---------|-----------|------------|
+| Auto-download from torrents | Basic (RSS + scripts) | Native, integrated |
+| Quality profiles | No | Multi-tier, custom formats |
+| Series/movie tracking | No | Watch lists, air dates |
+| Manual library management | Yes | Yes, metadata-driven |
+| Multi-user watchlists | No | Yes |
+| Complexity | Low | Medium |
+| Resource usage | Minimal | Moderate |
+
+Use Copyparty for curated file collections. Use *arr for automatic downloads. Use both together for maximum functionality — point *arr download directories at Copyparty's watched folders.
+
+## Disk Layout
+
+```
+/mnt/media/
+├── movies/ → Radarr root
+├── tv/ → Sonarr root
+├── music/ → Lidarr root
+├── books/ → Readarr root
+├── whisparr-movies/ → Whisparr root
+├── downloads/ → qBittorrent download dir
+│ ├── movies/
+│ ├── tv/
+│ ├── music/
+│ └── books/
+└── incomplete/ → qBittorrent incomplete downloads
+```
+
+## Gotchas
+
+- **Timezone mismatch:** All containers should use the same TZ. Sonarr's scheduler is timezone-sensitive.
+- **File permissions:** Set `umask=022` in qBittorrent. Ensure arr apps can read/move downloaded files.
+- **Network conflicts:** *arr apps use many ports. Check for conflicts with existing Dankserver services.
+- **Prowlarr API key rotation:** If rotated, update it in every arr app.
+- **qBittorrent Web UI password:** Change default on first run.
+- **Readarr stability:** Readarr is still in development. Do not rely on it for mission-critical collections.
+- **Whisparr separate Jellyfin library:** Keep adult content separate from regular media.
+- **RSS feed in qBittorrent:** Can auto-add torrents matching a pattern. Useful for podcast-like series.
+
+## Best Practices
+
+1. Use tags to route content to specific download clients or categories.
+2. Enable the Blocklist in arr apps to prevent re-downloading failed torrents.
+3. Set download limits in qBittorrent to avoid bandwidth saturation.
+4. Back up arr configs regularly (they are small SQLite DBs + JSON).
+5. Monitor Prowlarr indexer health — broken indexers hide new content.
+6. Start with default quality profiles. Add custom formats gradually.
+7. Back up arr configs: `./radarr/config`, `./sonarr/config`, etc.
+
+## See Also
+
+- [[Dankserver Stack]]
+- [[Jellyfin]] — media server that consumes *arr downloads
+- [[Copyparty]] — alternative file viewer
+- [[Calibre-Web]] — manual ebook management (Readarr auto-downloads into the same library)
+- [[Sonarr Wiki]] — https://wiki.servarr.com/sonarr
+- [[Radarr Wiki]] — https://wiki.servarr.com/radarr
diff --git a/docs/Dankserver/Calibre-Web.md b/docs/Dankserver/Calibre-Web.md
index 8d867e6..9034bdd 100644
--- a/docs/Dankserver/Calibre-Web.md
+++ b/docs/Dankserver/Calibre-Web.md
@@ -1,44 +1,93 @@
# Calibre-Web
-Calibre-Web is a web app that browses, reads, and downloads eBooks from an existing Calibre library database. It is the web frontend for the Calibre e-book manager.
+Calibre-Web is a web application that browses, reads, and downloads eBooks from an existing Calibre library database. It serves as the web frontend for the Calibre e-book manager.
**Host:** Dankserver — **Hostname:** `books.dankserver.local` — **Docs:** https://github.com/linuxserver/docker-calibre-web
-## Facts (from research, primary source)
+## Facts
| Item | Value |
|------|-------|
-| Image | `lscr.io/linuxserver/calibre-web:latest` (already running on Dankserver) |
+| Latest version | 0.6.27-ls397 |
+| Image | `lscr.io/linuxserver/calibre-web:0.6.27-ls397` |
+| Base OS | Ubuntu Noble (24.04 LTS, since 2024-12-05) |
| Port | 8083 (host) → 8083 (container) |
-| Env | `PUID=1000`, `PGID=1000`, `TZ=Etc/UTC` |
-| Optional | `DOCKER_MODS=linuxserver/mods:universal-calibre` (ebook conversion, x86-64 only) |
-| Volumes | `/config` (internal db + config), `/books` (existing Calibre library) |
-| Extra tools | `unrar` (path `/usr/bin/unrar`), `kepubify` (path `/usr/bin/kepubify`) |
+| Default login | `admin` / `admin123` |
+| Docker mod | `linuxserver/mods:universal-calibre` (ebook conversion, x86-64 only) |
+| Volumes | `/config` (internal database + config), `/books` (existing Calibre library) |
+| Extra tools | `unrar` at `/usr/bin/unrar`, `kepubify` at `/usr/bin/kepubify` (pre-configured since 2025-01-07) |
+
+## CVEs Fixed in Latest Version
+
+| CVE | Issue |
+|-----|-------|
+| CVE-2025-7404 | External binary names (e.g. `unrar`) restricted to prevent RCE |
+| — | XSS in book comments imported via Calibre, now sanitized |
+
+Additional packages added: `libxfixes3` and `libasound2t64` to support epub-to-pdf conversion. The `--no-sandbox` flag is applied to qtwebengine to prevent crashes in Docker.
## Configuration
-- Set the library path in the Calibre-Web admin page: Basic Configuration → Calibre Database → Location of Calibre database = `/books`.
-- Set the external binary paths in Basic Configuration → External Binaries after first login.
-- The default login is `admin` / `admin123` on first boot; change it immediately.
+### Environment Variables
+
+| Variable | Value | Description |
+|----------|-------|-------------|
+| `PUID` | `1000` | User ID |
+| `PGID` | `1000` | Group ID |
+| `TZ` | `Etc/UTC` | Timezone |
+| `DOCKER_MODS` | `linuxserver/mods:universal-calibre` | Optional: enables ebook conversion tools |
+| `OAUTHLIB_RELAX_TOKEN_SCOPE` | `1` | Required for Google OAuth |
+
+### Setup Steps
+
+1. Point the container at the Calibre library directory (set the `/books` volume).
+2. Log in with `admin` / `admin123` and change the password immediately.
+3. In Basic Configuration → Calibre Database → set Location of Calibre database to `/books`.
+4. In Basic Configuration → External Binaries → set the directory to `/usr/bin/` for Calibre-Web >= 0.6.22.
+5. **kepubify is pre-configured** since version 0.6.27. No need to set the kepubify path manually.
+6. Set up Google OAuth if desired (requires `OAUTHLIB_RELAX_TOKEN_SCOPE=1`).
## Volumes
| Container path | Host path (convention) |
|----------------|------------------------|
| `/config` | `/opt/calibre-web/config` |
-| `/books` | the existing Calibre library directory (user-entered later) |
+| `/books` | The existing Calibre library directory (user-entered during setup) |
-## Healthcheck
+## Docker Compose
-The web UI responds on 8083. A `wget -q --spider` check on `/` suffices.
+```yaml
+services:
+ calibre-web:
+ image: lscr.io/linuxserver/calibre-web:0.6.27-ls397
+ container_name: calibre-web
+ restart: unless-stopped
+ environment:
+ PUID: "1000"
+ PGID: "1000"
+ TZ: Etc/UTC
+ OAUTHLIB_RELAX_TOKEN_SCOPE: "1"
+ ports:
+ - "8083:8083"
+ volumes:
+ - /opt/calibre-web/config:/config
+ - /opt/calibre-web/books:/books
+ healthcheck:
+ test: ["CMD", "curl", "-f", "http://localhost:8083/"]
+ interval: 30s
+ timeout: 10s
+ retries: 3
+```
## Gotchas
-- If the DB path is wrong, login appears to succeed but the library is empty.
-- Ebook conversion needs the `universal-calibre` mod AND the external binary path set to `/usr/bin/ebook-convert` (or `/usr/bin/` on newer versions).
-- Google OAuth needs `OAUTHLIB_RELAX_TOKEN_SCOPE=1`.
+- **Wrong database path:** Login appears to succeed but the library is empty. Verify the Calibre database path in the admin page.
+- **Ebook conversion:** Requires the `universal-calibre` Docker mod AND the external binary path set to `/usr/bin/` (directory, not full path to `ebook-convert`).
+- **Google OAuth:** Set `OAUTHLIB_RELAX_TOKEN_SCOPE=1` or OAuth login fails.
+- **calibre 9.9.0 content server security fix:** A bug since v6.16.0 allows any user with server access to read non-Calibre files on the host. Upgrade to 9.9.0+ if serving content to untrusted users.
## See Also
- [[Dankserver Stack]]
- [[Calibre]] — the underlying library engine (host tool, not a container)
+- [[ARR-Stack]] — Readarr for automatic ebook downloads into the Calibre library
diff --git a/docs/Dankserver/Calibre.md b/docs/Dankserver/Calibre.md
index d184c48..90fcc0b 100644
--- a/docs/Dankserver/Calibre.md
+++ b/docs/Dankserver/Calibre.md
@@ -1,27 +1,69 @@
# Calibre
-Calibre is the e-book library manager behind Calibre-Web. It manages metadata, formats, and conversions for the library that Calibre-Web serves.
+Calibre is the e-book library manager that backs Calibre-Web. It manages metadata, formats, and conversions for the library that Calibre-Web serves.
**Host:** Dankserver (library directory on host) — **Docs:** https://calibre-ebook.com
-## Role in this stack
+## Role in this Stack
Calibre itself is a desktop/GUI application, not a container service. This stack runs **Calibre-Web** in a container and points it at an existing Calibre library directory on the Dankserver host.
-## Library Directory
+Calibre is used to:
+- Import books into the library (drag-and-drop or CLI `calibredb`)
+- Edit metadata (author, title, series, tags, cover art)
+- Convert between formats (EPUB → MOBI, MOBI → EPUB, etc.)
+- Manage collections and reading lists
-The user will enter the existing Calibre library path during Calibre-Web setup. The convention in this repo:
+The actual library files (`.epub`, `.mobi`, `.pdf`) and the Calibre database (`.db` file) live on the host filesystem. Calibre-Web reads and writes them through the mounted volume.
+
+## Current Version
+
+Calibre 9.9.0 is the latest stable release. Major features include:
+- Bookshelf view
+- Opt-in AI features
+- Content server security fix (CVE-2025-7404 equivalent): a bug since v6.16.0 allows any user with server access to read non-Calibre files on the host. Upgrade to 9.9.0+ if serving content to untrusted users.
+
+## Library Directory
| Purpose | Host path (convention) |
|---------|------------------------|
-| Calibre library | `/opt/calibre-web/books` (bind-mounted to `/books` in the container) |
+| Calibre library | `/opt/calibre-web/books` (bind-mounted to `/books` in the Calibre-Web container) |
-## What the container needs
+## What the Container Needs
-- Read/write access to the library directory (Calibre-Web edits metadata through the app).
+- Read/write access to the library directory. Calibre-Web edits metadata through the database file.
- Ownership matching `PUID=1000` / `PGID=1000` (the `dank` user on Dankserver).
+## Optional: Calibre Content Server
+
+Calibre includes a built-in HTTP content server (`calibre-server`) that can serve the library via OPDS feeds and a mobile-friendly web interface. This enables:
+- OPDS feeds for mobile reading apps (FBReader, Moon+ Reader)
+- Mobile-optimized UI via the `--mobile` flag
+- Direct Kindle push
+
+To run as a container:
+
+```yaml
+services:
+ calibre-server:
+ image: lscr.io/linuxserver/calibre:latest
+ container_name: calibre-server
+ restart: unless-stopped
+ environment:
+ PUID: "1000"
+ PGID: "1000"
+ TZ: Etc/UTC
+ command: calibre-server --daemonize --port 8080 --enable-auth --url-prefix /calibre /books
+ volumes:
+ - /opt/calibre-web/books:/books
+ ports:
+ - "8084:8080"
+```
+
+Note: The content server runs headless in `--daemonize` mode. It requires the full Calibre package, not a minimal server.
+
## See Also
- [[Calibre-Web]] — the containerized frontend
- [[Dankserver Stack]]
+- [[ARR-Stack]] — Readarr for automatic ebook downloads
diff --git a/docs/Dankserver/Copyparty.md b/docs/Dankserver/Copyparty.md
index 69bf243..207598e 100644
--- a/docs/Dankserver/Copyparty.md
+++ b/docs/Dankserver/Copyparty.md
@@ -1,25 +1,49 @@
# Copyparty
-Copyparty is a lightweight file server that shares and streams files over HTTP. It handles large payloads, media streaming, uploads, and directory browsing from a single Python binary.
+Copyparty is a lightweight file server and media streamer that runs as a single Python binary. It handles large payloads, media streaming, resumable uploads, directory browsing, and WebDAV from one process.
**Host:** Dankserver — **Hostname:** `files.dankserver.local` — **Docs:** https://github.com/9001/copyparty
-## Facts (from research)
+## Facts
| Item | Value |
|------|-------|
+| Latest version | v1.20.19 (released 2026-07-27) |
| Image | `ghcr.io/9001/copyparty:latest` (official Dockerfile in repo) |
| Port | 3923 (host) → 3923 (container) |
| Volume | bind mount the shared root directory |
-| Auth | per-directory `.cpy` config files, HTTP basic auth |
-| Streaming | HTTP range requests, HLS, WebDAV |
+| Auth | Per-directory `.cpy` config files, HTTP basic auth, `dirkeys`, `filekeys` |
+| Streaming | HTTP range requests, HLS, WebDAV, WOPI (Office editing) |
+| Docker editions | `dj` (document + media), `iv` (image + video), `v` (video only) |
## Configuration
-- Run with `-e COPAY_PASS=...` or configure per-folder passwords.
-- Set `-e COPAY_MODE=rw` for read-write shares where uploads are allowed.
-- The container publishes port 3923; expose it to Caddy only, not the WAN.
-- `--resumable` enables resumable uploads for large files (models, ISOs).
+### Environment Variables
+
+| Variable | Description |
+|----------|-------------|
+| `COPAY_PASS` | Master password for the web UI |
+| `COPAY_MODE` | Set to `rw` for read-write shares |
+
+### Configuration File Options
+
+Copyparty reads its configuration from `initcfg` (or `config` in the `/data` directory). Key options:
+
+- `use-bwrap: n` — Disable bwrap sandboxing for ffmpeg (default in Docker). Set to `y` to enable.
+- `shadowing //NULL` — Full unmap of a volume from the filesystem.
+- Config env-var expansion: Use `${VAR}` syntax in `[global]` section for environment variable substitution.
+
+### Docker Editions
+
+Different editions bundle different libraries:
+
+| Edition | Bundled Libraries | Use Case |
+|---------|-------------------|----------|
+| `dj` | Document + image + video libraries | Full-featured server |
+| `iv` | Image + video libraries | Image and video focused |
+| `v` | Video only | Transcoding only, minimal footprint |
+
+The `iv` edition is no longer available for arm32/armv6. Use `dj` for maximum compatibility.
## Volumes
@@ -27,17 +51,56 @@ Copyparty is a lightweight file server that shares and streams files over HTTP.
|----------------|------------------------|
| `/data` | `/opt/copyparty/data` |
+## Docker Compose
+
+```yaml
+services:
+ copyparty:
+ image: ghcr.io/9001/copyparty:latest
+ container_name: copyparty
+ restart: unless-stopped
+ environment:
+ COPAY_PASS: "${COPAY_PASS}"
+ COPAY_MODE: rw
+ ports:
+ - "3923:3923"
+ volumes:
+ - /opt/copyparty/data:/data
+ healthcheck:
+ test: ["CMD", "python3", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:3923/')"]
+ interval: 30s
+ timeout: 10s
+ retries: 3
+```
+
## Healthcheck
-A simple TCP check on 3923 works; Copyparty returns HTTP 200 on the web root.
+A simple HTTP check on port 3923 works. The root path returns HTTP 200 with the directory listing.
+
+## WOPI Integration
+
+Copyparty v1.20.9+ supports WOPI (Web Application Open Platform Interface) for editing Office documents in the web UI via Collabora Online. Enable with the `--wopi-accs` flag to limit WOPI access to specific paths.
+
+## Security Advisories
+
+| Version | CVE | Issue |
+|---------|-----|-------|
+| v1.20.19 | GHSA-phv8-wgjp-g4p9 | FTP upload outside volume |
+| v1.20.17 | GHSA-x5pq-m9p8-f4vx | dirkey/filekey confusion |
+| v1.20.9 | CVE-2026-27948 / GHSA-62cr-6wp5-q43h | XSS via file upload |
+
+Upgrade to v1.20.19+ to cover all known CVEs. The dirkey fix (v1.20.17) matters if using both `dirkeys` and `filekeys`.
## Gotchas
-- Large transfers benefit from disabling TLS inside the app and letting Caddy terminate TLS.
-- Do not give Copyparty the whole disk; give it a dedicated share directory.
-- Upload tokens scoped per folder prevent accidental global write access.
+- **Large transfers:** Disable TLS inside the app and let Caddy terminate TLS. This avoids double encryption overhead.
+- **Dedicated share directory:** Do not give Copyparty the whole disk. Mount only the intended share path.
+- **Upload tokens scoped per folder:** Prevents accidental global write access.
+- **CSP nonces (v1.20.17+):** Security hardening for JavaScript execution. May break third-party JS plugins (`--js-browser` / `--html-head`).
+- **Custom thumbnail extractors (v1.20.19+):** New `thumbex` framework. Requires configuration in `initcfg`.
## See Also
- [[Dankserver Stack]]
-- [[Caddy]] — TLS termination and auth in front
+- [[Caddy]] — TLS termination and authentication in front
+- [[ARR-Stack]] — Copyparty as a viewer alongside automated download management
diff --git a/docs/Dankserver/Dankserver Stack.md b/docs/Dankserver/Dankserver Stack.md
index 9743097..affe2fa 100644
--- a/docs/Dankserver/Dankserver Stack.md
+++ b/docs/Dankserver/Dankserver Stack.md
@@ -8,7 +8,13 @@ graph LR
C --> R[RomM]
C --> P[Copyparty]
C --> W[Calibre-Web]
- W --> L[Calibre library]
+ C --> J[Jellyfin]
+ C --> S[Sonarr]
+ C --> D[Radarr]
+ C --> L[Lidarr]
+ C --> A[Readarr]
+ C --> O[Prowlarr]
+ W --> L2[Calibre library]
```
## Services
@@ -18,6 +24,22 @@ graph LR
| RomM | romm.dankserver.local | 8085 | [[RomM]] |
| Copyparty | files.dankserver.local | 3923 | [[Copyparty]] |
| Calibre-Web | books.dankserver.local | 8083 | [[Calibre-Web]] |
+| Jellyfin | jellyfin.dankserver.local | 8096 | [[Jellyfin]] |
+| Sonarr | sonarr.dankserver.local | 8989 | [[ARR-Stack]] |
+| Radarr | radarr.dankserver.local | 7878 | [[ARR-Stack]] |
+| Lidarr | lidarr.dankserver.local | 8686 | [[ARR-Stack]] |
+| Readarr | readarr.dankserver.local | 8787 | [[ARR-Stack]] |
+| Prowlarr | prowlarr.dankserver.local | 9696 | [[ARR-Stack]] |
+| qBittorrent | qbittorrent.dankserver.local | 8080 | [[ARR-Stack]] |
+
+## Optional Services
+
+These services are included in the compose file but may be disabled.
+
+| Service | Hostname | Port | Docs |
+|---------|----------|------|------|
+| Jackett | jackett.dankserver.local | 9117 | [[ARR-Stack]] |
+| Whisparr | whisparr.dankserver.local | 6969 | [[ARR-Stack]] |
## Removed Services
diff --git a/docs/Dankserver/Jellyfin.md b/docs/Dankserver/Jellyfin.md
new file mode 100644
index 0000000..d3f96e8
--- /dev/null
+++ b/docs/Dankserver/Jellyfin.md
@@ -0,0 +1,173 @@
+# Jellyfin
+
+Jellyfin is a free, open-source media server and suite of applications for organizing, managing, and streaming personal media collections (movies, TV shows, music, photos) to networked devices. It is a community fork of Emby, written in C# (.NET), and uses FFmpeg for transcoding.
+
+**Host:** Dankserver — **Hostname:** `jellyfin.dankserver.local` — **Docs:** https://jellyfin.org
+
+## Facts
+
+| Item | Value |
+|------|-------|
+| Latest version | Check https://jellyfin.org/downloads |
+| Image | `jellyfin/jellyfin:latest` (Docker Hub) or `ghcr.io/jellyfin/jellyfin` |
+| Base | Debian-based |
+| Primary port | 8096 (HTTP, web UI + API) |
+| HTTPS port | 8920 (optional) |
+| Client discovery | 7359/udp |
+| DLNA | 1900/udp (optional) |
+| GPU passthrough | `/dev/dri` (Intel QSV / AMD VA-API) or NVIDIA NVENC |
+| License | MIT |
+
+## Architecture
+
+```mermaid
+graph LR
+ U[User] --> C[Caddy :80/:443]
+ C --> J[Jellyfin :8096]
+ J --> Lib[/media/movies]
+ J --> Lib2[/media/shows]
+ J --> Lib3[/media/music]
+ J --> Conf[Jellyfin config]
+ J --> Cache[Jellyfin cache]
+```
+
+## Docker Compose
+
+```yaml
+services:
+ jellyfin:
+ image: jellyfin/jellyfin:latest
+ container_name: jellyfin
+ restart: unless-stopped
+ user: "1000:1000"
+ ports:
+ - "8096:8096/tcp"
+ - "8920:8920/tcp"
+ - "7359:7359/udp"
+ - "1900:1900/udp"
+ environment:
+ - JELLYFIN_PublishedServerUrl=http://192.168.0.235:8096
+ - DOTNET_SYSTEM_IO_DISABLEFILELOCKING=true
+ volumes:
+ - /opt/jellyfin/config:/config
+ - /opt/jellyfin/cache:/cache
+ - /mnt/media/movies:/data/movies:ro
+ - /mnt/media/shows:/data/shows:ro
+ devices:
+ - /dev/dri:/dev/dri
+ group_add:
+ - "992" # Host render group GID — verify with `getent group render`
+ healthcheck:
+ test: ["CMD", "curl", "-f", "http://localhost:8096/health"]
+ interval: 30s
+ timeout: 10s
+ retries: 3
+```
+
+## Caddy Configuration
+
+```caddy
+jellyfin.dankserver.local {
+ reverse_proxy 127.0.0.1:8096
+
+ transport http {
+ versions h2c 1.1
+ }
+}
+```
+
+Set **Base URL** to `/` (or the path you configure) in Jellyfin Dashboard → Networking. Add `127.0.0.1` to **Known Proxies**.
+
+## Storage
+
+| Mount | Purpose | Size Estimate |
+|-------|---------|---------------|
+| `/opt/jellyfin/config` | SQLite database, settings, cached images | 10–100 GB |
+| `/opt/jellyfin/cache` | Transcoding segments, extracted posters | Self-cleaning per session |
+
+The database must be on local storage (SSD/NVMe preferred). Media libraries can be on NFS/mergerfs. If media is on NFS, set `DOTNET_SYSTEM_IO_DISABLEFILELOCKING=true`.
+
+## Authentication
+
+- **Built-in auth (default):** Per-user libraries, parental controls, max bitrate limits. Manage via Dashboard → Users.
+- **Caddy basic auth:** Single login layer. Does not integrate with per-user Jellyfin permissions.
+- **LDAP/OIDC:** Available via plugins (LDAP-Auth, Jellyfin SSO). Integrates with Authentik, Keycloak, etc.
+
+For Dankserver, built-in auth is recommended. Use Caddy basic auth on top for external access.
+
+## Metadata Scraping
+
+| Provider | Content | Type |
+|----------|---------|------|
+| TheMovieDb (TMDb) | Movies, TV shows, posters, fan art, overviews | Default |
+| OMDb API | Movies only, English metadata | Default |
+| TheTVDB | TV shows, episodes | Plugin |
+| fanart.tv | Extra artwork, banners, thumbnails | Plugin |
+| AniDB | Anime metadata | Plugin |
+| MusicBrainz | Music metadata | Plugin |
+| Open Subtitles | Subtitle downloads | Plugin |
+| Intro Skipper | TV show intro detection | Plugin |
+
+## Clients
+
+| Platform | App | Quality |
+|----------|-----|---------|
+| Web | Jellyfin Web | Excellent |
+| Android | Jellyfin (Google Play / F-Droid) | Excellent |
+| Android TV / Fire TV | Jellyfin | Good |
+| iOS / iPadOS | Swiftfin (third-party) | Good |
+| Desktop | Jellyfin Media Player (mpv-based) | Excellent |
+| Roku | Jellyfin (Channel Store) | Good |
+| Kodi | Jellyfin addon | Excellent |
+| Samsung/LG Smart TVs | TV browser | Fair |
+
+## Jellyfin vs Plex
+
+| Feature | Jellyfin | Plex |
+|---------|----------|------|
+| Cost | Free, no tiers | Free core; Plex Pass ($5/mo or $120 lifetime) |
+| License | MIT | Proprietary |
+| Account required | No | Yes |
+| Hardware transcoding | Free | Plex Pass required |
+| Mobile sync | Free | Plex Pass |
+| Live TV / DVR | Free | Plex Pass |
+| Client apps | Good, improving | Excellent, widest device support |
+
+Run Jellyfin alongside Plex for an incremental migration. Point Jellyfin at the same media folders as Plex. Watch history and metadata will not transfer cleanly.
+
+## Resource Requirements
+
+- **Idle / Direct Play:** ~200–500 MB RAM, <5% CPU. Negligible.
+- **1080p transcode (CPU):** 40–60% CPU on a modern multi-core CPU.
+- **1080p transcode (with QSV):** 3–5% CPU.
+- **4K HDR transcode (with QSV):** 5–15% CPU.
+- **Minimum host RAM:** 4 GB. 8 GB comfortable for transcoding.
+
+## Deployment Checklist
+
+1. Verify GPU on host: `ls -l /dev/dri/`, `lspci | grep -i vga`, `getent group render`.
+2. Install media drivers: `sudo apt install intel-media-va-driver-non-free vainfo intel-gpu-tools`.
+3. Create directories: `/opt/jellyfin/config`, `/opt/jellyfin/cache`.
+4. Write compose file, verify device and GID.
+5. Deploy: `podman compose up -d`.
+6. Test GPU passthrough: `podman exec jellyfin /usr/lib/jellyfin-ffmpeg/vainfo --display drm --device /dev/dri/renderD128`.
+7. Configure Caddy reverse proxy. Set Known Proxies in Jellyfin.
+8. Add media libraries in Dashboard.
+9. Install plugins: Open Subtitles, fanart.tv, Intro Skipper.
+10. Test playback on Android/Chrome.
+
+## Gotchas
+
+- **GPU passthrough silently failing:** `/dev/dri` must be passed and the correct render node used. Verify with `vainfo` inside the container.
+- **Wrong render group GID:** Common GID is 992, but varies per host. Run `getent group render` to find it.
+- **Subtitle burn-in triggers full transcode:** PGS (image-based) subtitles force full video transcoding. SRT/ASS (text-based) play fine.
+- **Database on NFS:** SQLite on network storage causes slow startup and corruption risk. Keep `/config` on local storage.
+- **Client bitrate limit too low:** Many clients default to 20 Mbps. Set Max Streaming Bitrate to Original or Maximum.
+- **Naming convention mismatches:** Non-standard folder/file names cause missing metadata. Use `Movie Title (Year)` for movies and `Show Name/Season 01/Show Name S01E01.mkv` for TV.
+
+## See Also
+
+- [[Dankserver Stack]]
+- [[ARR-Stack]] — automated download into Jellyfin libraries
+- [[Copyparty]] — alternative file viewer for existing collections
+- [[Plex]] — existing media server on this host
diff --git a/docs/Dankserver/RomM-Scraping.md b/docs/Dankserver/RomM-Scraping.md
new file mode 100644
index 0000000..855d0af
--- /dev/null
+++ b/docs/Dankserver/RomM-Scraping.md
@@ -0,0 +1,296 @@
+# RomM Scraping & Asset Management
+
+This guide covers metadata providers, cover art, ROM naming conventions, emulator integration, and the scraping workflow for RomM on Dankserver.
+
+**Reference:** https://docs.romm.app
+
+## 1. Metadata Providers
+
+RomM v5.1.0+ supports 11+ metadata providers. The `scan.priority.metadata` list determines which provider wins for descriptive fields. The `scan.priority.artwork` list controls cover art sourcing separately.
+
+### Screenscraper (ScreenScraper.fr)
+
+- **Type:** Free, community-driven (French provider)
+- **Credentials:** `SCREENSCRAPER_USER` + `SCREENSCRAPER_PASSWORD` (account user/pass)
+- **Registration:** https://www.screenscraper.fr/membreinscription.php
+- **Rate limits:** No documented hard cap. Community-claimed ~5 req/sec burst.
+- **Capabilities:** Titles, descriptions, cover art, screenshots, manuals, 3D boxes, CD/cartridge covers, marquee logos, fanart, bezels, gameplay videos.
+- **Region support:** Excellent. Respects `scan.priority.region` and `scan.priority.region_mode`.
+- **Best for:** Richest media coverage (all art types), multi-platform, free. Use as secondary to IGDB for metadata, or swap priority order if Screenscraper art quality is preferred.
+
+### IGDB (Internet Game Database / Twitch)
+
+- **Type:** Freemium (free tier with limits; paid for higher volume)
+- **Credentials:** `IGDB_CLIENT_ID` + `IGDB_CLIENT_SECRET` (Twitch OAuth2)
+- **Registration:** Twitch Developer Portal. Create app with name `romm-`, OAuth redirect `localhost`, category `Application Integration`, client type `Confidential`.
+- **Rate limits:** ~10 req/sec for free tier. Cached responses reduce effective calls.
+- **Capabilities:** Titles, descriptions, cover art, screenshots, related games, release dates, genres, platforms.
+- **Limitations:** No 3D boxes or physical media art. No manuals. No marquee/fanart.
+- **Default priority:** Highest (`igdb` listed first in both metadata and artwork priority).
+
+### MobyGames
+
+- **Type:** Paid (hobbyist API subscription; non-commercial use only)
+- **Credentials:** `MOBYGAMES_API_KEY`
+- **Rate limits:** Hobbyist tier ~0.2 req/sec (one request every 5 seconds).
+- **RomM recommendation:** "We recommend using ScreenScraper instead, as it is free to use."
+- **Best for:** Supplemental metadata depth for older PC/console titles.
+
+### RetroAchievements (RAHasher)
+
+- **Type:** Free, hash-based achievement tracking
+- **Credentials:** `RETROACHIEVEMENTS_API_KEY`
+- **How it works:** RomM bundles RAHasher (v1.8.3+). ROMs are hashed during scans, matched against RA online database. Achievement progress synced per-user via their RA username.
+- **Known issues:** Folder-based disc ROMs (`.cue` + `.bin`, `.gdi`) may fail hashing. Non-zip archives (`.7z`, `.rar`) had incorrect hash computation. Ensure `filesystem.skip_hash_calculation` is `false`.
+- **Important:** Achievements cannot be earned via RomM's built-in EmulatorJS player. Must use an RA-capable standalone emulator.
+
+### Additional Built-in Providers
+
+| Provider | Slug | Enabled Via | Type |
+|----------|------|-------------|------|
+| Hasheous | `hasheous` | `HASHEOUS_API_ENABLED=true` | Free, hash-based, proxies IGDB data |
+| Playmatch | `playmatch` | `PLAYMATCH_API_ENABLED=true` | Multi-provider hash matching |
+| LaunchBox | `launchbox` | `LAUNCHBOX_API_ENABLED=true` + XML | Local XML database |
+| Flashpoint | `flashpoint` | `FLASHPOINT_API_ENABLED=true` | 180,000+ browser games |
+| HowLongToBeat | `hltb` | `HLTB_API_ENABLED=true` | Game completion times |
+| SteamGridDB | `steamgriddb` | `STEAMGRIDDB_API_KEY` | Custom cover art |
+| ES-DE gamelist | `gamelist` | Place `gamelist.xml` in platform folder | Local XML |
+
+## 2. Recommended Provider Combinations
+
+| Combo | Providers | Best For | Cost |
+|-------|-----------|----------|------|
+| Quick Starter | Hasheous only | No API keys needed | Free |
+| French Connection | Screenscraper + RetroAchievements | Rich media, free | Free |
+| Chef's Choice (recommended) | Hasheous + IGDB + SteamGridDB + RetroAchievements | Best overall quality | Free (IGDB free tier) |
+| Twitch Fanboy | IGDB + Playmatch | Single-provider solution | Free |
+
+**For Dankserver,** the recommended setup:
+- `IGDB` (primary metadata) + `SCREENSCRAPER` (artwork backup + rich media) + `RETROACHIEVEMENTS` (achievements) + `HASHEOUS_API_ENABLED=true` (fast hash-based fallback)
+
+## 3. ROM File Naming Conventions
+
+### No-Intro Naming Scheme (Recommended)
+
+The de facto standard for cartridge and handheld ROMs. Only `Title` and `Region` are required.
+
+```
+Super Metroid (Japan, USA) (En,Ja)
+Sonic the Hedgehog (USA)
+Metal Gear Solid (USA) (Disc 1)
+[BIOS] PS1 System BIOS (USA)
+Tetris [!].gba
+```
+
+**Field meanings:**
+- `(Region)`: `USA`, `EUR`, `JPN`, `UE` (USA+Europe), `JUE` (Japan+USA+Europe)
+- `(Languages)`: `En`, `Ja`, `De`, `Fr`, `Es`
+- `[Status]`: `[!]` = verified good dump, `[b]` = bad dump, `[c]` = completed
+- `(Devstatus)`: `(Proto)`, `(Demo)`, `(Release Candidate)`
+- `[BIOS]` prefix: Identifies BIOS files
+
+### RomM's Region/Language/Revision Parsing
+
+RomM parses inline tags from filenames:
+
+```
+tetris (En) [1.0001] [HACK] [!].gba
+```
+
+- Region/language: ISO-like codes or full names
+- Revision: `rev v1` or `rev-1`
+- Provider ID tags: `(igdb-xxxx)`, `(ssfr-xxxx)`, `(moby-xxxx)`, `(ra-xxxx)` — forces specific provider match
+
+### Best Practices for Dankserver
+
+1. Use No-Intro naming as the standard.
+2. Use **Igir** to rename and organize ROMs into RomM's required folder structure:
+ ```
+ igir copy zip test clean \
+ --dat "No-Intro*.zip" \
+ --input "ROMs" \
+ --output "/opt/romm/roms/{romm}"
+ ```
+3. Keep `[BIOS]` prefix for BIOS files in the `bios/` directory.
+4. One ROM file per game folder where possible.
+5. Use archive formats (`.zip`, `.7z`) — RomM supports them natively.
+
+## 4. Cover Art & Media Assets
+
+### Media Types
+
+| Media Type | Description | Best Provider |
+|-----------|-------------|---------------|
+| `box2d` | Standard 2D front cover art | IGDB, Screenscraper |
+| `box3d` | 3D box art | Screenscraper |
+| `miximage` | Composite (box + screenshot + logo) | Screenscraper |
+| `screenshot` | In-game screenshot | IGDB, Screenscraper |
+| `marquee` | Transparent logo/header | Screenscraper |
+| `bezel` | EmulatorJS-compatible bezel | Screenscraper |
+| `manual` | PDF instruction manual | Screenscraper |
+| `video` | Gameplay video/trailer | Screenscraper |
+
+### Custom Artwork Directories
+
+RomM supports manual artwork placement via subdirectories within each game folder:
+
+```
+/opt/romm/library/roms/{platform}/{game}/
+ game.rom
+ covers/
+ screenshots/
+ 3dboxes/
+ manuals/
+ fanart/
+ videos/
+ marquees/
+ bezels/
+```
+
+For ES-DE gamelist imports, media maps as:
+
+| gamelist.xml Tag | Folder Name |
+|-----------------|-------------|
+| `` | `covers/` |
+| `` | `backcovers/` |
+| `` | `3dboxes/` |
+| `