9230d0dd7a7a1e422ac6644dde439d21cf9fd03a
8 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
58f4991d92 |
fix(mounts): 3 min 15 s hingen in EINER Zeile - os.makedirs auf dem toten Mount
Ampel / ampel (push) Successful in 29s
Nachtrag, weil die letzte Runde die Wiederanbindung nicht schneller machte
(202 s statt 150 s). 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 "eingehaengt" <- Reparatur: 3 min 15 s
Die Erkennung war also schon schnell; die REPARATUR fraess die Zeit. Und zwar
nicht in den Mount-Versuchen, sondern in der ersten Zeile von mounten():
os.makedirs(ziel, exist_ok=True)
`exist_ok` prueft mit os.path.isdir, und ein `stat` auf einen toten CIFS-Mount
blockiert im Kernel bis zum SMB-Timeout. Ausgerechnet der Aufruf, der nur "lege
den Ordner an, falls er fehlt" bedeutet, hing drei Minuten - BEVOR irgendeine der
sorgfaeltig begrenzten Pruefungen dran war. Dritter Fund derselben Sorte an einem
Tag: os-Aufruf auf einen Netzpfad ohne Zeitgrenze.
Jetzt klaert `pfad_lage()` die Lage mit einem abbrechbaren Kind-Prozess
(`timeout 4 ls -d`, drei Antworten: da / weg / unklar), und makedirs laeuft nur
bei "weg". Dieselbe Falle in `reparieren()` (os.path.ismount als Vorbedingung -
gebraucht wird es nicht, `umount -l` auf einen leeren Pfad kostet nichts) und in
`aushaengen()` (ismount + rmdir).
Dazu steht die DAUER jetzt im Log ("neu verbunden (4.2s)"). Sie war die
entscheidende Spur; wer sie ablesen kann, muss sie nicht rekonstruieren.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
2a90538473 |
fix: Deinstaller, Verwaltungsfenster, Dashboard-Widerspruch, Mount-Tempo
Ampel / ampel (push) Successful in 30s
Vier Meldungen des Commanders, alle nachgemessen.
1. 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 SPAET, die
MessageBox wurde davor benutzt. Der Deinstaller starb also in seiner ersten
Arbeitszeile, jedes Mal. Zwei weitere Maengel gleich mit:
Kodierung war ASCII trotz Umlauten, und `Remove-Item -Recurse -Force
$PSScriptRoot` loescht den Ordner, in dem das laufende Skript liegt - das
klappt auf Windows nicht zuverlaessig (venv-DLLs sind geladen). Jetzt raeumt
ein losgeloestes cmd nach, sobald PowerShell weg ist. Der erzeugte Deinstaller
ist gegengeprueft: parst, BOM da, Umlaute intakt.
2. VERWALTUNGSFENSTER (Doppelklick aufs Tray). Zeigt Status, Aufgaben und Log,
plus Knoepfe fuer Rippy, Log-in-Rippy und Deinstallieren - Deinstallieren geht
damit auch aus dem Tray-Menue. 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 nicht eine zweite, abweichende Wahrheit steht.
3. DASHBOARD-WIDERSPRUCH. 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.
4. MOUNT-TEMPO. Der Commander: "150 Sekunden? Das ist verrueckt langsam." Recht
hat er. Gemessen ging die Zeit fast komplett in einen FEHLVERSUCH: `umount -l`
ist lazy, der Abbau passiert spaeter, und wer direkt danach mountet, riskiert
dass der Abbau hinter dem neuen Mount landet. Der erste Reparaturversuch
scheiterte dadurch regelmaessig - und der zweite kostete 30 s mount-Timeout
plus Pruefungen. Jetzt werden 1,5 s auf den Abbau gewartet, bevor neu
gemountet wird; die Wache prueft erstmals nach 3 s statt 10 s und benutzt zum
ERKENNEN die einfache schnelle Probe (die Doppelprobe steckt dort, wo der
Wettlauf lauert: direkt nach dem Mount).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
4cf7acbb96 |
fix(mounts): DIE URSACHE gefunden - die CIFS-Verbindung stirbt mit dem Container
Ampel / ampel (push) Successful in 31s
Der Savepoint v3.17 fuehrte das als "Ursache liegt beim NAS, nicht gefunden". Gemessen ist es etwas ganz anderes, und es liegt bei uns: /proc/fs/cifs/DebugData -> Net namespace: 4026532653 api-Container -> net:[4026532653] DIESELBE worker-Container -> net:[4026532540] andere Die CIFS-Verbindung lebt in der NETZ-NAMESPACE DES API-CONTAINERS - dort wird sie eingehaengt, weil nur dieser Container CAP_SYS_ADMIN hat. Wird der Container neu gebaut, stirbt sein Netz-Namespace und mit ihm der Socket. Der Mount steht danach weiter in /proc/mounts (per rshared auf den Host propagiert) und sieht vollkommen gesund aus - aber jeder Zugriff laeuft in den CIFS-Timeout. Damit erklaert sich alles, was vorher widerspruechlich aussah: warum es nach JEDEM Deploy passiert, warum `mount` Erfolg meldet, warum /proc/mounts genau eine korrekte Schicht zeigt, und warum nur ein echtes Neu-Verbinden hilft. Das NAS ist unschuldig (eine Sitzung, Status 1, 630 Credits, Ping 0,47 ms). Zweiter Fund, der den Rest erklaert: Direkt nach einem frischen Mount antwortete die Freigabe - und Sekunden spaeter nicht mehr. Das ist ein Wettlauf mit `umount -l`: lazy heisst, der Abbau passiert spaeter, und faellt er samt Propagation hinter den neuen Mount, zeigt der Pfad wieder auf die Leiche. Eine einzige Probe kann das nicht sehen - deshalb prueft `wirklich_erreichbar()` zweimal mit drei Sekunden Abstand, und zwar sowohl beim Mounten als auch in der Wache. Dazu: erste Pruefung der Wache schon nach 10 s statt 60 s. Genau dann ist die Lage nach einem Deploy kaputt. Ehrlich offen bleibt die strukturelle Folge: Der api-Container HAELT die NAS-Verbindung. Startet er mitten in einem Rip neu, verliert auch der Worker sein Ziel. Das saubere Gegenmittel waere ein Mount auf dem HOST statt im Container - ein eigener Umbau, und er widerspraeche "Speicherziele ueber das UI einhaengen". Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
87537bf3e5 |
fix(mounts): Ergebnis pruefen statt glauben - "mount" meldet Erfolg und liefert nicht
Ampel / ampel (push) Successful in 28s
Nachtrag zum vorigen Commit, weil der die Freigabe noch nicht zurueckbrachte. Dreimal reproduziert: Beim API-Start meldete `mount` Rueckgabewert 0, das Log schrieb "rippy: eingehaengt", /proc/mounts zeigte GENAU EINE korrekt aussehende Schicht mit den richtigen Optionen - und `timeout 6 ls /app/media/rippy` lief trotzdem in die Zeitgrenze. Derselbe Ablauf ein zweites Mal, per POST /storage-mounts/rippy/repair, stellte sie sofort her (30 s, danach erreichbar). Der erste SMB-Sitzungsaufbau kurz nach dem Container-Start geht also gelegentlich schief, ohne es zu melden. Ein Rueckgabewert von `mount` beweist deshalb nichts. Jetzt: Nach dem Mount wird geprueft, ob die Freigabe ANTWORTET (ist_erreichbar, harte Grenze). Wenn nicht, einmal loesen und neu mounten. Hilft auch das nicht, fliegt ein Fehler mit Klartext - dann steht im Log "FEHLER" statt "eingehaengt", was schlicht die Wahrheit ist, und der Nutzer bekommt den Hinweis auf die Reparatur-Funktion statt eines Rips, der spaeter still scheitert. Damit ist die Kette geschlossen: os.path.isdir kann nicht mehr im Kernel haengen (voriger Commit), die Rohdaten-Schleife stirbt nicht mehr daran, und ein Mount gilt erst als hergestellt, wenn er antwortet. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
a15623cc70 |
fix(mounts): Freigabe kam nach einem Rebuild nicht zurueck - Erreichbarkeit zuerst
Ampel / ampel (push) Successful in 28s
Bestandsfehler, beim Gegenpruefen dieser Sitzung aufgefallen und dreimal
reproduziert: Nach `docker compose up -d --build` war die CIFS-Freigabe TOT
(4 von 4 Zugriffen liefen in 10 s Timeout, /storage-mounts meldete
`reachable: false`) - und blieb es. Erst POST /storage-mounts/rippy/repair
stellte sie in einer Sekunde her.
Beim API-Start haette dasselbe passieren muessen. Warum nicht:
if os.path.ismount(ziel):
return schreibtest(ziel)
Der Mountpunkt existiert im NEUEN Container weiter (Bind-Mount vom Host), also
sagte ismount "ist schon da" und alle_remounten() brach genau hier ab. Dazu
oeffnet schreibtest() eine Datei OHNE Zeitgrenze - auf einem toten CIFS
blockiert das im Kernel, und der Start-Thread haengt dauerhaft (das waren die
zwei Threads im Zustand D, die diese Sitzung schon einmal gekostet haben).
Jetzt entscheidet ist_erreichbar() zuerst - das hat eine harte Grenze
(`timeout 3 ls`, seit 24.07. im Einsatz). Antwortet die Freigabe, sind ismount
und schreibtest danach gefahrlos. Antwortet sie nicht, faellt es durch auf
loesen + frisch mounten: derselbe Weg, den reparieren() geht, nur automatisch.
Fuer den Commander heisst das: Der NAS-Mount steht nach einem Deploy wieder von
selbst, statt still zu fehlen. Ohne das war jeder Rip auf die NAS nach einem
Update kaputt, ohne dass irgendwo etwas davon zu sehen war.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
4195854bf8 |
feat(bausteine): Preset-Liste, Pfad-Vorschlag und Restzeit - alles gemessen
Vier neue reine Bausteine, jeder mit Tests. Sie beantworten Fragen, die Rippy bisher geraten oder gar nicht gestellt hat. 1. caps.parse_preset_liste - die Preset-NAMEN, die das HandBrake DIESES Workers wirklich kennt (`--preset-list`, Format im Worker-Image gemessen). Damit endet das Raten: die Namen unterscheiden sich je HandBrake-Version, und ein erfundener Name laesst die Kompression scheitern. Richtigstellung zum SAVEPOINT v3.16: Dort galt es als unmoeglich, die Hardware-Preset-Namen auf der Rippy-VM zu ermitteln, weil dort kein Hardware-Encoder laeuft. Gemessen ist das falsch - die Kategorie `Hardware/` steht vollstaendig in der Liste (VCN, NVENC, QSV, MF). HandBrake trennt zwei Fragen: --preset-list nennt alle Presets, --help nur die nutzbaren Encoder. Zwei Fragen, zwei Quellen. 2. mounts.pfad_map_vorschlag/pfad_map_zeile - der fehlende Anschluss fuer RIPPY_PATH_MAP. Geraten werden muss dafuer nichts: Rippy hat die Freigabe selbst eingehaengt und kennt ihre Quelle (//host/share). Mountpunkt plus Quelle IST das Mapping. NFS gibt bewusst "" - Windows-Schreibweise ist nicht ableitbar (AGENTS Regel D). Der Test fand dabei sofort die dokumentierte Windows-Falle: os.path.join baute `/app/media\rippy` in einen Container-Pfad, das Mapping waere still wirkungslos geblieben. _mountpoint nutzt jetzt posixpath. 3. presets.empfehlung - "immer das Beste" (Commander-Anforderung) ohne Punktesystem: fuer jede Lage eine feste Reihenfolge echter Namen, genommen wird der erste, den der Worker kennt. Hardware nur, wenn die Familie wirklich gemeldet ist (vce -> Preset heisst VCN, sonst liefe eine AMD-Karte unter dem Intel-Namen). vaapi und MF werden nie empfohlen: fuer vaapi gibt es kein Preset, bei MF ist die Nutzbarkeit nicht ablesbar. 4. eta - Restzeit aus dem gemessenen Fortschritt. Messreihe je Phase (Rip und Kompression haben nichts miteinander zu tun), Stillstand verlaengert die Schaetzung, und unter zwei Messwerten oder 60 Sekunden Spanne gibt es ehrlich keine Aussage. Test mit den echten 25.07.-Werten: 1,44 % in 29 min landet bei "noch ca. 1 Tag 23 h" - genau die Angabe, deren Fehlen den 50-Stunden-Lauf unsichtbar machte. Dazu: HandBrakes "Invalid preset <Name>" wird uebersetzt. Vorher stand im UI nur "HandBrake endete mit Code 3" - dass der Preset-NAME das Problem ist, war daraus nicht zu erraten. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
6a082cf77c |
fix(mounts): mounten() idempotent - stale/tote Mounts vor Re-Mount loesen
Ampel / ampel (push) Successful in 28s
Folgefix zum remount-Vorfall 24.07.: mounten() fiel bei einem TOTEN Mount (os.path.ismount wirft OSError) auf den echten `mount` durch und stapelte auf die Leiche. Ueber viele Neustarts (via rshared propagiert, ueberlebt Container-Recreate) wuchs das auf 12 Schichten; die tote oberste blockierte jeden Zugriff (ls-Timeout, obwohl SMB-445 offen) -> Medien-Mount unbrauchbar. Fix: _stale_mounts_loesen(ziel) loest per lazy `umount -l` alle Schichten, bevor neu gemountet wird -> kein Stapeln mehr, Re-Mount idempotent. Ein gesunder Mount wird weiterhin frueh erkannt (os.path.ismount) und unangetastet gelassen. Tests (test_mounts_helpers.py): Loesch-Schleife bis leer (monkeypatch) + Verdrahtung. Ruff gruen. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
e032a9df1c |
feat(backend): Media-Server-Aufbereitung, echte Webhooks, SMB-Klartext, UHD-Arbeitsverzeichnis, MakeMKV-Key via UI
- jobs.meta (Migration in beiden db.py): Disc-Metadaten wandern in den Job —
Quelle fuer Job-Detail-Popup (GET /jobs/{id}/detail), Ordner-Benennung, NFO.
- medien.py (Worker, mit Tests): Zielordner 'Titel (Jahr)' statt Job-UUID;
movie.nfo/tvshow.nfo (Kodi-Schema, kodi.wiki/view/NFO_files) + poster.jpg
fuer jellyfin/emby/kodi; plex nur Benennung; Kollision -> Job-ID-Suffix.
- notify.py (identisch in API+Worker, mit Tests): Webhook bei Job-Ende —
Discord ({content}, discord.com/developers), Slack ({text},
api.slack.com/messaging/webhooks), ntfy (Rohtext + ?title=, docs.ntfy.sh),
generisches JSON. Vorher war das Setting ein Placebo: nichts sendete je.
POST /notifications/test beweist die Anbindung sofort.
- mounts.py: NT_STATUS-Fehler -> handelbarer Klartext (ACCESS_DENIED ohne
Credentials = Gast-Abfrage verweigert), Timeout-Meldung, mount-Hinweis
bei error(13). Mit Tests.
- tasks.py: Platz-Check per Disc-Groesse (ioctl BLKGETSIZE64) VOR dem Rip;
Arbeitsverzeichnis workDir (unter /app/media, z.B. NAS) statt fix
/app/temp/raw — 4K-UHD-Rohdaten (bis 100 GB) sprengen sonst die VM-Platte;
MakeMKV-Key aus UI-Settings (~/.MakeMKV/settings.conf, Format wie
entrypoint.sh) gilt ab dem naechsten Rip ohne Rebuild.
- caps.py: Werkzeug-Versionen (MakeMKV aus ENV MAKEMKV_VERSION im Image,
makemkvcon hat keinen --version-Schalter lt. usage.txt; HandBrakeCLI
--version lt. handbrake.fr/docs) + Key-Quelle; workers.info-Spalte.
- /browse liefert jetzt auch DATEIEN (Name + Groesse) — der 'leere'
bluray-Ordner war voll, der Browser zeigte nur Unterordner.
- GET /system/info: Versionen, Plattenplatz, Key-/Webhook-Status.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|