Files
ytplayer/docker-compose.yml
Jonathan Sykes 11ddb8a970 Uploads library on the USB drive with a nightly stable-disk copy; Topic channels resolve by name
- UPLOAD_DIR moves to the USB drive; reads fall back to UPLOAD_BACKUP_DIR on the
  ytplayer-data volume, and while the drive is offline new uploads land there
- scripts/ops/uploads-backup.sh (homelab cron, 03:30) copies both ways, never
  deletes; the server removes a deleted upload from both places
- /api/channel resolves a bare channel name ('Artist - Topic') to its id via a
  search, and the client falls back to the channel name for old saved entries
2026-10-02 22:38:05 +08:00

118 lines
5.2 KiB
YAML

services:
ytplayer:
build: .
restart: unless-stopped
volumes:
# libsql DB file persists across container rebuilds
- ytplayer-data:/app/data
# Bulk media on the homelab's USB drive (/mnt/data, mounted nofail): the
# re-fetchable media cache and the uploads library. The DB, lyrics and
# notes stay on ytplayer-data. If the drive is missing, the marker file
# below is missing too: the cache pauses (playback streams) and uploads
# are served from the nightly backup copy on ytplayer-data.
- /mnt/data/ytplayer:/app/bulk
environment:
PORT: "3000"
MEDIA_DIR: "/app/bulk/media"
MEDIA_VOLUME_MARKER: "/app/bulk/.hl-data"
MEDIA_CACHE_MAX_BYTES: "268435456000" # 250 GiB of the 458 GiB drive
MEDIA_MIN_FREE_BYTES: "21474836480" # keep 20 GiB free on the drive
UPLOAD_DIR: "/app/bulk/uploads"
UPLOAD_VOLUME_MARKER: "/app/bulk/.hl-data"
# Second copy on the stable disk, refreshed nightly by
# scripts/ops/uploads-backup.sh on the homelab host; reads fall back to it.
UPLOAD_BACKUP_DIR: "/app/data/uploads-backup"
DB_PATH: "/app/data/ytplayer.db"
APP_VERSION: "1.0.0"
# Unlocks /admin (API tokens, lyric/chapter history + restore). Set it in
# Dokploy's Environment tab — never commit it. Unset = admin disabled.
ADMIN_PASSWORD: "${ADMIN_PASSWORD:-}"
# Phone remote: "1" = a phone may only pair with a screen on the same
# network (same public IP as seen by the server). Off by default.
REMOTE_SAME_NETWORK: "${REMOTE_SAME_NETWORK:-0}"
# Shared secret the lyrics-worker uses to list songs and upload lyrics.
# Set in Dokploy's Environment tab (same value feeds both services).
LYRICS_WORKER_TOKEN: "${LYRICS_WORKER_TOKEN:-}"
# Peer-to-peer sharing (docs/p2p-architecture.md). ON by default.
P2P_ENABLED: "${P2P_ENABLED:-1}"
# Malware scan before a file's hash is admitted. OFF by default; needs an
# image built with INSTALL_CLAMAV=1. Hashing + media validation always run.
P2P_MALWARE_SCAN: "${P2P_MALWARE_SCAN:-0}"
# P2P_STALE_DAYS: "7" # holder shown as stale after this many days unchecked
# P2P_KEEP_MIN_VIEWS: "3" # server keeps copies with ≥ this many views…
# P2P_KEEP_DAYS: "30" # …in this many days
# P2P_KEEP_RECENT_DAYS: "14" # …or played this recently
# P2P_INTAKE_DIR: "/app/data/p2p-intake" # quarantine for device uploads (never served)
# P2P_INTAKE_MAX_BYTES: "3221225472" # 3 GiB
# Optional: force yt-dlp search instead of InnerTube API
# SEARCH_INNERTUBE: "0" # force yt-dlp search
# Optional: override yt-dlp binary path if you mount a custom one
# YTDLP_PATH: "/usr/local/bin/yt-dlp"
# YTDLP_WORKER: "0" # disable the long-lived yt-dlp worker pool
# YTDLP_WORKERS: "2" # pool size
# Server media cache (server/media-cache.js) — defaults shown.
# (MEDIA_DIR / MEDIA_CACHE_MAX_BYTES / MEDIA_MIN_FREE_BYTES set above;
# defaults without them: /app/data/media, 10 GiB, 5 GiB)
# MEDIA_AUTO_MAX_SECONDS: "3600" # longest video auto-cached on play
# MEDIA_TRANSCODE: "1" # 0 disables the compression lane
# MEDIA_CODEC: "hevc" # hevc | h264
# MEDIA_CRF: "28"
# MEDIA_PRESET: "medium"
# MEDIA_THREADS: "2"
# MEDIA_OPT_MAX_SECONDS: "3600" # don't re-encode videos longer than this
healthcheck:
test: ["CMD", "curl", "-sf", "http://localhost:3000/api/version"]
interval: 30s
timeout: 5s
retries: 3
start_period: 15s
networks:
- dokploy-network
- lyrics
labels:
- "traefik.enable=true"
- "traefik.http.routers.ytplayer-http.rule=Host(`worship.hesed.sbs`)"
- "traefik.http.routers.ytplayer-http.entrypoints=web"
- "traefik.http.routers.ytplayer-http.middlewares=redirect-to-https"
- "traefik.http.routers.ytplayer-https.rule=Host(`worship.hesed.sbs`)"
- "traefik.http.routers.ytplayer-https.entrypoints=websecure"
- "traefik.http.routers.ytplayer-https.tls.certresolver=letsencrypt"
- "traefik.http.services.ytplayer-svc.loadbalancer.server.port=3000"
# Transcribes saved songs that have no lyrics yet, one at a time, with
# faster-whisper on CPU (no API keys, no credits). Separate container so it
# never competes with playback/downloads: capped CPU + memory, low priority,
# and it fetches audio from the ytplayer service over the private network.
lyrics-worker:
build: ./scripts/lyrics
restart: unless-stopped
depends_on:
- ytplayer
environment:
YTP_BASE: "http://ytplayer:3000"
YTP_TOKEN: "${LYRICS_WORKER_TOKEN:-}"
WHISPER_MODEL: "large-v3-turbo" # ~1.6 GB download on first start
WHISPER_THREADS: "2"
WATCH_SECONDS: "300"
volumes:
- lyrics-models:/models
- lyrics-state:/data
cpus: "2"
mem_limit: 3g
networks:
- lyrics
volumes:
ytplayer-data:
driver: local
lyrics-models:
driver: local
lyrics-state:
driver: local
networks:
dokploy-network:
external: true
lyrics:
driver: bridge