Drei Praxis-Bugs: tote NAS-Mounts reparierbar, HandBrake-Versionen konsistent, Encoder-Wahl beim Rip
Ampel / ampel (push) Successful in 28s
Ampel / ampel (push) Successful in 28s
1) Speicher-Mounts robust (Befund: toter CIFS-Mount nach NAS-Ausfall/Rebuild —
mounted:false, verschwand aus 'Verfuegbare Ziele', Neu-Anlegen -> 409, man
sass fest):
- mounts.py: ist_erreichbar() (listdir, soft-Mount bricht schnell ab),
ist_gemountet() faengt OSError toter Mounts, aushaengen() mit
umount -l Fallback, reparieren() (lazy abhaengen + frisch mounten).
- /storage-mounts liefert 'reachable'; POST bei existierendem Namen:
aktiv -> 409, tot -> automatische Reparatur mit neuen Angaben;
neuer POST /storage-mounts/{name}/repair (gespeicherte Zugangsdaten).
- /storage-targets crasht nicht mehr an totem Mount (os.path.ismount
OSError abgefangen).
- UI: eigene 'Netzwerk-Mounts'-Liste mit Status (aktiv/nicht erreichbar/
getrennt) + Reparieren- und Entfernen-Knopf — tote Mounts sind sichtbar
und wiederherstellbar statt zu verschwinden.
2) HandBrake-Versionen konsistent (Befund: Docker 1.6.1, Windows-Skript
fest 1.9.2, Update-Check meldet 1.11.2 — verwirrend):
- Windows-Installer zieht jetzt DYNAMISCH die neueste Version (GitHub
latest, Fallback 1.11.2) — passt zum Update-Check.
- Update-UI erklaert klar: Docker = stabiles Debian-Paket (bewusst aelter,
kein Fehler), Windows = neueste. MakeMKV-Update zeigt den Befehl.
3) Encoder-/Worker-Auswahl beim Rip (Feature):
- Celery worker_direct=True: jeder Worker konsumiert zusaetzlich seine
Direkt-Queue. API-Helper transcode_queue(node) routet gezielt an den
gewaehlten Worker, faellt aber sicher auf die geteilte transcode-Queue
zurueck, wenn er offline ist (kein Haengenbleiben).
- /capabilities liefert den Celery-Node je Worker; POST /jobs nimmt
transcode_node (-> Job-meta); rip_disc + retry-transcode routen danach.
- Rip-Dialog: Encoder-/Worker-Dropdown, sichtbar ab 2 Online-Workern.
- ping_worker-Task zum Verifizieren des gezielten Routings.
- Nebenfund gefixt: DeviceDiscovery leitete die Titel-Auswahl (titles)
gar nicht an die API weiter — jetzt titles + transcode_node.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+51
-5
@@ -30,7 +30,27 @@ def validiere_name(name: str) -> bool:
|
||||
|
||||
|
||||
def ist_gemountet(name: str) -> bool:
|
||||
return os.path.ismount(_mountpoint(name))
|
||||
"""Ist unter /app/media/<name> etwas gemountet? Ein TOTER CIFS-Mount
|
||||
(NAS weg) lässt os.path.ismount mit OSError fliegen — das zählt weiter
|
||||
als 'gemountet' (nur eben kaputt), damit die Reparatur greift."""
|
||||
try:
|
||||
return os.path.ismount(_mountpoint(name))
|
||||
except OSError:
|
||||
return True
|
||||
|
||||
|
||||
def ist_erreichbar(name: str) -> bool:
|
||||
"""Kann auf das Ziel WIRKLICH zugegriffen werden?
|
||||
|
||||
Ein toter CIFS-Mount (Host weg, stale) ist zwar 'gemountet', aber jeder
|
||||
Zugriff scheitert mit „Host is down" (Befund 24.07.). CIFS läuft mit
|
||||
`soft`, d. h. der Zugriff bricht schnell ab statt zu hängen.
|
||||
"""
|
||||
try:
|
||||
os.listdir(_mountpoint(name))
|
||||
return True
|
||||
except OSError:
|
||||
return False
|
||||
|
||||
|
||||
def schreibtest(pfad: str) -> bool:
|
||||
@@ -169,22 +189,48 @@ def mounten(name: str, typ: str, quelle: str, optionen: str = "",
|
||||
pass
|
||||
|
||||
|
||||
def _lazy_umount(ziel: str) -> None:
|
||||
"""`umount -l`: hängt auch einen TOTEN/beschäftigten Mount ab (detach now,
|
||||
cleanup later). Ohne das ließ sich ein Mount zu einem weggefallenen NAS
|
||||
gar nicht mehr entfernen (Befund 24.07.)."""
|
||||
subprocess.run(["umount", "-l", ziel], capture_output=True, text=True, timeout=30)
|
||||
|
||||
|
||||
def aushaengen(name: str) -> None:
|
||||
ziel = _mountpoint(name)
|
||||
if os.path.ismount(ziel):
|
||||
try:
|
||||
noch_mount = os.path.ismount(ziel)
|
||||
except OSError:
|
||||
noch_mount = True # stale
|
||||
if noch_mount:
|
||||
ergebnis = subprocess.run(
|
||||
["umount", ziel], capture_output=True, text=True, timeout=30
|
||||
)
|
||||
if ergebnis.returncode != 0:
|
||||
raise RuntimeError(
|
||||
f"umount schlug fehl: {(ergebnis.stderr or '').strip()[:300]}"
|
||||
)
|
||||
# Toter/beschäftigter Mount → lazy detach (klappt immer)
|
||||
_lazy_umount(ziel)
|
||||
try:
|
||||
os.rmdir(ziel)
|
||||
except OSError:
|
||||
pass # nicht leer oder weg — egal
|
||||
|
||||
|
||||
def reparieren(name: str, typ: str, quelle: str, optionen: str = "",
|
||||
username: str = "", passwort: str = "") -> bool:
|
||||
"""Toten/veralteten Mount frisch neu verbinden: lazy abhängen, neu mounten.
|
||||
|
||||
Nötig, wenn ein NAS-Mount stale geworden ist (Rebuild, NAS-Schlaf) — das
|
||||
normale mounten() würde am „ist schon Mountpoint" hängenbleiben.
|
||||
"""
|
||||
ziel = _mountpoint(name)
|
||||
try:
|
||||
if os.path.ismount(ziel):
|
||||
_lazy_umount(ziel)
|
||||
except OSError:
|
||||
_lazy_umount(ziel)
|
||||
return mounten(name, typ, quelle, optionen, username, passwort)
|
||||
|
||||
|
||||
def alle_remounten() -> list:
|
||||
"""Beim API-Start: alle gespeicherten Mounts wiederherstellen."""
|
||||
meldungen = []
|
||||
|
||||
Reference in New Issue
Block a user