diff --git a/AGENTS.md b/AGENTS.md index f6c0ea8..f730710 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -83,6 +83,10 @@ Bibliotheks-APIs: `--help`/Doku prüfen und die Fundstelle im Commit nennen. nach Rippy, zeigt den laufenden Job und nimmt mehrere Aufträge an. Dazu **die Mount-Ursache**: Die CIFS-Verbindung lebt in der Netz-Namespace des api-Containers und stirbt mit ihm — eine Wache heilt das jetzt selbst +- ✅ **Etappe 23 (v3.19):** Sprachwahl vor dem Rip (Ton + Untertitel, Automatik + einstellbar), Auswurf wirkt wirklich (MakeMKV verriegelt die Tür), externer + Worker mit Verwaltungsfenster/Deinstaller/Slots, Schlüssel-Automatik für 4K, + Mount-Wiederanbindung 202 s → 8 s, Weitergabe an einem frischen Klon geprüft - 📝 **Details immer in SAVEPOINT.md** — diese Sektion nennt nur die Etappe ## Was diese Sitzungen wiederholt gekostet hat @@ -114,6 +118,13 @@ Sekunden später stirbt. Beide Fehler waren monatelang unsichtbar, weil der Code dem Rückgabewert glaubte. Nach einer Aktion den ZUSTAND fragen — und wenn er flattert, zweimal mit Abstand. +**Bei „zu langsam" die DAUER je Schritt messbar machen, nicht die plausibelste +Ursache beheben** (26.07.2026). Eine Mount-Wiederanbindung brauchte 150 s; die +erste, sehr plausible Erklärung war falsch, und die Änderung machte es langsamer +(202 s). Erst Zeitstempel im Log zeigten die Stelle: ein `os.makedirs` in Zeile +eins, das drei Minuten im Kernel hing. Danach 8 s. Wer eine Dauer nicht +aufschlüsselt, optimiert die falsche Stelle. + **Netz-Pfade nie ungebremst anfassen.** `os.path.isdir`/`open` auf einem toten CIFS-Mount blockieren im Kernel und lassen sich aus Python NICHT abbrechen. Ein Kind-Prozess lässt sich abbrechen: `timeout N ls -d ` (Muster in diff --git a/SAVEPOINT.md b/SAVEPOINT.md index 43625ea..36921a6 100644 --- a/SAVEPOINT.md +++ b/SAVEPOINT.md @@ -1,6 +1,254 @@ # SAVEPOINT — Rippy -## Aktueller Stand: v3.18 — Auswurf, externer Worker, und DIE Mount-Ursache (26.07.2026, Abend) +## Aktueller Stand: v3.19 — Sprachwahl, Verwaltungsfenster, Schlüssel-Automatik (26.07.2026, spät) + +> **Deployt und live gegengeprüft.** Sieben Commander-Meldungen, alle gemessen +> statt vermutet. Drei davon waren echte Fehler, die niemand gesehen hatte, und +> einer davon hat sich als Kette aus drei Ursachen entpuppt. + +### ZUSTAND, gemessen + +| | | +|---|---| +| Repo + VM | `848deb1`, Ampel **grün**, deployt | +| Tests | **269** grün (Sitzungsbeginn: 130) | +| Freigabe nach Deploy | heilt sich in **8 Sekunden** selbst (vorher 150–202 s) | +| Job `95afdc89` | `can_retry = true`, 74,1 GB erreichbar | +| Sprach-Scan an der Akira-BD | Ton `deu 4×, jpn 2×, und 4×` · Untertitel `deu 4×, und 8×` | + +### 1. AUSWURF: das ioctl meldete Erfolg und tat nichts + +Am laufenden System an der eingelegten Disc nachgestellt: + +``` +wirf_disc_aus("/dev/sr0") → True +Laufwerksstatus danach → 4 (Disc drin) +``` + +MakeMKV **verriegelt die Laufwerkstür** während des Rips (`CDROM_LOCKDOOR 1`) und +entriegelt sie nicht wieder. Ein verriegeltes Laufwerk quittiert `CDROMEJECT` +trotzdem mit Erfolg. Gegenprobe an derselben Disc: mit `CDROM_LOCKDOOR 0` davor +→ Status 2 (Schublade offen). Genau das macht das Werkzeug `eject` immer. + +Wichtiger als das Entriegeln: Das Ergebnis wird jetzt **geprüft statt geglaubt**. +Deshalb rutschte der Fehler in v3.14 durch — dort wurde richtig festgestellt, +dass die Einstellung von niemandem gelesen wurde; danach WURDE sie gelesen, +ausgeworfen wurde weiterhin nicht, und im Log stand „Disc ausgeworfen". + +**Live bewiesen** (API-Weg = der Knopf im UI): 0,67 s von „Disc drin" zu +„Schublade offen". Der automatische Weg nach einem Rip ist derselbe Code und +durch Tests gedeckt, aber nicht nochmal live gemessen — die Schublade lässt sich +per Software nicht wieder schließen (`CDROMCLOSETRAY` bleibt wirkungslos, +viermal versucht; das BU40N will von Hand zugeschoben werden). + +**Zweite Disc parallel** war nur daran gescheitert: `has_active_job` blockiert +ausschließlich bei `pending`/`running`, ein Job in `transcoding` gibt das Gerät +längst frei, und der Docker-Worker läuft ohne `--concurrency`, also mit 4 Slots. + +### 2. SPRACHWAHL VOR DEM RIP + +Die Auskunft lag längst vor und wurde weggeworfen: Derselbe Titel-Scan, der die +Titel-Tabelle füllt, liefert in derselben `makemkvcon`-Ausgabe die Streams mit. + +Format an der Akira-Blu-ray gemessen: + +``` +SINFO:,,,,"" +1 = Typ ("Audio"/"Subtitles") 3 = Sprachcode 4 = Sprachname +6 = Codec 14 = Kanäle 30 = Beschreibung +``` + +⚠️ **Die Falle:** Die Sprache steht in **3/4, NICHT in 28/29**. Die tragen auf +JEDEM Stream „eng"/„English" — auch auf einer deutschen Tonspur und auf dem +Videostream; das ist MakeMKVs eigene Anzeigesprache. Wer 28 nimmt, hält jede Disc +für englisch. Ein Test hält das fest. + +**Angewendet wird bei der KOMPRESSION, nicht beim Rippen** — drei Gründe: Der Rip +bleibt vollständig und verlustfrei (Muss-Feature laut KONZEPT); HandBrake hat +dafür dokumentierte Schalter (`--audio-lang-list` / `--subtitle-lang-list`, die +genau die ISO-639-2-Codes nehmen, die MakeMKV liefert — beides gegengeprüft); und +wer später andere Sprachen will, komprimiert neu statt die Disc wieder +einzulegen. Genau das sagt der Dialog auch, sonst glaubt man, es werde +unvollständig gerippt. + +Nichts angeklickt heißt „alles behalten". Auch die Einstellung ist bewusst LEER +vorbelegt: Ein stilles „deu" würde bei einem japanischen Original die +Originaltonspur wegwerfen, ohne dass jemand gefragt hat. Wunschsprachen für die +Automatik stehen unter Einstellungen → Verarbeitung. + +### 3. DER EXTERNE WORKER: Deinstaller, Verwaltungsfenster, Slots + +**Der Deinstaller lief nicht mehr** — reproduziert mit echtem PowerShell: + +``` +Der Typ [System.Windows.Forms.MessageBox] wurde nicht gefunden. +``` + +`Add-Type -AssemblyName System.Windows.Forms` stand **eine Zeile zu spät**. Der +Deinstaller starb in seiner ersten Arbeitszeile, jedes Mal. Zwei weitere Mängel +gleich mit: ASCII-Kodierung trotz Umlauten, und `Remove-Item -Recurse -Force +$PSScriptRoot` löscht den Ordner, in dem das laufende Skript liegt (klappt auf +Windows nicht zuverlässig — venv-DLLs sind geladen). Jetzt räumt ein losgelöstes +`cmd` nach, sobald PowerShell weg ist. Der erzeugte Deinstaller ist +gegengeprüft: parst, BOM da, Umlaute intakt. + +**Verwaltungsfenster** (Doppelklick aufs Tray): Status, Aufgaben, Log, plus +Knöpfe für Rippy, Log-in-Rippy und Deinstallieren — das geht damit auch aus dem +Tray-Menü. Eigener PROZESS statt Fenster im Tray, weil pystray und tkinter beide +den Haupt-Thread wollen; tkinter statt WinForms, weil es bei jeder +Windows-Python-Installation dabei ist. Headless gerendert und angesehen. Alles +Fachliche kommt von Rippy (`/capabilities`, `/jobs`), damit dort keine zweite, +abweichende Wahrheit steht. + +**Slots einstellbar.** Vorher fest `--pool=solo` = genau EIN Auftrag. Der +Installer fragt die Zahl jetzt, Vorbelegung ab 12 Kernen zwei, sonst einer: +HandBrake nutzt schon alle Kerne, aber x265 skaliert nicht linear. Auf Windows +gibt es keinen prefork-Pool (kein `fork`) — deshalb `--pool=threads`, was passt, +weil die Arbeit ein Kind-Prozess ist und der Thread nur wartet. Auf dem +Commander-PC erkennt der Installer 16 Kerne und schlägt 2 vor. + +**Log geht nach Rippy** (`logbruecke.py`, Quelle `w:`), die Logs-Seite hat +Knöpfe je Quelle. Durchgelassen wird wenig und mit Grund: Die Job-Meldungen +stehen längst in Rippy; gefehlt hat, was DANEBEN passiert (hochgefahren oder +nicht, Verbindung, Abstürze). Dazu eine Drossel (30 Zeilen/Minute), die MELDET, +wieviel sie verschluckt hat. Die lokale Datei bleibt — sie ist genau dann die +einzige Auskunft, wenn Rippy nicht erreichbar ist. + +### 4. SCHLÜSSEL-AUTOMATIK für 4K-UHD (auf Commander-Entscheid) + +`makemkvcon` unter LINUX ruft Disc-Schlüssel nie ab, die WINDOWS-Version schon. +Bisher Handarbeit: Laufwerk an den PC, Disc öffnen, `_private_data.tar` suchen, +im UI hochladen. Läuft jetzt von selbst — **zweischichtig, mit Absicht**: + +1. **Der Wächter (verlässlich):** sieht `_private_data.tar` nach und lädt sie zu + Rippy hoch, sobald sie sich geändert hat. Keine Laufwerkserkennung, nichts + geraten. Deckt auch ab, dass man die Disc einfach in MakeMKV öffnet. +2. **Das Anstoßen (nach bestem Wissen):** liegt eine Disc im Laufwerk, wird + `makemkvcon info` darauf losgelassen — dabei holt MakeMKV den Schlüssel. + +Warum getrennt: Das Format der BELEGTEN `DRV:`-Zeile ließ sich nicht messen (der +Commander-PC hat kein optisches Laufwerk — alle 16 Plätze melden +`DRV:i,256,999,0,"","",""`, das ist gemessen). Geraten wird also nur in Schicht 2, +und wenn die Vermutung falsch ist, passiert dort einfach nichts. **Die teure +Annahme steckt nie im verlässlichen Teil.** + +Gemessene Fundstellen: Datenverzeichnis ist `%USERPROFILE%\.MakeMKV` (NICHT +`%APPDATA%\MakeMKV`) — dort lag die echte Datei mit 6.420.480 Bytes. Programm: +`C:\Program Files (x86)\MakeMKV\makemkvcon64.exe`, v1.18.4. Die Automatik +schaltet sich ab, wenn MakeMKV fehlt: Auf einem reinen Encoding-PC gibt es nichts +zu holen. + +### 5. DIE 150 SEKUNDEN — drei Ursachen in einer Kette + +Der Commander: *„150 Sekunden für das Neu-Anbinden eines Mounts? Das ist verrückt +langsam."* Er hatte recht, und die erste Vermutung war falsch. Erst die +Zeitstempel im Log zeigten, wo die Zeit sitzt: + +``` +12:53:56 API gestartet +12:54:03 „antwortet nicht — wird neu verbunden" ← Erkennung: 7 s, gut +12:57:18 „eingehängt" ← Reparatur: 3 min 15 s +``` + +Die Reparatur hing in der **ersten Zeile** von `mounten()`: + +```python +os.makedirs(ziel, exist_ok=True) +``` + +`exist_ok` prüft mit `os.path.isdir`, und ein `stat` auf einen toten CIFS-Mount +blockiert im Kernel bis zum SMB-Timeout. Ausgerechnet der Aufruf, der „lege den +Ordner an, falls er fehlt" bedeutet, hing drei Minuten — **bevor** irgendeine der +sorgfältig begrenzten Prüfungen dran war. Dritter Fund derselben Sorte an einem +Tag: os-Aufruf auf einen Netzpfad ohne Zeitgrenze. + +Jetzt klärt `pfad_lage()` das mit einem abbrechbaren Kind-Prozess (`timeout 4 +ls -d`, drei Antworten: da / weg / unklar). Dieselbe Falle steckte in +`reparieren()` (`os.path.ismount` als Vorbedingung) und `aushaengen()` +(`ismount` + `rmdir`). Dazu: Wache prüft erstmals nach 3 s statt 60 s, benutzt +zum Erkennen die schnelle Einzelprobe, und die **Dauer steht jetzt im Log**. + +**Gemessen: 202 s → 8 s.** + +### 6. DER WIDERSPRUCH AUF DEM DASHBOARD + +Oben stand „Akira im Laufwerk erkannt", die Server-Status-Karte gleichzeitig +„Bereit — keine Disc in Arbeit / Disc einlegen". Zwei Aussagen, ein Blick. Die +Karte fragt jetzt `/devices` und sagt „Disc erkannt — wartet auf ‚Rippen +starten'" samt Titel. + +### 7. WEITERGABE GEPRÜFT — drei Lücken geschlossen + +Nicht durch Lesen, sondern indem ich mich wie ein fremder Rechner verhalten habe: +frischer `git clone` von Gitea in ein leeres Verzeichnis, dann +`./install.sh --nur-pruefen`. **2,6 MB, alles grün**, Laufwerk samt richtigem +sg-Knoten über die SCSI-Adresse erkannt. Der Weg trägt. + +Drei Lücken fielen auf: + +1. **In Beispielbefehlen stand meine IP.** `install.ps1` und + `remote-transcode-worker.yml` nannten 192.168.178.162 — genau die Zeile, die + ein Fremder kopiert. Jetzt Platzhalter, beim Windows-Installer mit dem Hinweis, + wo die richtige IP steht (und dass es NICHT die des eigenen PCs ist). +2. **Der Assistent fragte den MakeMKV-Beta-Key nicht.** Er stand in der README, + in der `.env` und in den Einstellungen — nur nicht dort, wo man beim + Einrichten hinsieht. Ein Fremder installiert, legt eine Blu-ray ein und + bekommt später einen Fehlschlag, ohne Hinweis. **Wahrscheinlichste + Stolperstelle einer frischen Installation.** Jetzt fragt der Assistent ihn ab, + mit dem Unterschied im Klartext: DVDs gehen ohne, Blu-ray braucht ihn — und er + ist NICHT der Disc-Schlüssel einer 4K-Disc. +3. Eine Docstring nannte die NAS-IP als Beispiel → neutral. + +Tests und Log-Beispiele behalten die echten Namen: Sie dokumentieren Messungen. + +### NOCH OFFEN + +**1. Der gemeinsame Weitergabe-TEST steht aus** — geprüft ist die Vorbereitung, +nicht der Durchlauf auf einem fremden Rechner. Ungetestet bleibt dabei weiterhin +`install.sh` **als root** (Verzeichnisse anlegen, `mount --make-rshared`, +systemd-Unit) — auf dieser VM sind alle root-Schritte No-Ops. + +**2. Der api-Container HÄLT die NAS-Verbindung** (Namespace-Befund aus v3.18). +Startet er mitten in einem Rip neu, verliert auch der Worker sein Ziel. Bis auf +Weiteres: nicht deployen, während ein Rip läuft. Sauber wäre ein Mount auf dem +HOST — eigener Umbau, widerspräche „Speicherziele über das UI". + +**3. Die Schublade der VM steht offen** und braucht einen Handgriff. + +**4. Der externe Worker läuft weiter mit altem Code.** Er braucht einen Lauf des +neuen Installers für: Pfad-Mapping, Preset-Meldung, Log-Brücke, Tray-Anzeige, +Slots, Verwaltungsfenster, Schlüssel-Automatik. Danach sind auch die +Live-Gegenproben möglich, die jetzt fehlen — mehrere Encodes gleichzeitig +(bisher nur die richtige Celery-Option, kein gemessener Durchsatz), die +Schlüssel-Automatik an einer echten UHD-Disc, und die belegte `DRV:`-Zeile. + +**5. Sprachauswahl live an einem echten Encode** — der Scan ist gegengeprüft +(Sprachen kommen korrekt an), die Anwendung in HandBrake noch nicht. + +**6. Unverändert offen:** Live-Gegenprobe der Metadaten-Kette (MyAnimeList war +die ganze Sitzung weg, HTTP 504); ISO-Sicherung und Mehr-Laufwerk-Betrieb; +Klartext-Geheimnisse in der settings-Tabelle; VM-CPU-Typ auf `qemu64`; kein +TypeScript-Typcheck. + +### FALLEN DIESER RUNDE + +- **Dreimal derselbe Fehlertyp an einem Tag:** ein `os`-Aufruf auf einen Netzpfad + ohne Zeitgrenze (`isdir` in der Rohdaten-Suche, `makedirs` im Mounten, + `ismount`/`rmdir` im Reparieren/Aushängen). Wer im Container einen Pfad unter + `/app/media` anfasst, nimmt einen abbrechbaren Kind-Prozess. +- **Meine erste Erklärung für die 150 s war falsch** (Wettlauf mit `umount -l`). + Die Änderung machte es sogar langsamer (202 s). Erst Zeitstempel im Log haben + die Stelle gezeigt. Lehre: bei „langsam" nicht die plausibelste Ursache + beheben, sondern die Dauer je Schritt messbar machen. +- **Ein Stub muss zur neuen Aufrufzahl passen.** Zwei Tests brachen, als eine + Prüfung zu zwei wurde bzw. ein `timeout ls` dazukam. Besser die HÖHERE Funktion + stubben als Aufrufe zählen. +- **`Add-Type` gehört VOR die erste Benutzung des Typs.** Klingt banal; hat den + Deinstaller monatelang komplett lahmgelegt, ohne dass es auffiel. + +--- + +## Vorheriger Stand: v3.18 — Auswurf, externer Worker, und DIE Mount-Ursache (26.07.2026, Abend) > **Deployt und live gegengeprüft.** Zwei Commander-Meldungen: Das Laufwerk geht > nach dem Rip nicht auf, und der externe Worker „ist ein bisschen dünn". Beide