RuleRunner prüft Anbindungen vor jedem Lauf, baut das Ziel auf und führt die Engine im Worker mit Inaktivitäts-Watchdog aus. Der Lock wird erst beim echten Ende des Workers freigegeben. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
7.1 KiB
7.1 KiB
Devlog
Arbeitsprotokoll mit Zeiterfassung. Ein Eintrag pro Arbeitsblock, neueste oben. Zeiten in Ortszeit (Europe/Berlin).
Zeitübersicht
| Datum | Von | Bis | Dauer | Schritt | Thema |
|---|---|---|---|---|---|
| 2026-10-10 | 17:21 | 17:27 | 0:06 | 1 | Gerüst, Datenmodell, Migrationen |
| 2026-10-10 | 18:35 | 18:48 | 0:13 | 2 | Mount-Schicht, Validierung, SFTP |
| 2026-10-10 | 18:53 | 19:07 | 0:14 | 3 | Mover-Logik |
| Summe | 0:33 |
2026-10-10 · 18:53–19:07 · Schritt 3: Mover-Logik
Erledigt
- Remote
origin(Gitea) war eingerichtet →maingepusht. mover/scan.py,naming.py,ledger.py,destinations.py(lokal/Kernel-Mount und SFTP),engine.py(Ablauf pro Lauf),runner.py(Lock, Lebenszeichen, Watchdog, Ziel-Aufbau).timeouts.run_with_watchdog: Inaktivitäts-Zeitlimit mitCancelToken.- 60 neue Tests (183 gesamt), u. a. Abbruch in jeder Phase, Wettlauf um Dateinamen, hängender Lauf, SFTP-Ziel mit Kollision gegen echten SFTP-Server.
- Mutationsprobe: 9 eingebaute Fehler (keine Stabilitätsprüfung, kein fsync, keine Quellprüfung, Wettlauf, direkt unter Endnamen schreiben, keine Buchführung, keine Platzprüfung, Lock-Freigabe, kein Watchdog). Zwei überlebten zunächst (fsync, enges Wettlauf-Fenster) → zwei Tests ergänzt; jetzt alle erkannt.
Entscheidungen
- Zeitlimit als Inaktivitäts-Watchdog statt Gesamtlaufzeit: große Dateien über langsame Leitungen werden nicht abgebrochen, hängende Zugriffe schon.
- Regel-Lock gibt erst der Worker selbst frei. Nach einer Zeitüberschreitung bleibt die Regel blockiert, bis der hängende Worker endet – so kopieren nie zwei Läufe dieselbe Datei gleichzeitig. Statusseite zeigt „läuft seit …“.
- Stabilität innerhalb eines Laufs (zweite Messung nach dem Fenster) plus Quellprüfung direkt vor dem Umbenennen.
- Umbenennen ohne Überschreiben per
link()(atomar, EEXIST bei Kollision); ohne Hardlink-Unterstützung (oft CIFS) Rückfall auf „prüfen + rename“. - Bei Rekursion wird die Ordnerstruktur im Ziel beibehalten; Trennung nach Quell-Unterordnern geht über mehrere Regeln mit eigenem Unterpfad.
- Quarantänepfad ist relativ zur Quell-Anbindung (gleiches Dateisystem → rename).
copied_fileswird nach jedem vollständigen Scan bereinigt (statt nur beim Protokoll-Aufräumen) – zeitnäher, und nie nach einem abgebrochenen Scan.- ESTALE/EIO/ENOTCONN mitten im Lauf → Lauf abbrechen (nicht jede Datei einzeln scheitern lassen, nicht gegen den Quarantäne-Zähler zählen).
Stolpersteine
tmp_pathliegt auf einem Gerät → Tests liefen erst über den Schnellweg (link) statt über den Kopierweg; Fixturecross_deviceerzwingt ihn.- Prüfung „lokales Ziel nicht unter Mount-Basis“ nutzte nur
resolve()– ein Symlink umgeht das. Jetzt wörtlich und aufgelöst. - Lock-Übergabe an den Worker: doppelte Freigabe bei sehr schnellem Lauf (beim Gegenlesen gefunden, vor dem ersten Testlauf behoben).
2026-10-10 · 18:35–18:48 · Schritt 2: Mount-Schicht, Validierung, SFTP
Anforderungsänderung (Rücksprache)
- Ziel ist voraussichtlich ein LXC, der nur per SSH erreichbar ist → neuer
Anbindungstyp
sftp(nur als Ziel). Host: VM mit Debian 13 empfohlen. /healthzzählt nur aktive Anbindungen; Python ≥ 3.12 (Debian 13: 3.13).
Erledigt
validation.py: strikte Muster für Server/Freigabe/Pfade, Options-Allowlist pro Typ, gesperrte Schlüssel, Werte gegen^[A-Za-z0-9._:/@-]+$.crypto.py: Fernet-Schlüssel (0600, O_EXCL), Ed25519-Schlüsselerzeugung, Schwärzung.mounting/:MountBackend,LinuxMountBackend,FakeMountBackend,mountinfo-Parser (Oktal-Escapes, übereinanderliegende Mounts),MountManager(einhängen, aushängen, Wiederherstellung beim Start, Test, Lebenszeichen, Wiederherstellung), SFTP-Anbindung mit TOFU-Hostschlüssel.timeouts.py: Zugriffe in Daemon-Threads mit Zeitlimit (kein Pool, damit hängende Threads nichts blockieren).- 113 neue Tests (123 gesamt), u. a. gegen einen echten SFTP-Server im Prozess.
- Mutationsprobe: 5 absichtlich eingebaute Fehler (Credentials-Datei bleibt liegen,
fehlende ismount-Prüfung, kein
umount -l, nicht-leerer Mountpoint, gesperrte Optionen) werden alle von Tests erkannt.
Entscheidungen
- Migration 0001 angepasst statt 0002 – noch kein Release, keine Datenbank im Einsatz. Ab v0.1.0 sind veröffentlichte Migrationen unveränderlich.
- Mount verschwunden (nicht eingehängt) → Lauf abbrechen, kein automatisches Neu-Einhängen (Fallstrick 2). Gestört (ESTALE & Co.) → Neu-Einhängen (Fallstrick 13).
- Root-Squash-Auswertung als reine Funktion (
assess_write), damit testbar ohne root und ohne NAS. statvfs/fsyncper SFTP über die OpenSSH-Erweiterungen; fehlen sie, gibt es einen Hinweis statt eines Fehlers.- Ruff-Regel
PTHabgeschaltet: die bewusstenos.open/os.fsync-Aufrufe sind hier richtig.
Stolpersteine
- Funktionen und Klassen mit dem Präfix
test_/Testin Produktionscode würden von pytest beim Import gesammelt →check_connection,CheckReport.
Offen / nächster Schritt
- Schritt 3: Mover (Stabilität, Atomarität, Kollisionen, Kopier-Buchführung, Worker mit Zeitlimit, Quarantäne) mit Zielen lokal/Kernel-Mount und SFTP.
2026-10-10 · 17:21–17:27 · Schritt 1: Gerüst, Datenmodell, Migrationen
Erledigt
- Repo angelegt, venv (lokal Python 3.14; Code zielt auf ≥ 3.12, Debian 13 liefert 3.13).
python3-venvfehlt auf der Entwicklungsmaschine → venv mit--without-pip+get-pip. pyproject.toml(hatchling, src-Layout), ruff + mypy strict.- Datenmodell nach Spezifikation plus Ergänzungen (siehe Docstring
models.py):credentials.domain,mounts.state_since/write_ok/root_squash/last_test_*,rules.op_timeout_seconds/quarantine_after,runs.status/trigger/dry_run, Tabellefile_failures,copied_files.mtime_nsstatt Float-mtime. - Alembic-Migration 0001; Migrationen liegen im Paket, damit sie mit installiert werden.
- 10 Tests: Schema = Modell (kein Drift), Down-/Upgrade, Defaults, UTC-Roundtrip,
RESTRICT bei benutzten Mounts/Zugangsdaten, CASCADE bei Regeln, Eindeutigkeit
in
copied_files, CHECK-Constraints der Enums.
Entscheidungen
- Enums als
VARCHAR+CHECK(kein natives Enum; SQLite kann das ohnehin nicht). UTCDateTimelehnt naivedatetimeab, statt sie still als UTC zu interpretieren.- SQLite mit WAL,
foreign_keys=ON,busy_timeout=5000(Scheduler + Web + Worker-Threads).
Stolpersteine
- Alembic-Autogenerate erzeugt CHECK-Constraints bei
Enum(create_constraint=True)dreifach → expliziteCheckConstraint-Zeilen aus der Migration entfernt. - Test deckte auf: ORM setzt ohne
passive_deletesmounts.cred_idauf NULL, statt dasRESTRICTgreifen zu lassen. Behoben.
Offen / nächster Schritt
- Schritt 2: Mount-Schicht, Validierung,
FakeMountBackend, Fernet-Verschlüsselung.