Self-hosted multi-user YouTube subscription feed
Find a file
npeter83 f32c3f08dc fix(auth): pin each tab to its account so cross-tab changes don't leak in
A tab that never explicitly picked an account rode the session's default, so when ANOTHER
tab changed that default (adding or switching an account), this tab silently swapped identity
on its next refetch — the account chip updated while the feed/saved-views lagged until reload.

- Pin every tab to whatever account it first loaded as (write the sessionStorage override on
  load when none is set), so its requests always carry its own account header and cross-tab
  default changes can't reach it.
- 'Add account' now clears this tab's pin before the Google redirect, so on return the tab
  adopts the freshly-added account (and pins that) — the current tab switches to it while other
  tabs keep their own, matching the Gmail add-account model.
2026-07-02 00:16:51 +02:00
backend feat(auth): per-tab account — different account per browser tab 2026-07-01 23:43:35 +02:00
docs feat(selfhost): one-command self-host package (epic 6d) 2026-06-21 01:42:18 +02:00
frontend fix(auth): pin each tab to its account so cross-tab changes don't leak in 2026-07-02 00:16:51 +02:00
scripts chore: rename remaining subfeed references to siftlode 2026-06-21 06:53:12 +02:00
.dockerignore feat: M1 foundation — compose stack, FastAPI, Google OAuth, encrypted tokens 2026-06-11 01:01:37 +02:00
.env.example chore: prepare repo for public release — scrub internal infra, rewrite README 2026-07-01 12:46:50 +02:00
.gitattributes chore: enforce LF line endings via .gitattributes (CRLF for .ps1) 2026-06-11 01:02:06 +02:00
.gitignore feat: M1 foundation — compose stack, FastAPI, Google OAuth, encrypted tokens 2026-06-11 01:01:37 +02:00
docker-compose.localdev.yml chore: prepare repo for public release — scrub internal infra, rewrite README 2026-07-01 12:46:50 +02:00
docker-compose.selfhost.yml chore: rename remaining subfeed references to siftlode 2026-06-21 06:53:12 +02:00
docker-compose.yml chore: rename remaining subfeed references to siftlode 2026-06-21 06:53:12 +02:00
Dockerfile fix(version): read app version from the committed VERSION file 2026-06-15 02:22:27 +02:00
install.ps1 feat(selfhost): one-command self-host package (epic 6d) 2026-06-21 01:42:18 +02:00
install.sh feat(selfhost): one-command self-host package (epic 6d) 2026-06-21 01:42:18 +02:00
README.md chore: prepare repo for public release — scrub internal infra, rewrite README 2026-07-01 12:46:50 +02:00
VERSION chore(release): 0.20.2 2026-07-01 14:30:50 +02:00

Siftlode

Your YouTube subscriptions, the way a feed should work. Siftlode pulls every upload from the channels you follow into one clean, filterable feed — no algorithm deciding what you see, and no Shorts or livestream noise unless you want it. Self-hosted, multi-user, and private: your data stays on your own server.

Siftlode feed

Everything expensive (channels, videos, metadata) is fetched from YouTube once and stored locally, so filtering, searching and sorting are instant and don't burn API quota. Click a video to watch it in an in-app player that resumes where you left off — or open it on youtube.com so your own ad blocker and SponsorBlock keep working.

Features

  • A readable subscription feed — sort and filter by channel, tag, language, topic, length, upload date or watch state; hide channels without unsubscribing; save filter setups as named views.
  • Search all of YouTube from the feed — results play, save and add to playlists like any other video, and are materialised into your catalog.
  • Channel pages & a channel manager — per-channel stats and uploads, priorities, and your own 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.
  • 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.

Quick start (self-hosting)

You don't need to build anything — Siftlode runs from a prebuilt public image, and all configuration (your admin account, Google sign-in, email) happens in a first-run web wizard. You need Docker with the Compose plugin.

1. Get the files and run the installer:

git clone https://forge.b1fr0st.eu/peter/siftlode.git
cd siftlode
./install.sh          # Windows (PowerShell):  ./install.ps1

The installer generates a private .env (secrets), pulls the image, starts the app + database, and prints a one-time setup URL like http://localhost:8080/setup?token=….

2. Finish in your browser. Open that URL and follow the wizard:

  1. Admin account — the email + password you'll sign in with.
  2. Google sign-in (optional) — paste a Google OAuth client to enable "Sign in with Google" and pulling your YouTube subscriptions. Skip it to use email + password only.
  3. Email / SMTP (optional) — for verification/notification emails. Skip it and you (the admin) simply approve new accounts yourself.

Then sign in with your admin account. That's it. See docs/self-hosting.md for the full walkthrough.

Just trying it out? Press Enter at the installer's URL prompt to run on http://localhost:8080.

Build from source (alternative)

Prefer to build the image yourself instead of pulling it:

git clone https://forge.b1fr0st.eu/peter/siftlode.git
cd siftlode
cp .env.example .env
# generate the two secrets and paste them into .env:
python -c "import secrets;print('SECRET_KEY='+secrets.token_urlsafe(48))"
python -c "import base64,os;print('TOKEN_ENCRYPTION_KEY='+base64.urlsafe_b64encode(os.urandom(32)).decode())"
docker compose up --build -d      # builds from the included Dockerfile

Open http://localhost:8080 and finish in the setup wizard as above. (Set a POSTGRES_PASSWORD in .env too.)

HTTPS / public access

Port 8080 over plain HTTP is fine for a LAN or a quick trial. For public access put a reverse proxy (Caddy, Nginx, Traefik…) in front to terminate TLS, and set the public URL (the installer prompt, or OAUTH_REDIRECT_URL in .env) to your https://… address — this also marks the session cookie secure. Add that same …/auth/callback URL to your Google OAuth client's authorized redirect URIs.

Updating & backups

docker compose -f docker-compose.selfhost.yml pull   # or: docker compose pull
docker compose -f docker-compose.selfhost.yml up -d

Database migrations run automatically on startup. Your data (accounts, subscriptions, playlists, the video catalog) lives in a Postgres volume — back it up with scripts/backup.sh (or backup.ps1 on Windows) and restore with scripts/restore.sh.

How it works

  • Shared catalog, private state. Channels and videos are stored once and shared; each user's subscriptions, tags, playlists and watch/save/hide state are private.
  • Cheap by design. Public reads are cached locally; a shared daily quota budget and a background scheduler keep unattended syncing within YouTube's free API limits. An optional API key lets backfill run without depending on a user's OAuth token.

Tech

FastAPI + PostgreSQL (SQLAlchemy, Alembic) backend; React + Vite + Tailwind + TanStack Query frontend; packaged as a single Docker image with Docker Compose.

Note

This project is developed with AI assistance.