Zugeordnet wird ueber die TMDB-Kennung, die Jellyfin zu jedem Item mitliefert — dieselbe Kennung, auf der Flimmers Katalog ohnehin aufbaut. Eine unscharfe Suche ueber Titel und Jahr ist damit nicht noetig: die Zuordnung ist eindeutig oder sie findet nicht statt. Was Jellyfin ohne Kennung fuehrt, landet im Bericht statt falsch zugeordnet zu werden. Angemeldet wird mit Benutzername und Passwort, nicht mit einem API-Schluessel. Mit einem Schluessel liefert Jellyfin den Bestand ohne UserData, also ohne Gesehen-Status — und zwar ohne Fehlermeldung. Der Verbindungstest prueft deshalb ausdruecklich nach und sagt es, statt den Abgleich stumm ins Leere laufen zu lassen. Das Zugangstoken liegt mit AES-256-GCM verschluesselt in der Datenbank; abgeleitet aus AUTH_SECRET. Der Abgleich ist eine Einbahnstrasse: er ergaenzt, was auf dem Server liegt oder dort gelaufen ist, und loescht nie etwas, das in Flimmer steht. Fehlende Titel werden nur aufgenommen, wenn das Haekchen gesetzt ist — im taeglichen Lauf also nie. Entdoppelt wird zweifach: Episoden ueber (userId, episodeId, run), Filme ausdruecklich per Nachschlagen. Bei Filmen ist episodeId null, und Postgres behandelt NULL im eindeutigen Index als jeweils verschieden — der Index greift dort nicht. Watch.source haelt fest, was von wo kam. Ohne diese Angabe liesse sich spaeter nicht mehr sagen, welche Sichtung aus Jellyfin stammt. Umgesetzt sind Stufe 1 und 2. Stufe 3 (Zurueckschreiben) kommt in der Abnahmeliste nicht vor und braeuchte eine Versionsweiche, die sich ohne echten Server nicht verlaesslich pruefen laesst; das Feld writeBack ist angelegt, die Logik dahinter bleibt leer. Elf Tests gegen nachgebildete Antworten. Zusaetzlich der ganze Klickweg gegen einen echten HTTP-Server im Produktionsbau: verbinden, pruefen, abgleichen, zweiter Lauf ohne Dubletten, und der Warnfall ohne UserData. Refs #1
Serien und Filme tracken — selbst gehostet, ohne fremde Konten.
Episodenfortschritt, eigene Universen mit mehreren Reihenfolgen, Statistiken
und Benachrichtigungen, wenn es weitergeht.
Flimmer ist ein selbst hostbarer Tracker für Serien und Filme — benannt nach der Flimmerkiste und dem warmen Flimmern, das ein Bildschirm im dunklen Zimmer verbreitet. Metadaten kommen von TMDB, die Daten bleiben auf deinem Server.
Bebilderte Anleitung: docs/ANLEITUNG.md — führt einmal durch die ganze Anwendung. Schnittstelle: docs/API.md.
Was Flimmer kann
- Tracking — Serien und Filme mit Status (Schaue gerade, Pausiert, Abgeschlossen, Abgebrochen, Watchlist), Episodenfortschritt einzeln oder staffelweise, „+1 Episode"-Knopf, Rewatch-Zählung, Bewertung, Notizen, Tags.
- Universen — MCU, Star Wars, Wizarding World … beliebige Filme und Serien in einem Universum bündeln, mit mehreren Reihenfolgen (Erscheinung, Story-Chronologie, Einsteiger-Route) und Gesamtfortschritt.
- Metadaten von TMDB — Suche, Poster, Staffeln und Episoden, offizielle Collections, Streaming-Verfügbarkeit in deiner Region.
- Eigene Sammlung — markiere, was du schon hast, und wo: Jellyfin, Plex, NAS, Blu-ray. Gerade die Titel, die nirgends mehr streamen, findest du so wieder.
- Benachrichtigungen — bei neuen Episoden und Releases, per E-Mail, ntfy, Gotify oder Webhook (Discord/Slack-kompatibel), pro Nutzer einstellbar.
- Mehrere Nutzer — jede Person hat ihre eigene Bibliothek; Universen lassen sich teilen. Der erste registrierte Zugang wird Administrator.
- Übersicht, Kalender und Statistik — was als Nächstes dran ist, wann welche Episode läuft, und wie viel Zeit wohin geflossen ist.
- Import und Export — aus Trakt.tv oder CSV hinein, als CSV oder JSON hinaus.
- REST-API und PWA — dokumentierte Schnittstelle mit eigenen API-Schlüsseln, installierbar auf dem Startbildschirm, mit Offline-Rückfall.
- Papierkorb statt Löschen — 30 Tage Bedenkzeit, und jede Sichtung lässt sich mit einem Klick zurücknehmen.
Schnellstart
Voraussetzungen: Docker und Docker Compose.
git clone https://git.wilde-server.de/florian.wilde/flimmer.git
cd flimmer
cp .env.example .env
In der .env mindestens diese drei Werte setzen:
POSTGRES_PASSWORD=ein-langes-zufaelliges-passwort
AUTH_SECRET=noch-ein-langes-zufaelliges-geheimnis
APP_URL=https://flimmer.example.com
Geheimnisse erzeugen:
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
Dann starten:
docker compose up -d
Flimmer läuft auf http://localhost:3000. Der erste Zugang, der sich registriert, wird automatisch Administrator — lege ihn also gleich an. Danach lässt sich die Registrierung in der Verwaltung schließen.
TMDB-Schlüssel
Ohne TMDB findet die Suche keine Metadaten. Der Schlüssel ist kostenlos:
- Konto auf themoviedb.org anlegen
- Unter Einstellungen → API einen Schlüssel beantragen
- In die
.enveintragen unddocker compose up -derneut ausführen:
TMDB_API_KEY=dein-schluessel
TMDB_REGION=DE
TMDB_LANGUAGE=de-DE
Betrieb hinter einem Reverse Proxy
APP_URL muss auf die öffentliche Adresse zeigen — daraus werden Links in
Benachrichtigungen und der OIDC-Rücksprung gebildet. Als Healthcheck eignet
sich /api/health; der Endpunkt prüft auch die Datenbankverbindung.
Beispiel für Caddy:
flimmer.example.com {
reverse_proxy flimmer:3000
}
Aktualisieren
cd flimmer
./update.sh
Das Skript sichert zuerst die Datenbank, holt die Änderungen, baut das Image neu und wartet, bis die Instanz wieder antwortet. Schlägt etwas fehl, nennt es den konkreten Weg zurück — mit dem Befehl und der Sicherungsdatei, nicht bloß dem Rat, ein Backup zu haben.
./update.sh --ohne-backup ohne Datenbanksicherung
./update.sh --ohne-pull nur neu bauen und starten
./update.sh --behalte 20 wie viele Sicherungen aufgehoben werden (Standard: 10)
./update.sh --hilfe
Die Sicherungen landen unter backups/ und sind gzip-komprimierte pg_dumps.
Von Hand geht es genauso:
git pull && docker compose build && docker compose up -d
Datenbankmigrationen laufen in beiden Fällen automatisch beim Start des Containers — ein eigener Handgriff ist nicht nötig.
Backup
Alles, was zählt, steckt in der Datenbank; das Volume flimmer-data enthält
nur den Bild-Cache und lässt sich jederzeit neu aufbauen.
# Sicherung
docker compose exec -T db pg_dump -U flimmer -d flimmer --clean --if-exists \
| gzip > flimmer-$(date +%F).sql.gz
# Rückspielen
gunzip -c flimmer-2026-09-01.sql.gz \
| docker compose exec -T db psql -U flimmer -d flimmer
Für einen nächtlichen Lauf genügt ein Cron-Eintrag mit demselben Befehl. Wer zusätzlich den Bild-Cache sichern will:
docker run --rm -v flimmer_flimmer-data:/data -v "$PWD":/backup alpine \
tar czf /backup/flimmer-data.tar.gz -C /data .
Konfiguration
Sämtliche Einstellungen laufen über Umgebungsvariablen; die vollständige Liste
mit Erklärungen steht in .env.example. Die wichtigsten:
| Variable | Bedeutung |
|---|---|
APP_URL |
Öffentliche Adresse der Instanz |
AUTH_SECRET |
Geheimnis für Sitzungen — mindestens 32 Zeichen |
TMDB_API_KEY |
TMDB-Schlüssel für Metadaten |
TMDB_REGION |
Region für die Streaming-Verfügbarkeit (Standard DE) |
ALLOW_REGISTRATION |
Selbstregistrierung bei der Erstinbetriebnahme |
ENABLE_SCHEDULER |
Eingebauter Hintergrundlauf für Sync und Benachrichtigungen |
SMTP_* |
E-Mail-Versand |
OIDC_* |
Optionale SSO-Anbindung (Authelia, Keycloak, Authentik …) |
ENABLE_PASSKEYS |
Anmeldung per Passkey (Standard true) |
Passkeys
Statt Passwort geht auch ein Passkey — Windows Hello, Face ID, Touch ID oder ein Sicherheitsschlüssel. Anlegen unter Einstellungen → Passkeys, anmelden über „Mit Passkey anmelden" ohne vorherige E-Mail-Eingabe. Das Passwort bleibt daneben bestehen; den letzten verbleibenden Anmeldeweg lässt Flimmer nicht entfernen.
Drei Dinge, die beim Selbsthosten zählen:
- HTTPS ist Pflicht. Der Browser gibt Passkeys nur über eine verschlüsselte
Verbindung frei; einzige Ausnahme ist
localhost. Läuft deine Instanz über einfaches HTTP, verschwindet die Passkey-Verwaltung mit einem Hinweis, statt einen kryptischen Browserfehler zu zeigen. - Der Hostname kommt aus
APP_URL, nicht aus demHost-Header. Hinter einem Reverse Proxy mussAPP_URLalso stimmen, sonst hängen die Passkeys am falschen Namen. - Ein Domainwechsel entwertet alle Passkeys. Wer von
flimmer.fritz.boxaufflimmer.example.comumzieht, muss sie neu anlegen — sie sind fest an den Hostnamen gebunden. Halte vorher einen zweiten Anmeldeweg bereit.
Ein Passkey, der nur auf einem Gerät liegt (nicht synchronisiert), ist mit dem Gerät verloren. Die Übersicht in den Einstellungen sagt dazu, was Sache ist.
Entwicklung
npm install
docker compose -f docker-compose.dev.yml up -d # nur PostgreSQL
cp .env.example .env # DATABASE_URL auf localhost:5439
npm run db:migrate
npm run db:seed # Demo-Konten
npm run dev
| Befehl | Wirkung |
|---|---|
npm run dev |
Entwicklungsserver |
npm run build |
Produktionsbuild |
npm run typecheck |
TypeScript prüfen |
npm run db:migrate |
Migration erzeugen und anwenden |
npm run db:studio |
Prisma Studio |
npm run db:seed |
Beispieldaten |
npm test |
Testlauf gegen die Entwicklungsdatenbank (braucht keinen TMDB-Schlüssel) |
npm run icons |
Favicon und PWA-Icons aus dem SVG-Logo neu erzeugen |
npm run screenshots |
Screenshots der Anleitung neu aufnehmen |
Ohne TMDB-Schlüssel lässt sich die Suche mit einem lokalen Ersatz ausprobieren, der aus den Beispieldaten antwortet:
node --import tsx scripts/tmdb-stub.mjs 3099
TMDB_API_KEY=stub TMDB_BASE_URL=http://127.0.0.1:3099/3 npm run dev
Aufbau des Projekts:
src/
app/ Routen (App Router), API-Endpunkte, Server-Aktionen
components/ Wiederverwendbare Bausteine
lib/ Serverseitige Logik: DB, Auth, TMDB, Tracking, Statistik,
Universen, Benachrichtigungen, Abgleich, Import/Export
i18n/ Oberflächentexte
prisma/ Schema, Migrationen, Seed samt Beispielkatalog
tests/ Testlauf mit nachgebildeten TMDB-Antworten
public/brand/ Logo in allen Varianten
docker/ Entrypoint des Containers
docs/ Anleitung mit Screenshots, API, Design-Dokumentation
Automatisierung
Unter .gitea/workflows/ liegen zwei Abläufe für Gitea Actions: ci.yml
prüft bei jedem Push Typen, Tests und Produktionsbuild gegen eine echte
PostgreSQL-Instanz, release.yml legt beim Pushen eines Versions-Tags das
Release an — mit den Notizen aus releases/vX.Y.Z.md.
Hintergrundlauf
Einmal täglich frischt Flimmer laufende Serien bei TMDB auf, meldet neue
Episoden, prüft die Verfügbarkeit von Watchlist-Titeln und leert den
Papierkorb. Gesteuert wird das über ENABLE_SCHEDULER und SYNC_HOUR.
Wer lieber einen eigenen Cron benutzt, setzt ENABLE_SCHEDULER=false, vergibt
ein CRON_SECRET und ruft auf:
curl -X POST -H "Authorization: Bearer $CRON_SECRET" https://flimmer.example.com/api/cron/sync
In der Verwaltung lässt sich der Lauf jederzeit von Hand anstoßen.
Technik
Next.js (App Router) mit TypeScript, PostgreSQL über Prisma, Tailwind CSS.
Warum Next und nicht SvelteKit: Oberfläche, REST-API und Hintergrundläufe
stecken in einem Deployment, Server Components halten den Datenbankzugriff
serverseitig, und der standalone-Build ergibt ein schlankes Image ohne
Build-Werkzeuge. Für ein selbst gehostetes Werkzeug zählt vor allem, dass ein
einziger Container genügt.
Lizenz
MIT — siehe LICENSE.
Metadaten stammen von TMDB. Flimmer wird von TMDB weder betrieben noch unterstützt.
