Files
siftlode/docker-compose.selfhost.yml
T
peter 4aaaaa7860 chore(comments): drop epic, sprint and review-round ids from the code
The code cited its plan instead of saying why: `E28 review J4`, `(R6 S3b)`, `S1 justified…`,
`deep-review round 5`. That plan lives in the private ops repo, so for every reader of this one the
citation was noise, and a sentence whose subject was the plan step ("round 2 showed…") hid its
lesson behind an id. Each one is gone and the "why" kept: a bare citation is deleted, a sentence
carrying a lesson is reworded around it ("a later attempt showed…").

Measured (`scripts/audit_metrics.py`, product code): 652 lines in 270 files carried an epic code by
the definition the next commit holds (507 / 241 by the old, two-digit-only one) -> 0. Across all
code, tests, scripts and configs: ~1100 epic-code lines plus ~320 sprint/round ids.

Behaviour unchanged, checked mechanically: every changed .py file has the same AST once docstrings
are blanked; css/sh/toml/yml/json files are identical once comments are stripped; the only
non-comment changes in TS/JS are test titles, one description string in a contract table, and five
ESLint messages, whose text is all the gates snapshot diff holds. Not touched: Markdown (docs and
the changelog keep their history), Alembic migrations, TV episode notation (`S2 · E5`), DESIGN.md
ids (`C1`, `B4`, `S3h`), tracker ids (`SEC-16`, `DX-17`).

One stale claim fixed on the way: test_hls_reaper's docstring said the player checkpoints "every
10 s whether or not it is playing", which plex/stream.py calls wrong; it now says what the code does.
2026-10-05 00:19:39 +02:00

145 lines
5.7 KiB
YAML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Self-hosting Siftlode — pulls the prebuilt image, no source build needed.
#
# 1. Run ./install.sh (or install.ps1 on Windows) once — it generates a .env with secrets.
# 2. docker compose -f docker-compose.selfhost.yml up -d
# 3. Open the setup wizard at the URL printed in the logs:
# docker compose -f docker-compose.selfhost.yml logs api | grep SETUP
#
# The .env only needs four values (the install script generates all of them):
# POSTGRES_PASSWORD, SECRET_KEY, TOKEN_ENCRYPTION_KEY, OAUTH_REDIRECT_URL
# Everything else — the admin account, Google sign-in, SMTP — is set in the web wizard on
# first run. See docs/self-hosting.md.
# Bounded container logs. Docker's json-file driver keeps every line forever by default, and the API
# now writes one structured line per request — 3×50 MB per service is days of history on a
# busy instance and a hard ceiling on the disk it can take.
x-logging: &logging
driver: json-file
options:
max-size: "50m"
max-file: "3"
services:
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: ${POSTGRES_USER:-siftlode}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD must be set (run install.sh)}
POSTGRES_DB: ${POSTGRES_DB:-siftlode}
volumes:
- siftlode_pgdata:/var/lib/postgresql/data
networks: [internal]
security_opt:
- no-new-privileges:true
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-siftlode} -d ${POSTGRES_DB:-siftlode}"]
interval: 10s
timeout: 5s
retries: 12
logging: *logging
restart: unless-stopped
api:
image: git.b1fr0st.eu/peter/siftlode:${IMAGE_TAG:-latest}
env_file:
- .env
environment:
DATABASE_URL: postgresql+psycopg://${POSTGRES_USER:-siftlode}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB:-siftlode}
# This instance owns the background scheduler (single writer).
SCHEDULER_ENABLED: "true"
# Download center: the API serves finished files + builds filmstrips; the worker (below) runs
# the yt-dlp job loop. Read-only root is fine — it only writes to the /downloads mount + tmpfs.
DOWNLOAD_ROOT: /downloads
WORKER_ENABLED: "false"
depends_on:
db:
condition: service_healthy
networks: [internal]
ports:
# Reachable on the host's LAN at http://<host>:8080. Put a reverse proxy (Caddy/Nginx) in
# front for HTTPS / public exposure — and set OAUTH_REDIRECT_URL to the https URL.
- "${HTTP_PORT:-8080}:8000"
volumes:
# Downloaded media. Defaults to a Docker-managed named volume; set DOWNLOAD_HOST_PATH in .env
# to a host directory (e.g. one your Plex server reads) to keep the Plex-style tree there.
- ${DOWNLOAD_HOST_PATH:-siftlode_downloads}:/downloads
# Plex library, read-only. The Plex module streams files itself, so it needs to SEE them: set
# PLEX_MEDIA_HOST_PATH in .env to the host directory holding the library (the local side of the
# admin "path map", whose default is /data=/plex-media). Left unset it falls back to an empty
# named volume — harmless, and the Plex module is off by default.
- ${PLEX_MEDIA_HOST_PATH:-siftlode_plex_media}:/plex-media:ro
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
read_only: true
tmpfs:
- /tmp
healthcheck:
test: ["CMD", "python", "-c", "import urllib.request,sys; sys.exit(0 if urllib.request.urlopen('http://localhost:8000/healthz').status==200 else 1)"]
interval: 15s
timeout: 5s
retries: 5
start_period: 30s
logging: *logging
restart: unless-stopped
# Download worker: same image, runs the yt-dlp / ffmpeg job loop instead of the API. Shares the
# DB (job queue) and the downloads mount with the API. Not read-only (yt-dlp/deno/ffmpeg write to
# $HOME caches + the staging dir).
worker:
image: git.b1fr0st.eu/peter/siftlode:${IMAGE_TAG:-latest}
command: ["python", "-m", "app.worker"]
# tini as PID 1: reaps orphaned ffmpeg/yt-dlp grandchildren when the watchdog hard-kills a
# download child's process group (the worker reaps its own direct children via subprocess.wait).
init: true
env_file:
- .env
environment:
DATABASE_URL: postgresql+psycopg://${POSTGRES_USER:-siftlode}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB:-siftlode}
SCHEDULER_ENABLED: "false"
DOWNLOAD_ROOT: /downloads
WORKER_ENABLED: "true"
depends_on:
db:
condition: service_healthy
# Start only after the API is healthy (it applies the DB migrations on startup), so the
# download tables exist before the worker touches them.
api:
condition: service_healthy
bgutil-pot:
condition: service_started
networks: [internal]
volumes:
- ${DOWNLOAD_HOST_PATH:-siftlode_downloads}:/downloads
# Same read-only Plex library as the API — the worker resolves library paths too.
- ${PLEX_MEDIA_HOST_PATH:-siftlode_plex_media}:/plex-media:ro
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
tmpfs:
- /tmp
logging: *logging
restart: unless-stopped
# PO-token provider sidecar: mints YouTube Proof-of-Origin tokens so the worker beats bot-detection
# and unlocks high-quality formats (reached at http://bgutil-pot:4416, the config default).
bgutil-pot:
image: brainicism/bgutil-ytdlp-pot-provider:1.3.1
init: true
networks: [internal]
security_opt:
- no-new-privileges:true
logging: *logging
restart: unless-stopped
volumes:
siftlode_pgdata:
siftlode_downloads:
# Empty fallback for the Plex media mount when PLEX_MEDIA_HOST_PATH is unset (Plex module off).
siftlode_plex_media:
networks:
internal: