diff --git a/AGENTS.md b/AGENTS.md index 2a8406f..7ee3a54 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -66,4 +66,21 @@ Bibliotheks-APIs: `--help`/Doku prüfen und die Fundstelle im Commit nennen. - ✅ **Etappe 18 (v3.11):** 4K-UHD gelöst — `makemkvcon` holt Schlüssel unter Linux nie, unter Windows schon; Schlüsselspeicher übernehmbar. Akira-UHD geht auf der VM auf (`TCOUNT:5`, bewiesen) +- ✅ **Etappe 19 (v3.14):** Durchsicht Frontend/Backend — vier Placebos weg + (Fortschritt log, Auswurf tat nichts, „Alle Tracks" konnte nichts, Encoder + wurden behauptet statt gemessen), Zombie-Erkennung gebaut, Pfad-Prüfung + gehärtet, und der Platten-Schutz aus `c065967` als **unwirksam** entlarvt - 📝 **Details immer in SAVEPOINT.md** — diese Sektion nennt nur die Etappe + +## Was diese Sitzungen wiederholt gekostet hat + +**Nicht aus einem Zustandswert auf einen Mechanismus schließen.** Vorgefallen: +aus „kein Schlüssel da" → „Server abgeschaltet" (falsch), aus Status +`transcoding` → „Celery hat neu zugestellt" (falsch), aus `progress=99` → +„Altwert aus dem Absturz" (falsch — ein Bug), aus gleichem `st_dev` → +„`os.rename` funktioniert" (falsch — der Kernel vergleicht den Mount). +Jedes Mal hätte eine Messung von unter einer Minute gereicht. + +**Und die Umkehrung gilt genauso:** gleiches `st_dev` heißt NICHT gleicher +Mount. Wo eine Eigenschaft ausprobierbar ist, probiere sie aus, statt sie +vorherzusagen. diff --git a/SAVEPOINT.md b/SAVEPOINT.md index f180bc5..12545fb 100644 --- a/SAVEPOINT.md +++ b/SAVEPOINT.md @@ -1,6 +1,167 @@ # SAVEPOINT — Rippy -## Aktueller Stand: v3.13 — ÜBERGABE (25.07.2026, 18:05) +## Aktueller Stand: v3.14 — Durchsicht: vier Placebos und ein unwirksamer Schutz (25.07.2026, 18:40) + +> Diese Sitzung hat nichts deployt (der Akira-Encode läuft — ein Rebuild +> schießt ihn ab). Alles unten ist committet und die Ampel ist grün. +> **Zuerst lesen: „SOFORT ENTSCHEIDEN" ganz unten.** + +### DER WICHTIGSTE FUND: der Platten-Schutz aus `c065967` greift nicht + +`_original_aufheben()` entschied per `os.stat().st_dev`, ob umgehängt oder +kopiert werden muss. Auf der VM gemessen — **beides gleichzeitig wahr**: + +``` +st_dev /app/temp = 2050 +st_dev /app/media = 2050 → identisch +os.rename(...) → EXDEV, "Invalid cross-device link" +``` + +Der Kernel vergleicht bei `rename()` den **Mount**, nicht das Gerät. +`/app/temp` (Docker-Volume) und `/app/media` (Bind-Mount) sind zwei Mounts +DERSELBEN ext4-Partition. Die Prüfung sah deshalb „gleiches Dateisystem", +**übersprang die Platzprüfung**, und `shutil.move` kopierte doch — 75 GB bei +37 GB frei. Der Schutz hätte genau den Schaden zugelassen, gegen den er +gebaut wurde. + +**Behoben:** `os.rename` wird jetzt VERSUCHT statt vorhergesagt. Klappt es, +ist es umgehängt. Kommt EXDEV, steht die Kopie fest — und erst dann wird der +Platz geprüft. Vier Tests dazu (`test_original_aufheben.py`), inklusive des +Falls, der die Platte füllte. + +### GEMESSEN, Stand 18:30 (alles per Befehl auf der VM geprüft) + +- **Container:** alle 5 `Up`, api/postgres/redis `healthy`. api/ui/worker seit + 18:00:39 (Deploy von `8bb075c`), postgres/redis älter. +- **Platte:** 148 G, 105 G belegt, 37 G frei = 75 G Rohschnitt + 4,9 G Medien + (Evangelion) + ~25 G System. **Keine Kopier-Reste**, kein `original`-Ordner. +- **Akira-Encode läuft:** HandBrake PID 40 seit 18:01:37, Preset + `H.265 MKV 2160p60 4K`, **3,84 von 4 Kernen**. Leseposition im Quellstrom + **1.145.940.149 von 79.604.951.639 Bytes = 1,44 %**. +- **Restlaufzeit ≈ 28–55 h.** Zwei Messfenster ergaben 395 und 784 KB/s — das + Tempo schwankt mit der Bildkomplexität, deshalb eine Spanne und keine Zahl. +- **Die Ursache dafür ist neu und behebbar:** Die VM läuft auf dem generischen + QEMU-CPU-Modell (`QEMU Virtual CPU version 2.5+`), `grep -c avx2 + /proc/cpuinfo` = **0**, nur bis `sse4_2`. x265 lebt von AVX2. Abhilfe: + CPU-Typ von VM 106 in Proxmox auf `host` stellen (braucht VM-Neustart). +- **HandBrake im Worker-Image kann:** `svt_av1`, `x264`, `x265` (je 10/12-bit), + `mpeg4/2`, `VP8/9`, `theora` — und **keinen einzigen Hardware-Encoder**. +- **Ampel grün** für `b526a0a` und `8bb075c` (Gitea-API abgefragt). + +### GEBAUT — vier Placebos entfernt bzw. echt gemacht + +1. **Fortschritt log statt Wahrheit.** `get_progress_from_line` matchte jede + Zahl vor einem `%` — also auch HandBrakes **Scan-Durchlauf**, der VOR dem + Encodieren bis 100 % hochläuft. Dazu warf `if progress > 0` alle echten + Werte unter 1,00 % weg. Ergebnis: Anzeige klebte stundenlang auf 99 %. + Jetzt wird nur die `Encoding:`-Zeile gelesen, `task N of M` mitgerechnet, + und **-1 heißt „keine Angabe"** (Muster von `get_progress_from_prgv`). + Formatstrings aus dem Binary gelesen, nicht geraten. + **⚠️ Richtigstellung zu v3.13:** Dort steht, `progress=99` sei ein „Altwert + aus dem Absturz". Falsch — beide Startpfade setzen auf 0, der Wert war + frisch vom Scan-Durchlauf geschrieben. Es war ein Bug, kein Überrest. +2. **„Automatischer Auswurf" tat nichts.** Die Einstellung (Standard: ein) + wurde von niemandem gelesen: DVD/Blu-ray warfen **nie** aus, Audio-CDs + **immer**, weil abcde `-x` fest verdrahtet bekam. Jetzt entscheidet die + Einstellung beides (`wirf_disc_aus()` per CDROMEJECT-ioctl, Linux-guarded). +3. **„Alle Tracks rippen" konnte nichts bewirken** — abcde bekommt keine + Track-Auswahl und es gibt keine Oberfläche dafür. Schalter entfernt, an + seiner Stelle steht jetzt die Wahrheit („wird immer vollständig gerippt"). +4. **Encoder-Auslese behauptete statt zu messen.** `cpu-x264`/`cpu-x265` + standen fest verdrahtet drin („immer dabei") — ein Rip-Worker **ohne** + HandBrake behauptete damit, komprimieren zu können. Und `vaapi` wurde + allein wegen `/dev/dri` gemeldet, ohne zu prüfen, ob HandBrake das kann + (dieses Image kann es nicht). Jetzt aus `HandBrakeCLI --help` geparst, plus + **CPU-Modell, Kernzahl und Vektorbefehlsstufe** je Worker — mit sichtbarer + Warnung im UI, wenn AVX2 fehlt. Genau die Angabe, deren Fehlen den + 55-Stunden-Encode unsichtbar machte. + +### GEBAUT — Zombie-Erkennung (v3.13 Punkt 2 abgearbeitet) + +`zombies.py`: Beim Worker-Start werden Jobs, die auf `ripping`/`transcoding`/ +`canceling` stehen, gegen Celerys `active`/`reserved`/`scheduled` gehalten und +ehrlich auf `failed` gesetzt, wenn niemand daran arbeitet. Drei Sicherungen, +weil ein falsch getöteter Job teurer ist als eine stehende Leiche: + +- **nur beim Start** (da ist „es lief nichts" eindeutig), +- **120 s Gnadenfrist** (Celery stellt unbestätigte Aufgaben erneut zu), +- **Vollzähligkeit**: antworten weniger Knoten als laut Herzschlag online + sind, wird NICHTS gewertet — sonst wäre der laufende Job eines + beschäftigten Remote-Workers eine falsche Leiche. + +13 Tests, unter anderem: „laufender Job wird nicht angetastet" und +„schweigender Worker verhindert jedes Urteil". + +### GEBAUT — Pfad-Prüfung gehärtet + +Elf Stellen prüften mit nacktem `startswith(MEDIA_ROOT)`. `/app/media-boese/x` +beginnt mit `/app/media`, liegt aber außerhalb — betroffen waren auch `/browse` +und `/browse/mkdir`, wo der Pfad vom Nutzer kommt. Neuer Zwillings-Helfer +`unter_wurzel()` in `api/main.py` und `worker/tasks.py`, alle elf Stellen +umgestellt, Tests in beiden. + +Nebenbefund dabei: `_zielbasis()` benutzte `os.path.normpath` — unter Windows +werden daraus Backslashes und die Prüfung greift nicht mehr; das gewählte Ziel +wäre still auf den Standard zurückgefallen. Genau die Falle, die +`_arbeitsverzeichnis()` drei Zeilen weiter dokumentiert und mit `posixpath` +vermeidet. Live war es nie (nur aus `rip_disc`, das auf Windows verriegelt +ist), jetzt konsistent. + +### GEFUNDEN, NICHT ANGETASTET — vier tote Endpunkte + +Nirgends im UI referenziert (mechanisch gegengeprüft: alle `api.*`-Aufrufe +gegen alle Routen): + +| Endpunkt | Lage | +|---|---| +| `POST /jellyfin/format` | ersetzt durch `medien.py` im Worker; zieht `nfo_generator.py` + `image_downloader.py` in der API mit, die sonst niemand nutzt | +| `POST /prescan` | ohne Aufrufer (die `PreScan`-Klasse selbst wird woanders sehr wohl gebraucht) | +| `GET /stream/jobs` | niemand konsumiert ihn; der Kommentar behauptet einen „Fix 23.07.", der ihn funktionsfähig machte — gebraucht wird er trotzdem nicht | +| `GET /worker-setup/windows-gui` | seit der `.exe` (v3.9) unreferenziert | + +Bewusst nicht entfernt: `test_api_smoke.py` prüft `/prescan` als verdrahtete +Route, und Entfernen ist eine Entscheidung, keine Reparatur. **Der Commander +entscheidet.** + +### NOCH OFFEN / EHRLICH UNGEKLÄRT + +- **Was die Platte am 25.07. mittags füllte, ist nicht belegt.** Es gibt keine + Kopier-Reste, kein `original`-Verzeichnis und **keinen einzigen Log-Eintrag + von `_original_aufheben`** — das loggt in beiden Zweigen. Zwischen 10:21:50 + und 17:49 steht überhaupt nichts im Log. Der EXDEV-Mechanismus ist jetzt + belegt (siehe oben), dass er DAMALS zuschlug, ist es nicht. +- **`tracks:/dev/sr0` meldet `{"status":"done","tracks":[]}`** — null Titel für + eine Disc, die MakeMKV mit `TCOUNT:5` öffnet. Nicht weiter verfolgt. +- **Der Discord-Webhook und der MakeMKV-Key liegen im Klartext** in der + settings-Tabelle. Für Heimnetz erwartbar, aber der Webhook ist eine echte + Zugangsberechtigung. +- **Zwei neue API-Tests laufen nur in der Ampel** — `test_api_smoke.py` + überspringt sich unter Windows selbst (`main.py` braucht `fcntl`). + +### SOFORT ENTSCHEIDEN + +**Der laufende Job wird beim Fertigwerden mit dem DEPLOYTEN (alten) Code +versuchen, 75 GB in 37 GB zu kopieren.** `keepOriginal` steht auf `true`, und +den Wert hat die Aufgabe beim Start um 18:01:37 gelesen — ihn jetzt umzustellen +ändert für diesen Lauf nichts mehr. Der Ablauf wäre: Platte läuft auf 100 %, +dann ENOSPC, dann räumt der `except`-Zweig die Teilkopie weg. Selbstheilend — +aber währenddessen ist die Systemplatte voll, und genau daran hat sich heute +Mittag der Worker-Container verschluckt. + +Die Optionen, in der Reihenfolge, die ich empfehle: + +1. **Encode abbrechen, deployen, Kompression neu anstoßen.** Kostet die + bisherigen ~30 Minuten (der Lauf ist bei 1,44 % — praktisch nichts) und + bringt sofort alle Fixes live, inklusive des Platten-Schutzes und der + ehrlichen Fortschrittsanzeige. Wer ohnehin über den CPU-Typ nachdenkt, + erledigt das im selben Fenster. +2. **Laufen lassen und in 1–2 Tagen deployen**, vorher die 75 GB von Hand + wegräumen oder auf die NAS schieben, damit die Kopie nicht anlaufen kann. +3. **Laufen lassen und den vollen Plattenlauf in Kauf nehmen.** + +--- + +## Vorheriger Stand: v3.13 — ÜBERGABE (25.07.2026, 18:05) > Dieser Block ist eine Übergabe an die nächste Sitzung. Er trennt bewusst > **gemessen** von **vermutet** — in dieser Sitzung wurden zwei Behauptungen