diff --git a/AGENTS.md b/AGENTS.md index f730710..e8f861b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -87,6 +87,11 @@ Bibliotheks-APIs: `--help`/Doku prüfen und die Fundstelle im Commit nennen. 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 +- ✅ **Etappe 24 (v3.20):** Rippy bremste sich selbst aus — das Rate-Limit lag + unter der eigenen Last (100/min gegen 123/min), hinter dem Proxy teilten alle + Clients einen Eimer, und ein abgewiesener Abruf leerte das UI. Dazu: der + „Neu"-Knopf kennt jetzt die Phase (Rip oder Kompression) und fragt, wo er es + nicht weiß; zwei Fehler im Deploy-Weg behoben - 📝 **Details immer in SAVEPOINT.md** — diese Sektion nennt nur die Etappe ## Was diese Sitzungen wiederholt gekostet hat @@ -131,3 +136,37 @@ Kind-Prozess lässt sich abbrechen: `timeout N ls -d ` (Muster in `mounts.ist_erreichbar` und `rohdaten.verzeichnis_da`). Und „konnte nicht nachsehen" ist etwas anderes als „ist nicht da" — beides zu vermischen erzeugt falsche Aussagen im UI. + +**Ein verpasster Abruf ist keine Nachricht über die Welt** (26.07.2026). Im UI +stand fünfmal `catch(() => [])`: Jeder fehlgeschlagene Abruf hieß damit „es gibt +keine Jobs, keine Laufwerke, keine Ablagen" — die Liste leerte sich für einen +Takt und füllte sich vier Sekunden später wieder. Der Commander meldete das als +„wird oft neu geladen", und die Ursache war unsichtbar, weil der Fehlerzweig +nichts protokollierte. Wer nichts Neues weiß, behält, was er wusste: bei +Fehlschlag `null` und den alten Stand stehen lassen — nie einen leeren Wert, der +als Aussage gelesen wird. + +**Eine eigene Schutzbremse gegen die eigene Last rechnen** (26.07.2026). Das +Rate-Limit stand auf 100 Anfragen/min, während ein einziger offener Tab 111/min +verursacht (Dashboard 75 + Log-Kasten 24 + Laufwerke 12). Rippy bremste sich +also permanent selbst aus, und niemand sah es: Der 429 stand in keinem Log, und +das UI verbuchte ihn als Leermeldung. Dazu der zweite Fehler — hinter einem +Reverse-Proxy ist `request.client.host` IMMER der Proxy, also hatten Browser, +zweiter Tab und Windows-Tray EINEN gemeinsamen Eimer (812 von 876 Anfragen kamen +scheinbar von einer IP). Wer eine Grenze setzt, rechnet die eigene Grundlast vor, +schreibt sie als Kommentar dazu und lässt jedes Greifen protokollieren. + +**Eine geschluckte Warnung ist eine Falle** (26.07.2026). `cp "$ENV_SRC" .env +2>/dev/null || echo "WARNUNG: …"` scheiterte auf der Ziel-VM bei JEDEM Deploy, +weil die .env dort anders lag. Die Zeile scrollte im Build-Rauschen vorbei, +gebaut wurde still mit einer zwei Tage alten Kopie — mit einem toten +Download-Notbehelf darin, an dem jeder worker-Build abbrach. Entweder abbrechen +oder so laut werden, dass es nicht zu übersehen ist (Dateidatum, Kandidatenliste); +ein `|| echo` in einem 200-Zeilen-Log ist keins von beidem. + +**Wenn der Commander eine Korrelation nennt, ist das eine Spur.** „Wenn der +Worker installiert ist, wird der Bereich oft neu geladen" klang nach Bauchgefühl +und war exakt richtig: `tray.py` fragt `/api/jobs` über Port 80, landet damit im +Rate-Limit-Eimer des Browsers und drückt ihn über die Grenze. Dieselbe Lehre wie +bei „auf Windows ginge das sofort" (Disc-Schlüssel) — die Beobachtung ernst +nehmen, auch wenn die vermutete Erklärung („Celery-Ping?") daneben liegt. diff --git a/SAVEPOINT.md b/SAVEPOINT.md index 36921a6..4f2c43d 100644 --- a/SAVEPOINT.md +++ b/SAVEPOINT.md @@ -1,6 +1,150 @@ # SAVEPOINT — Rippy -## Aktueller Stand: v3.19 — Sprachwahl, Verwaltungsfenster, Schlüssel-Automatik (26.07.2026, spät) +## Aktueller Stand: v3.20 — Rippy bremste sich selbst aus (26.07.2026, Nacht) + +> **Zwei Commander-Meldungen, eine gemeinsame Wurzel: Rippy behinderte sich +> selbst und schwieg darüber.** Dazu zwei Fehler im Deploy-Weg, die jeden +> `worker`-Build zum Absturz brachten — gefunden, weil ich selbst darüber fiel. + +### ZUSTAND, gemessen + +| | | +|---|---| +| Repo + VM | `9156e8a`, Ampel **grün**, deployt | +| Tests | **289** grün (Sitzungsbeginn heute: 130) | +| Rate-Limit-Eimer | PC bei 590, VM gleichzeitig bei 599 — **getrennt** (vorher einer für alle) | +| Anfragen des Dashboards | 75/min → **30/min** | +| Phasen-Marke | live gesetzt: `rip_fertig: false` beim Rip-Start | + +### 1. „Wird oft neu geladen" — es lud gar nichts neu + +Erst gemessen, dann geglaubt. Über zwanzig Sekunden: + +``` +/jobs byteweise IDENTISCH über 5 Abfragen +/capabilities byteweise IDENTISCH über 5 Abfragen +alle Endpunkte ≤ 30 ms +``` + +Es wurde also nichts neu geladen — es wurde **geleert**. Im nginx-Log standen 97 +Antworten mit **HTTP 429**. Drei Fehler griffen ineinander: + +**(a) Die Bremse lag unter der eigenen Last.** `MAX_REQUESTS_PER_MINUTE = 100`, +während ein einziger offener Tab verursacht: + +| Taktgeber | Rechnung | pro Minute | +|---|---|---| +| Dashboard | 5 Endpunkte alle 4 s | 75 | +| Log-Kasten | 2 Endpunkte alle 5 s | 24 | +| Laufwerks-Suche | 1 Endpunkt alle 5 s | 12 | +| Windows-Tray | `/jobs` alle 5 s | 12 je Worker | +| | **Summe** | **123** | + +**(b) Alle Clients teilten einen Eimer.** Hinter dem nginx ist +`request.client.host` immer der Proxy: 812 von 876 Anfragen kamen scheinbar von +`172.19.0.6`. Der nginx gab die echte Adresse nicht weiter. + +**Das ist die Erklärung für die Kopplung an den Worker**, die der Commander +gesehen hat: [`tray.py`](docker/worker/tray.py) fragt `http:///api/jobs` — +über Port 80, also durch denselben Proxy. Der Tray zahlte aus dem Geldbeutel des +Browsers. Seine vermutete Ursache („Celery-Ping?") lag daneben, seine Beobachtung +war exakt richtig. + +**(c) Ein abgewiesener Abruf leerte das UI.** Fünfmal stand im Dashboard +`catch(() => [])`. Das heißt „es gibt keine Jobs" — gemeint war „ich weiß gerade +nichts Neues". Für einen Takt stand „Keine Jobs in diesem Tab", die Zähler +sprangen auf (0), vier Sekunden später war alles zurück. + +Behoben: `X-Real-IP` im nginx, Grenze auf 600/min **mit vorgerechneter +Herleitung im Quelltext**, ein Test hält die Grenze gegen die eigene Last fest, +jedes Greifen steht im Log (gedrosselt auf eine Meldung pro Client und Minute), +`null` statt `[]` bei Fehlschlag, 20-s-Zeitgrenze für axios, und das Dashboard +trennt schnelle Daten (Jobs/Laufwerke, 4 s) von langsamen (Hardware/Worker/ +Ablagen, 12 s). + +**Bewiesen:** fünf Anfragen vom PC → `X-RateLimit-Remaining` 594→590; die VM +durch denselben nginx gleichzeitig bei 599/598. Zwei Clients, zwei Eimer. + +### 2. „Der Button ‚Neu' — WAS wird da gemacht?" + +Immer die Kompression. Auch bei einem Job, dessen **Rip** abgebrochen war: Am +Nachmittag lagen 4,8 GB von rund 40 GB da, und „Neu" hätte daraus brav einen Film +komprimiert, der bei 12 % aufhört. + +Das Problem war nicht der Knopf, sondern fehlendes Wissen: Sobald `status = +"failed"` in der Zeile steht, ist die Phase fort — die Spalte hat nur einen Wert. +`zombies.war_im_rip()` sieht sie im Moment des Aufräumens, die API später nie. + +Also wird sie vermerkt ([`phasen.py`](docker/api/phasen.py)): + +| Zeitpunkt | Marke | +|---|---| +| Rip-Start | `rip_fertig = false` | +| Rip fertig, Kompression eingereiht | `rip_fertig = true` | +| Start eines Transcodes | `rip_fertig = true` (heilt Bestandsjobs) | + +Daraus folgen **drei** Antworten, nicht zwei: + +- `transcode` → **„Neu komprimieren"** · Disc bleibt draußen, Stunde gespart +- `rip` → **„Neu rippen"** · `POST /jobs/{id}/retry-rip`, neuer Job mit neuer ID +- `unklar` → **Dialog**, der beide Wege erklärt und die Rohdaten-Größe als + Entscheidungshilfe nennt („eine Blu-ray bringt roh 25–45 GB mit") + +Der dritte Fall ist der Grund, warum nicht geraten wird: Bestandsjobs tragen die +Marke nicht, und dem Commander an einem Job mit 74 GB intakter Rohdaten das +Komprimieren wegzunehmen wäre genauso falsch wie ein Bruchstück anzubieten. + +Warum ein **neuer** Job und nicht der alte wiederbelebt: Das Roh-Verzeichnis +heißt `/`. Bei gleicher ID läge das alte Bruchstück +im neuen Verzeichnis, und die Kompression sammelt am Ende ALLE MKV-Dateien darin +ein — sie würde es mitverarbeiten. + +### 3. Zwei Fehler im Deploy-Weg (gefunden, weil ich selbst darüber fiel) + +`./deploy.sh` ohne Argumente — der dokumentierte Normalfall — endete mit +`line 5: $4: unbound variable`. **Leere Argumente überleben ssh nicht:** Die +Gegenseite bekommt die Befehlszeile als EINEN String und parst sie neu, `""` +verschwindet dabei ersatzlos. Jetzt `${4:-}`. + +Danach brach jeder `worker`-Build am MakeMKV-Download ab. Ursache war nicht der +Download, sondern eine **geschluckte Warnung**: Die echte `.env` liegt auf der VM +unter `~/projects/rippy/.env`, der Default zeigte auf `~/rippy/.env`. Das `cp` +scheiterte bei jedem Deploy, die Warnung scrollte im Build-Rauschen vorbei, und +gebaut wurde mit der Kopie im Klon — zwei Tage alt, darin ein +`MAKEMKV_URL_BASE`-Notbehelf auf einen web.archive.org-Schnappschuss. Der liefert +inzwischen **525**, während `makemkv.com/download` wieder **200** gibt. Der +Notbehelf von vorgestern war die Ursache von heute. + +Jetzt: `.env` übernommen → es steht da. Quelle fehlt, Klon hat eine → laute +Warnung mit Dateidatum und Kandidatenliste. Keine von beiden → Abbruch, statt mit +leerem DB-Passwort zu bauen. + +**Für die nächste Sitzung:** +``` +RIPPY_VM=arcane@192.168.178.162 \ +RIPPY_REPO_URL= \ +RIPPY_ENV_SRC=/home/arcane/projects/rippy/.env ./deploy.sh +``` + +### 4. Was noch offen ist + +- **Voller Durchlauf läuft gerade** (Job `bfb7a946`, über den neuen + `retry-rip`-Weg gestartet): Akira-BD → Auswurf → Kompression auf + `tobisnicerpc` mit Wunschsprache `deu` (Ton **und** Untertitel). Das ist die + Kette, die noch nie komplett durchlief — Sprachwahl war bisher nur bis zum + Scan belegt, nie durch einen echten Encode. +- **Job `2182d525` trägt einen falschen Fehlertext.** Das Aufräumen lief, bevor + der phasenbewusste Text deployt war; dort steht noch „mit ‚Neu komprimieren' + läuft die Kompression erneut" für einen Rip, der bei 12 % starb. Der Knopf + fragt inzwischen nach, der Text im Detail-Popup lügt weiter. Der Eintrag samt + 4,8-GB-Bruchstück kann über den Papierkorb weg — der Neu-Rip ersetzt ihn. +- **`sudo ./install.sh` auf einem FRISCHEN Host ist weiter unbelegt.** Der + Prüfpfad (`--nur-pruefen`) läuft auf der VM sauber durch, alle fünf Prüfungen + grün. Ein echter Root-Lauf hier hätte den laufenden Rip getötet. + +--- + +## Vorheriger 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