chore(release): v0.22.0 — Download Center worker/sidecar in all composes + docs
Prep the Download Center epic (Phase 1 + editor + share) for prod/self-host:
- Dockerfile: create /downloads owned by appuser so a named-volume mount is writable (prod Linux).
- docker-compose.{home,selfhost,yml}: add the 'worker' (yt-dlp/ffmpeg job loop) + 'bgutil-pot'
(PO-token) services + a downloads mount (DOWNLOAD_ROOT, WORKER_ENABLED). Media defaults to a
named volume; DOWNLOAD_HOST_PATH points it at a host dir (e.g. a Plex-readable folder).
- README / docs/self-hosting.md / .env.example / install.{sh,ps1}: document the Download Center,
the two extra containers, and DOWNLOAD_HOST_PATH.
- VERSION 0.22.0 + releaseNotes entry.
This commit is contained in:
parent
7ab76ccafb
commit
abedca5b8c
10 changed files with 154 additions and 4 deletions
|
|
@ -54,3 +54,11 @@ SMTP_FROM=
|
|||
# more than one instance against the same database, keep SCHEDULER_ENABLED=true on exactly one
|
||||
# of them and false on the rest, to avoid double quota use and write races.
|
||||
SCHEDULER_ENABLED=true
|
||||
|
||||
# --- Download center ---
|
||||
# The Download Center adds a `worker` container (runs the yt-dlp/ffmpeg job loop) and a small
|
||||
# `bgutil-pot` sidecar (mints YouTube tokens) — both come up automatically with docker compose.
|
||||
# Downloaded media defaults to a Docker-managed named volume. Set DOWNLOAD_HOST_PATH to a host
|
||||
# directory instead — e.g. one your Plex server can read — to keep the Plex-style tree there.
|
||||
# The path must be writable by the container user (uid of `appuser`, 1000): chown 1000:1000 <dir>.
|
||||
# DOWNLOAD_HOST_PATH=/mnt/media/youtube
|
||||
|
|
|
|||
|
|
@ -54,7 +54,13 @@ COPY backend/ .
|
|||
COPY VERSION ./VERSION
|
||||
COPY --from=frontend /fe/dist ./app/static_spa
|
||||
|
||||
RUN adduser --disabled-password --gecos "" appuser && chown -R appuser /app
|
||||
# Create the download-center mount point owned by the app user, so a fresh named volume mounted
|
||||
# at /downloads inherits appuser ownership (Docker copies the image dir's ownership into a new
|
||||
# empty volume) — the worker/API can then write there without a manual chown. A bind mount to a
|
||||
# host path instead needs that path writable by this uid (see docs/self-hosting.md).
|
||||
RUN adduser --disabled-password --gecos "" appuser \
|
||||
&& mkdir -p /downloads \
|
||||
&& chown -R appuser /app /downloads
|
||||
USER appuser
|
||||
|
||||
EXPOSE 8000
|
||||
|
|
|
|||
|
|
@ -22,6 +22,9 @@ blocker and SponsorBlock keep working.
|
|||
tags to slice the feed by.
|
||||
- **Playlists with two-way YouTube sync** — build them locally, keep them in sync in both directions.
|
||||
- **In-app player** with resume, plus keyboard/scroll controls.
|
||||
- **Download Center** — save videos to the server with yt-dlp in a Plex-friendly layout (format
|
||||
presets, per-user storage quota), trim / crop / split & join them in a built-in editor, then save
|
||||
to your device, share with another user, or hand out a public watch link.
|
||||
- **Multi-user** with per-user private state, a shared catalog, and a fair daily API-quota guard.
|
||||
- **Self-hosted & private**, with a first-run web setup wizard and the interface in **English,
|
||||
Hungarian and German**.
|
||||
|
|
|
|||
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
|||
0.21.0
|
||||
0.22.0
|
||||
|
|
|
|||
|
|
@ -37,6 +37,10 @@ services:
|
|||
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
|
||||
|
|
@ -45,6 +49,10 @@ services:
|
|||
# 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
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
cap_drop:
|
||||
|
|
@ -60,8 +68,48 @@ services:
|
|||
start_period: 30s
|
||||
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: forge.b1fr0st.eu/peter/siftlode:${IMAGE_TAG:-latest}
|
||||
command: ["python", "-m", "app.worker"]
|
||||
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
|
||||
bgutil-pot:
|
||||
condition: service_started
|
||||
networks: [internal]
|
||||
volumes:
|
||||
- ${DOWNLOAD_HOST_PATH:-siftlode_downloads}:/downloads
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
cap_drop:
|
||||
- ALL
|
||||
tmpfs:
|
||||
- /tmp
|
||||
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
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
siftlode_pgdata:
|
||||
siftlode_downloads:
|
||||
|
||||
networks:
|
||||
internal:
|
||||
|
|
|
|||
|
|
@ -25,11 +25,19 @@ services:
|
|||
env_file: .env
|
||||
environment:
|
||||
DATABASE_URL: postgresql+psycopg://${POSTGRES_USER:-siftlode}:${POSTGRES_PASSWORD:-siftlode}@db:5432/${POSTGRES_DB:-siftlode}
|
||||
# Download center: the API serves finished files + builds filmstrips; the worker (below) runs
|
||||
# the yt-dlp job loop.
|
||||
DOWNLOAD_ROOT: /downloads
|
||||
WORKER_ENABLED: "false"
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
ports:
|
||||
- "${APP_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:-downloads}:/downloads
|
||||
healthcheck:
|
||||
test: ["CMD", "python", "-c", "import urllib.request; exit(0 if urllib.request.urlopen('http://localhost:8000/healthz').status == 200 else 1)"]
|
||||
interval: 15s
|
||||
|
|
@ -38,5 +46,39 @@ services:
|
|||
start_period: 25s
|
||||
restart: unless-stopped
|
||||
|
||||
# Download worker: same image, runs the yt-dlp / ffmpeg job loop instead of the API. Shares the
|
||||
# DB (job queue) + the downloads mount with the API.
|
||||
worker:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
args:
|
||||
APP_VERSION: ${APP_VERSION:-dev}
|
||||
GIT_SHA: ${GIT_SHA:-unknown}
|
||||
BUILD_DATE: ${BUILD_DATE:-}
|
||||
command: ["python", "-m", "app.worker"]
|
||||
env_file: .env
|
||||
environment:
|
||||
DATABASE_URL: postgresql+psycopg://${POSTGRES_USER:-siftlode}:${POSTGRES_PASSWORD:-siftlode}@db:5432/${POSTGRES_DB:-siftlode}
|
||||
SCHEDULER_ENABLED: "false"
|
||||
DOWNLOAD_ROOT: /downloads
|
||||
WORKER_ENABLED: "true"
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
bgutil-pot:
|
||||
condition: service_started
|
||||
volumes:
|
||||
- ${DOWNLOAD_HOST_PATH:-downloads}:/downloads
|
||||
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
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
pgdata:
|
||||
downloads:
|
||||
|
|
|
|||
|
|
@ -8,7 +8,9 @@ configured in a web wizard on first start. There's no editing of config files by
|
|||
|
||||
- A machine with **Docker** and the **Docker Compose plugin** (Docker Desktop on Windows/macOS,
|
||||
or Docker Engine on Linux).
|
||||
- A few hundred MB of disk and ~1 GB RAM free.
|
||||
- A few hundred MB of disk and ~1 GB RAM free for the app itself. The optional Download Center
|
||||
stores media too — budget disk for whatever you download (it's bounded by per-user quotas you
|
||||
set as admin).
|
||||
- Optional: a domain name + reverse proxy if you want HTTPS / public access (see below).
|
||||
|
||||
## 1. Get the files
|
||||
|
|
@ -77,6 +79,24 @@ public access, put a reverse proxy (Caddy, Nginx, Traefik…) in front to termin
|
|||
secure). If you've already run the installer, edit `OAUTH_REDIRECT_URL` in `.env` to the https
|
||||
callback URL and `docker compose -f docker-compose.selfhost.yml up -d`.
|
||||
|
||||
## Download Center (media storage)
|
||||
|
||||
The stack includes a **Download Center**: an admin-enabled feature that saves videos to the server
|
||||
with yt-dlp (Plex-friendly folders + `.nfo`/poster), lets users trim/crop/join clips, and shares
|
||||
them. It runs two extra containers that come up automatically — a `worker` (the download/edit job
|
||||
loop) and a small `bgutil-pot` sidecar (mints YouTube tokens so downloads aren't bot-blocked). No
|
||||
configuration is required; per-user storage quotas are set on the admin **Downloads → System** page.
|
||||
|
||||
By default the media lives in a Docker-managed volume (`siftlode_downloads`). To keep it somewhere
|
||||
you can reach from other apps — e.g. a folder your **Plex** server indexes — point it at a host
|
||||
directory by adding this to `.env` and re-running `up -d`:
|
||||
|
||||
```bash
|
||||
DOWNLOAD_HOST_PATH=/mnt/media/youtube
|
||||
```
|
||||
|
||||
The directory must be writable by the container user (uid `1000`): `sudo chown -R 1000:1000 <dir>`.
|
||||
|
||||
## Day-to-day
|
||||
|
||||
```bash
|
||||
|
|
@ -84,8 +104,9 @@ callback URL and `docker compose -f docker-compose.selfhost.yml up -d`.
|
|||
docker compose -f docker-compose.selfhost.yml pull
|
||||
docker compose -f docker-compose.selfhost.yml up -d
|
||||
|
||||
# Logs / status
|
||||
# Logs / status (api = web, worker = downloads/edits)
|
||||
docker compose -f docker-compose.selfhost.yml logs -f api
|
||||
docker compose -f docker-compose.selfhost.yml logs -f worker
|
||||
docker compose -f docker-compose.selfhost.yml ps
|
||||
|
||||
# Stop
|
||||
|
|
|
|||
|
|
@ -14,6 +14,20 @@ export interface ReleaseEntry {
|
|||
}
|
||||
|
||||
export const RELEASE_NOTES: ReleaseEntry[] = [
|
||||
{
|
||||
version: "0.22.0",
|
||||
date: "2026-07-04",
|
||||
summary: "The Download Center — save videos to the server, edit them into clips, and share them.",
|
||||
features: [
|
||||
"Download Center: save any video (or a search result) to the server with yt-dlp, laid out Plex-style with a poster + info file. Choose a built-in format preset or make your own, keep an eye on your per-user storage quota, and save the finished file to your device.",
|
||||
"Built-in video editor: trim, crop, and cut a download into segments — keep the parts you want as separate clips or join them into one file. Pick a precise (frame-accurate) or fast cut, with a scrub timeline and a hover preview.",
|
||||
"Sharing: share a download with another user (it appears in their “Shared with me”, where they can edit their own copy or remove it), or hand out a public watch link that plays on a clean, login-free page — with an optional password, an expiry, and a stream-only vs downloadable toggle.",
|
||||
"Tidier video titles across the feed, search and downloads — clickbait ALL-CAPS and trailing hashtag clutter are cleaned up for display (the original title is kept underneath).",
|
||||
],
|
||||
fixes: [
|
||||
"The browser tab now carries an app icon and shows the section you're on; clicking the Siftlode logo returns you to the feed.",
|
||||
],
|
||||
},
|
||||
{
|
||||
version: "0.21.0",
|
||||
date: "2026-07-02",
|
||||
|
|
|
|||
|
|
@ -28,6 +28,10 @@ POSTGRES_PASSWORD=$pgPass
|
|||
SECRET_KEY=$secretKey
|
||||
TOKEN_ENCRYPTION_KEY=$fernet
|
||||
OAUTH_REDIRECT_URL=$url/auth/callback
|
||||
|
||||
# Optional: store Download Center media in a host folder (e.g. one your Plex server reads) instead
|
||||
# of a Docker volume. Must be writable by uid 1000 (chown -R 1000:1000 <dir>).
|
||||
# DOWNLOAD_HOST_PATH=/mnt/media/youtube
|
||||
"@ | Set-Content -Path .env -Encoding ascii
|
||||
Write-Host "Wrote .env (secrets generated)."
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,10 @@ POSTGRES_PASSWORD=$POSTGRES_PASSWORD
|
|||
SECRET_KEY=$SECRET_KEY
|
||||
TOKEN_ENCRYPTION_KEY=$TOKEN_ENCRYPTION_KEY
|
||||
OAUTH_REDIRECT_URL=$URL/auth/callback
|
||||
|
||||
# Optional: store Download Center media in a host folder (e.g. one your Plex server reads) instead
|
||||
# of a Docker volume. Must be writable by uid 1000 (sudo chown -R 1000:1000 <dir>).
|
||||
# DOWNLOAD_HOST_PATH=/mnt/media/youtube
|
||||
EOF
|
||||
chmod 600 .env
|
||||
echo "Wrote .env (secrets generated)."
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue