CI / pruefen (pull_request) Canceled after 0s
Ein Passkey ist der dritte Weg in denselben Trichter: finishLogin() prueft die Antwort und gibt die Kennung zurueck, der Endpunkt legt damit ueber createSession() dieselbe Sitzung an wie bei Passwort und OIDC. Die Sitzungsschicht bleibt unangetastet. Die Trennung zwischen Pruefung und Sitzung ist Absicht: in passkeys.ts steht nur WebAuthn, das Setzen von Cookie und Sitzungszeile gehoert zur HTTP-Schicht. Die Challenge liegt in einem signierten, fuenf Minuten gueltigen httpOnly- Cookie statt in einer Tabelle — das spart Schema und Aufraeumjob fuer alles, was liegen bleibt, wenn jemand den Dialog abbricht. Die RP ID kommt aus APP_URL, nicht aus dem Host-Header. Hinter einem Reverse Proxy hinge sie sonst am falschen Namen, und ein Passkey ist fest an diesen Namen gebunden. Ueber nacktes HTTP verschwindet die Verwaltung mit einem Hinweis statt einen kryptischen Browserfehler zu zeigen. Den letzten verbleibenden Anmeldeweg laesst sich nicht entfernen: wer kein Passwort und keine OIDC-Verknuepfung hat, behaelt seinen letzten Passkey. Die Bibliothek prueft den Signaturzaehler selbst und wirft dabei gewoehnliche Error. Die werden jetzt uebersetzt, damit eine erwartete Ablehnung nicht als unerwarteter Serverfehler im Log landet. Die eigene Zaehlerpruefung bleibt als zusaetzliche Sicherung fuer den Fall, den die Bibliothek durchlaesst. Geprueft mit virtuellem Authenticator gegen einen Produktionsbau: anlegen, abmelden, Anmeldung allein per Passkey, Sitzungscookie gesetzt, Challenge-Cookie verbraucht, Passwortweg unveraendert. Mit kuenstlich hochgesetztem Zaehler wird die Anmeldung abgelehnt und der Zaehler bleibt stehen. Dazu sieben Tests fuer Verwaltung, Rechte und die Aussperrsperre. Refs #6
291 lines
10 KiB
Markdown
291 lines
10 KiB
Markdown
<p align="center">
|
|
<img src="public/brand/flimmer-logo.svg" alt="Flimmer" width="380">
|
|
</p>
|
|
|
|
<p align="center">
|
|
<b>Serien und Filme tracken — selbst gehostet, ohne fremde Konten.</b><br>
|
|
Episodenfortschritt, eigene Universen mit mehreren Reihenfolgen, Statistiken
|
|
und Benachrichtigungen, wenn es weitergeht.
|
|
</p>
|
|
|
|
---
|
|
|
|
**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](https://www.themoviedb.org/), die Daten
|
|
bleiben auf deinem Server.
|
|
|
|
<p align="center">
|
|
<img src="docs/screenshots/02-uebersicht.webp" alt="Die Übersicht von Flimmer" width="820">
|
|
</p>
|
|
|
|
> **Bebilderte Anleitung:** [docs/ANLEITUNG.md](docs/ANLEITUNG.md) —
|
|
> führt einmal durch die ganze Anwendung.
|
|
> **Schnittstelle:** [docs/API.md](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.
|
|
|
|
```bash
|
|
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:
|
|
|
|
```dotenv
|
|
POSTGRES_PASSWORD=ein-langes-zufaelliges-passwort
|
|
AUTH_SECRET=noch-ein-langes-zufaelliges-geheimnis
|
|
APP_URL=https://flimmer.example.com
|
|
```
|
|
|
|
Geheimnisse erzeugen:
|
|
|
|
```bash
|
|
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
|
|
```
|
|
|
|
Dann starten:
|
|
|
|
```bash
|
|
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:
|
|
|
|
1. Konto auf [themoviedb.org](https://www.themoviedb.org/signup) anlegen
|
|
2. Unter *Einstellungen → API* einen Schlüssel beantragen
|
|
3. In die `.env` eintragen und `docker compose up -d` erneut ausführen:
|
|
|
|
```dotenv
|
|
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:
|
|
|
|
```caddyfile
|
|
flimmer.example.com {
|
|
reverse_proxy flimmer:3000
|
|
}
|
|
```
|
|
|
|
## Aktualisieren
|
|
|
|
```bash
|
|
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_dump`s.
|
|
|
|
Von Hand geht es genauso:
|
|
|
|
```bash
|
|
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.
|
|
|
|
```bash
|
|
# 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:
|
|
|
|
```bash
|
|
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`](.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 dem `Host`-Header. Hinter
|
|
einem Reverse Proxy muss `APP_URL` also stimmen, sonst hängen die Passkeys am
|
|
falschen Namen.
|
|
- **Ein Domainwechsel entwertet alle Passkeys.** Wer von `flimmer.fritz.box` auf
|
|
`flimmer.example.com` umzieht, 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
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
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](LICENSE).
|
|
|
|
Metadaten stammen von TMDB. Flimmer wird von TMDB weder betrieben noch
|
|
unterstützt.
|