# Halo-Server Ein Medienserver, der dieselben Dinge kann wie Jellyfin — und zwei, die dort nicht gehen. **Stand: Stufe 1 von 5.** Es gibt Benutzer, Anmeldung, Einrichtung und das Gerüst. Einen Film spielt er noch nicht ab. Der Entwurf samt Begründungen steht in [`docs/superpowers/specs/2026-10-03-halo-server-design.md`](../docs/superpowers/specs/2026-10-03-halo-server-design.md). ## Warum **Download mit allen Tonspuren.** Jellyfins Transcode-Endpunkt bildet eine Tonspur je Sitzung ab; was offline landet, ist einspurig, und welche Spur es ist, entscheidet der Server. Einmal war es die Audiodeskription — drei Gigabyte Mobilfunk für eine Datei, die niemand hören wollte. **Fortschritt ohne Schwelle.** Jellyfin merkt sich unterhalb eines Mindestanteils gar nichts. Wer zehn Minuten sieht und abbricht, findet beim nächsten Mal keinen Anknüpfungspunkt. ## Betreiben ```bash cp docker-compose.example.yml docker-compose.yml # Pfade anpassen — jeder darin ist ein Beispiel, keiner stimmt bei dir. docker compose up -d ``` Dann `http://localhost:8080` öffnen; der Einrichtungsassistent legt das erste Administratorkonto an. Danach verschliesst sich dieser Weg von selbst. ### Einstellungen | Variable | Bedeutung | Voreinstellung im Image | |---|---|---| | `HALO_DATA` | Datenbank und Einstellungen. **Das Einzige, was gesichert werden muss.** | `/config` | | `HALO_CACHE` | Bilder und Zwischenstände. Darf verlorengehen. | `/cache` | | `HALO_TRANSCODE` | Kratzplatz fürs Umrechnen. Am besten auf eine SSD. | `/transcode` | | `ASPNETCORE_URLS` | Worauf gelauscht wird | `http://+:8080` | | `TZ` | Zeitzone | `UTC` | | `HALO_RATELIMIT_SIGNIN` | Anmeldeversuche je Minute und Adresse | `10` | | `HALO_RATELIMIT_POLLING` | Nachfragen je Minute und Adresse | `120` | **Medienpfade stehen hier nicht.** Sie werden in der Oberfläche als Bibliotheken angelegt und liegen in der Datenbank — sonst müsste der Container neu starten, um eine Bibliothek hinzuzufügen. ### Rechte Das Image läuft als `1000:1000`, **nicht als root**. Gehören die eingehängten Verzeichnisse jemand anderem, trage in der Compose-Datei `user: ":"` ein (`id -u` und `id -g` auf dem Wirt sagen, welche) oder übereigne sie mit `chown`. Stimmt etwas nicht, bricht der Server **beim Start** ab und nennt Variable, Pfad, seine eigene Kennung und den nächsten Schritt. Das ist Absicht: ohne diese Meldung stünde dort nur „unable to open database file". ### Hardware fürs Umrechnen `/dev/dri` durchreichen — VAAPI deckt Intel wie AMD ab. Fehlt es, rechnet Halo in Software; das ist langsamer, aber es läuft. **Zum blossen Umpacken wird gar keine Hardware gebraucht**, und das ist der Weg, der den meisten Beständen hilft. ## Entwickeln ```bash dotnet test # 55 Tests dotnet run --project src/Halo.Api # auf http://localhost:5000 ``` Ohne gesetzte Variablen legt der Server `data/`, `cache/` und `transcode/` neben dem Programm an. ```bash # Image bauen, beide Architekturen docker buildx build --platform linux/amd64,linux/arm64 -t halo:dev . # Eine Migration nach einer Modelländerung dotnet ef migrations add --project src/Halo.Data --startup-project src/Halo.Api --output-dir Migrations ``` Migrationen wandern **beim Start** des Servers, nicht über ein eigenes Kommando. ### Aufbau | Projekt | Aufgabe | |---|---| | `Halo.Core` | Domäne: Benutzer, Anmeldung, Bibliotheken. Kennt kein HTTP und keine Datenbank | | `Halo.Data` | EF Core, SQLite, Migrationen | | `Halo.Api` | Endpunkte, Authentifizierung, Auslieferung der Oberfläche | Was sicherheitsrelevant ist, liegt in `Halo.Core` — dort ist es ohne Server prüfbar, und die Tests dazu sind die, die am meisten wert sind. ### Zwei Entscheidungen, die beim Lesen Fragen aufwerfen **Zeitpunkte liegen als Unix-Millisekunden in der Datenbank, nicht als Text.** SQLite hat keinen Zeittyp; ein `DateTimeOffset` landet sonst als Text mitsamt Zonenversatz, und dann steht `…T12:00+02:00` neben `…T11:00Z` — derselbe Augenblick, als Text aber falsch herum sortiert. EF weigert sich deshalb, `ExpiresAt > jetzt` überhaupt zu übersetzen. **Token sind keine JWT.** Der Server schlägt sie nach, statt sie zu prüfen. Ein nachgeschlagenes Token lässt sich **sofort** widerrufen; ein signiertes gilt bis zum Ablauf weiter, und für ein verlorenes Telefon ist das der falsche Satz. In der Datenbank steht nur der Fingerabdruck. ## Anmeldung am Fernseher Ein Passwort auf der Apple-TV-Fernbedienung zu tippen ist eine Zumutung. Stattdessen: 1. Der Fernseher fordert an und bekommt einen **sechsstelligen Code** für den Bildschirm und einen **Abholschein**, den nur er kennt. 2. Am Telefon, angemeldet, wird der Code bestätigt — dort steht, welches Gerät. 3. Der Fernseher holt mit seinem Abholschein das Token ab. **Der Code allein holt nie ein Token ab.** Ohne den Abholschein wären sechsstellige Codes ein Türschloss mit einer Million Schlüsseln. ## Lizenz **GPL-2.0-only.** Abhängigkeiten müssen dazu passen: MIT, BSD oder GPL-2.0. **Apache-2.0 ist ausgeschlossen** — Einzelheiten und der Grund stehen in [`LICENSES.md`](../LICENSES.md).