chore: rebrand Subfeed -> Siftlode
Rename all user-facing references (UI wordmark Sift+lode, titles, app name,
legal pages, onboarding wizard, emails, README/docs) and infra paths
(/srv/subfeed -> /srv/siftlode, image tag, deploy script, backup filenames).
Internal identifiers kept on purpose: Postgres user/db "subfeed", logger
namespace, localStorage keys, and the subfeed_pgdata volume (renaming would
orphan the migrated production data).
2026-06-14 04:40:22 +02:00
|
|
|
# Siftlode
|
2026-06-11 01:01:37 +02:00
|
|
|
|
2026-07-01 12:46:50 +02:00
|
|
|
**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.
|
|
|
|
|
|
|
|
|
|

|
|
|
|
|
|
|
|
|
|
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](https://docs.docker.com/get-docker/) with the Compose plugin.
|
|
|
|
|
|
|
|
|
|
**1. Get the files and run the installer:**
|
|
|
|
|
|
|
|
|
|
```sh
|
|
|
|
|
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](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:
|
2026-06-11 01:01:37 +02:00
|
|
|
|
|
|
|
|
```sh
|
2026-07-01 12:46:50 +02:00
|
|
|
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
|
2026-06-11 01:01:37 +02:00
|
|
|
```
|
|
|
|
|
|
2026-07-01 12:46:50 +02:00
|
|
|
Open `http://localhost:8080` and finish in the setup wizard as above. (Set a `POSTGRES_PASSWORD` in
|
|
|
|
|
`.env` too.)
|
|
|
|
|
|
|
|
|
|
## HTTPS / public access
|
2026-06-11 16:35:00 +02:00
|
|
|
|
2026-07-01 12:46:50 +02:00
|
|
|
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.
|
2026-06-11 16:35:00 +02:00
|
|
|
|
2026-07-01 12:46:50 +02:00
|
|
|
## Updating & backups
|
2026-06-11 16:35:00 +02:00
|
|
|
|
2026-07-01 12:46:50 +02:00
|
|
|
```sh
|
|
|
|
|
docker compose -f docker-compose.selfhost.yml pull # or: docker compose pull
|
|
|
|
|
docker compose -f docker-compose.selfhost.yml up -d
|
|
|
|
|
```
|
2026-06-11 16:35:00 +02:00
|
|
|
|
2026-07-01 12:46:50 +02:00
|
|
|
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`.
|
2026-06-11 16:35:00 +02:00
|
|
|
|
2026-07-01 12:46:50 +02:00
|
|
|
## How it works
|
2026-06-11 16:35:00 +02:00
|
|
|
|
2026-07-01 12:46:50 +02:00
|
|
|
- **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.
|
2026-06-11 16:35:00 +02:00
|
|
|
|
2026-06-11 01:01:37 +02:00
|
|
|
## Tech
|
|
|
|
|
|
2026-07-01 12:46:50 +02:00
|
|
|
FastAPI + PostgreSQL (SQLAlchemy, Alembic) backend; React + Vite + Tailwind + TanStack Query
|
|
|
|
|
frontend; packaged as a single Docker image with Docker Compose.
|
2026-06-11 01:01:37 +02:00
|
|
|
|
|
|
|
|
## Note
|
|
|
|
|
|
|
|
|
|
This project is developed with AI assistance.
|