From 2a4619c8e811406a8f0d8cf5790ab53993eef236 Mon Sep 17 00:00:00 2001 From: DragonSlayer_14 Date: Tue, 15 Sep 2026 00:50:30 +0200 Subject: [PATCH] Docs: Dokumentiert SmartMount 2.0.0 in der README MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Beschreibt die neue Architektur (Laufwerkspaare, Mount-Backends, verschlüsselte Zugangsdaten, mac2ip-Integration), Voraussetzungen, Setup-Ablauf (setup fstab, service install) sowie die vollständige CLI-Referenz. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01FZ3VzCWgbQRMyFEEKPvnZz --- README.md | 313 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 305 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 0ce2061..1e32030 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,311 @@ # SmartMount -SmartMount ist ein innovatives Tool zur intelligenten Verwaltung von Netzwerk-Dateisystemen. Es ermöglicht das -automatische Einbinden von Netzwerk-Freigaben über das lokale Netzwerk und wechselt nahtlos zu einer Cloud-basierten -Lösung, falls keine lokale Verbindung verfügbar ist. Durch diese hybride Architektur wird ein zuverlässiger Zugriff auf -wichtige Daten sichergestellt - egal ob zu Hause oder unterwegs. +SmartMount bindet Paare aus einem **lokalen** (LAN, WebDAV/SMB/NFS) und einem **Cloud**-Laufwerk +ein: ist das lokale Laufwerk erreichbar, wird es gemountet - sonst automatisch das +Cloud-Laufwerk. Ein Watchdog prüft periodisch die Erreichbarkeit und schaltet bei Bedarf +zwischen beiden um. Zugangsdaten werden verschlüsselt in einer lokalen [Turso](https://github.com/tursodatabase/turso)-Datenbank +gespeichert, niemals im Klartext in der Konfigurationsdatei. Das Command heißt `smart-mount`. -### Build: +Die Synchronisation der Inhalte zwischen lokalem und Cloud-Laufwerk ist **nicht** Teil dieses +Programms - SmartMount setzt voraus, dass beide Seiten bereits inhaltlich synchron gehalten +werden (z. B. über ein separates Sync-Tool). -Für Debian muss `cargo-deb` installiert sein, dann kann man das Paket mit diesem Paket builden: +--- -```shell -cargo deb --separate-debug-symbols --compress-debug-symbols +## Voraussetzungen + +- **[`mac2ip`](https://gitea.creative-dragonslayer.de/Linuxapps/Mac2Ip)** und **`nmap`**: hart + benötigt, sofern lokale Laufwerke per MAC-Adresse adressiert werden (Auflösung MAC → IP). +- **`timeout`** (GNU coreutils): auf praktisch jedem Linux-System bereits vorhanden (Basis- + Systempaket). Wird verwendet, um `mount`/`umount`-Aufrufe zeitlich zu begrenzen - siehe + "Verhalten bei nicht mehr erreichbarem Server" unten. +- **Pro genutztem Mount-Typ** (nur was tatsächlich konfiguriert ist, wird zur Laufzeit + geprüft - siehe `MountBackend::check_available`): + - `davfs2` für WebDAV-Laufwerke + - `cifs-utils` für SMB/CIFS-Laufwerke + - `nfs-common` (Debian/Ubuntu) bzw. `nfs-utils` (Fedora/Arch) für NFS-Laufwerke +- Für MAC-basierte lokale Laufwerke im **Nutzerkontext**: `mac2ip` löst über `ip neigh` auf, + ohne dafür Root-Rechte zu benötigen - nur der letzte Fallback-Schritt (ein aktiver + `nmap`-Scan, falls das Zielgerät nicht in der ARP-Nachbartabelle steht) braucht + passwortlosen `sudo`-Zugriff auf `nmap`. Ohne das schlägt die Auflösung in diesem Fall sauber + fehl (kein Absturz) - entweder passwortlosen `sudo` für `nmap` einrichten, oder das + entsprechende Laufwerkspaar im System-/root-Kontext betreiben. + +--- + +## CLI-Nutzung + +```bash +# Neues Laufwerkspaar interaktiv anlegen (fragt Name, Kontext, Mount-Typ, Adresse, +# Freigabe und ggf. Zugangsdaten ab) +smart-mount drive add + +# Nicht-interaktiv per Flags (für Skripte/Automatisierung) - fehlende Pflichtfelder sind +# dann ein Fehler statt eines (ohne Terminal ohnehin unmöglichen) Prompts. Jedes +# *-password-stdin-Flag liest genau eine Zeile von stdin (bei mehreren im selben Aufruf +# entsprechend mehrere Zeilen, eine pro Flag in Reihenfolge): +smart-mount drive add --non-interactive \ + --name NAS --context system \ + --local-kind smb --local-ip 192.168.1.50 --local-share share --local-username nasuser --local-password-stdin \ + --cloud-kind webdav --cloud-host https://cloud.example.com/dav --cloud-share / --cloud-username clouduser --cloud-password cloud-pw-direkt \ + <<< "lokales-passwort" +# (--local-password/--cloud-password gehen auch direkt als Argument, sind aber im +# Prozess-Listing/Shell-Verlauf sichtbar - für Skripte lieber *-stdin verwenden) + +# Bestehendes Laufwerkspaar bearbeiten - nur angegebene Felder ändern sich, alles andere +# (inkl. gespeichertem Passwort) bleibt unangetastet. Ohne Flags: interaktiver Durchlauf, +# vorbelegt mit den aktuellen Werten (inkl. Rückfrage, ob das Passwort geändert werden soll). +smart-mount drive edit --name "Neuer Name" --local-ip 192.168.1.99 +smart-mount drive edit --non-interactive --local-password-stdin <<< "neues-passwort" + +# Konfigurierte Laufwerkspaare auflisten / entfernen (--json für Skripte) +smart-mount drive list +smart-mount drive list --json +smart-mount drive remove + +# Ein einzelnes Paar oder alle einbinden/aushängen +smart-mount mount --name +smart-mount mount --all +smart-mount unmount --all + +# Aktuellen Status (Mount-Zustand + Erreichbarkeit beider Seiten) anzeigen (--json für Skripte) +smart-mount status +smart-mount status --json + +# Ein Reconcile-Durchlauf (lokal/Cloud-Umschaltung) - für systemd-Timer/Cron gedacht +smart-mount watch + +# systemd-Units installieren (System- bzw. Nutzerkontext) +sudo smart-mount service install --system +smart-mount service install --user + +# Einmaliges root-Setup, damit Nutzer-Kontext-Paare unprivilegiert (un)gemountet werden können +sudo smart-mount setup fstab + +# Voraussetzungen prüfen (Binaries, Gruppenmitgliedschaft, fstab-Setup, Scheduler) - deckt +# gebündelt ab, was man sonst erst einzeln beim Mount-Fehlschlag entdecken würde +smart-mount doctor +smart-mount doctor --json + +# Shell-Completion-Skript ausgeben (bash/zsh/fish/elvish/powershell) +smart-mount completions bash > /etc/bash_completion.d/smart-mount +smart-mount completions zsh > "${fpath[1]}/_smart-mount" ``` + +Die Konfiguration liegt unter `~/.config/smart-mount/config.toml` (Nutzerkontext) bzw. +`/etc/smart-mount/config.toml` (root/System-Kontext) - automatisch aufgelöst je nachdem, ob +`smart-mount` mit Root-Rechten läuft. Die verschlüsselte Zugangsdaten-Datenbank +(`smart-mount.db`) liegt im selben Verzeichnis. + +### Wo die Laufwerke eingebunden werden + +Jedes Laufwerkspaar bekommt sein **eigenes** Unterverzeichnis unter `settings.mount_base_dir` +(`/`) - mehrere Paare stören sich also nie gegenseitig. Standardwert +für `mount_base_dir`: `/run/media/smart-mount` im System-Kontext (root, ein gemeinsamer, +keinem Nutzer zugeordneter Namensraum), `/run/media//smart-mount` im Nutzerkontext - +`/run/media` ist auf den meisten Systemen bereits die übliche Konvention für eingebundene +Wechseldatenträger/Netzlaufwerke (z. B. udisks2/GNOME) und liegt auf `tmpfs`, muss also nie +persistieren. Der Mountpoint selbst (und alle nötigen Elternverzeichnisse) werden bei jedem +`mount`/`watch`-Lauf automatisch angelegt, falls sie fehlen - eigenes Anlegen ist nicht nötig. + +**Achtung bei Nutzer-Kontext-Paaren:** `/run/media` gehört standardmäßig `root:root` mit Modus +`0755` - ein normaler Nutzer kann dort also nicht einmal sein eigenes Unterverzeichnis +anlegen. `sudo smart-mount setup fstab` übernimmt das einmalig (legt `/run/media/` +sowie `/run/media//smart-mount` an und macht den Nutzer zum Besitzer) - ohne diesen +Schritt schlägt das automatische Anlegen für Nutzer-Kontext-Paare fehl. + +Der Standard lässt sich in `config.toml` unter `[settings] mount_base_dir = "..."` jederzeit +auf einen beliebigen anderen Pfad ändern. + +--- + +## Automatischer Start beim Systemstart + +```bash +sudo smart-mount setup fstab # einmalig, nur nötig bei Nutzer-Kontext-Paaren +sudo smart-mount service install --system # für System-Kontext-Paare +smart-mount service install --user # für die eigenen Nutzer-Kontext-Paare +``` + +`service install` wählt automatisch den passenden Mechanismus: ist `systemctl` vorhanden, +werden systemd-Units installiert (ein `oneshot`-Service für den initialen Mount beim Boot +sowie ein Timer, der periodisch `smart-mount watch` aufruft - Intervall: +`settings.watch_interval_secs`, Standard 120s). Ist kein systemd vorhanden, wird automatisch +auf Cron ausgewichen - als root wird `/etc/cron.d/smart-mount` geschrieben, als normaler +Nutzer die eigene, persönliche Crontab über `crontab -l`/`crontab -` aktualisiert (ein +verwalteter Block lässt dabei bereits vorhandene, unabhängige Cron-Einträge unangetastet und +verhindert Duplikate bei wiederholten Aufrufen). Ist weder systemd noch Cron vorhanden, werden +stattdessen die beiden äquivalenten Zeilen zum manuellen Eintragen ausgegeben: + +```cron +@reboot smart-mount mount --all +*/2 * * * * smart-mount watch +``` + +`smart-mount service crontab` erzwingt gezielt den Cron-Weg (z. B. um systemd bewusst zu +umgehen), mit identischem Verhalten wie der automatische Fallback von `install`. + +```bash +sudo smart-mount service uninstall --system +smart-mount service uninstall --user +``` + +räumt alles wieder auf, was `install`/`crontab`/`setup fstab` eingerichtet haben - systemd- +Units (falls vorhanden), den Cron-Eintrag (falls vorhanden) und - nur bei `--system`, da +`setup fstab` root-weit für alle Nutzer-Kontext-Paare gilt - den verwalteten `/etc/fstab`- +Block. Jeder Teil wird unabhängig geprüft: fehlt etwas (z. B. weil nur Cron statt systemd +installiert war), wird das ohne Fehler übersprungen; die Ausgabe listet, was tatsächlich +entfernt wurde. Backing-Verzeichnisse, gemountete Daten und Gruppenmitgliedschaften (z. B. in +der `davfs2`-Gruppe) werden dabei bewusst **nicht** angerührt - dafür gibt es keine +automatische Umkehrung, da das ungewollte Nebenwirkungen haben könnte (siehe +Architekturentscheidungen unten). + +--- + +## Architekturentscheidungen + +- **Verhalten bei nicht mehr erreichbarem Server (der ganze Sinn dieses Tools)**: Netzwerk- + Dateisysteme können unter Linux "einfrieren", wenn der Server verschwindet - `umount` + oder Schreibzugriffe hängen dann scheinbar unbegrenzt. Das wird gezielt adressiert: + - **NFS**: Standardmäßig setzt `mount.nfs` `hard` (unbegrenzte Wiederholungsversuche bei + Zeitüberschreitung) - genau das Einfrierverhalten. SmartMount setzt automatisch `soft`, + sofern nicht bereits `hard`/`soft`/`softerr` explizit in `extra_options` gesetzt ist + (`man 5 nfs`, Abschnitt "soft / softerr / hard" nennt einen dauerhaft nicht erreichbaren + Server explizit als Zielszenario für `soft`). + - **CIFS**: `mount.cifs` verwendet laut eigener Dokumentation bereits standardmäßig `soft` - + kein Eingriff nötig. + - **WebDAV (davfs2)**: `umount` ist laut davfs2-eigener FAQ *absichtlich* so lange + blockierend, bis alle zwischengespeicherten Daten geschrieben sind - bei einem dauerhaft + unerreichbaren Server kehrt das nie zurück. Dafür gibt es keine passende Mount-Option. + `umount -l` (lazy) würde zwar sofort zurückkehren, macht laut `man umount` aber + **künftige Remounts derselben Freigabe bis zum nächsten Neustart unmöglich** - für ein + Tool, dessen ganzer Zweck automatisches Zurückschalten ist, wäre das schlimmer als das + ursprüngliche Problem. + - **Der eigentliche Fix**: jeder `mount`/`umount`-Aufruf läuft über das coreutils-Tool + `timeout` (30s). Bei `umount` wird ein Abbruch wegen Zeitüberschreitung toleriert (nur + geloggt, `Ok`) statt als Fehler behandelt - der nächste `watch`-Durchlauf versucht es + erneut (idempotent). Dadurch kann `smart-mount watch`/`unmount` selbst dann nicht mehr + unbegrenzt hängen bleiben, wenn eine einzelne Seite dauerhaft nicht antwortet - und weil + beim Umschalten zuerst die neue Seite gemountet und der Symlink umgebogen wird, bevor die + alte Seite (mit eben diesem tolerierten Timeout) ausgehängt wird, blockiert ein + hängender Alt-Mount den sichtbaren Wechsel ohnehin nicht. +- **Privilegienmodell für Nutzer-Mounts**: `sudo smart-mount setup fstab` schreibt einmalig + `/etc/fstab`-Einträge mit `user,exec,noauto` sowie nötige Gruppenmitgliedschaften (z. B. + `davfs2`-Gruppe). Jede Seite (lokal/Cloud) bekommt dabei ihr **eigenes, eindeutiges** + verstecktes Backing-Verzeichnis (nicht denselben Mountpoint für beide) - das entspricht + exakt dem einzigen in `man 8 mount` ("Non-superuser mounts") dokumentierten Fall, statt sich + auf unspezifiziertes Verhalten bei zwei Zeilen mit demselben Ziel zu verlassen. Der + konfigurierte, sichtbare Mountpoint selbst ist ein Symlink, den SmartMount zur Laufzeit + zwischen den beiden Backing-Verzeichnissen umschaltet. `exec` wird explizit gesetzt, weil die + `user`-Option laut `man 8 mount` sonst für jedes Dateisystem automatisch `noexec` erzwingt - + ohne das könnten auf einem Nutzer-Kontext-Laufwerk liegende Skripte nicht ausgeführt werden. +- **Voller Zugriff für einen bestimmten Nutzer (`owner_user`)**: Bei CIFS/WebDAV (keine + nativen Unix-Rechte) setzt SmartMount automatisch `uid=`/`gid=`/`file_mode=0700`/ + `dir_mode=0700`, sobald ein Paar ein `owner_user` hat (Pflicht bei Nutzer-Kontext-Paaren, + optional bei System-Kontext). Das ist nicht nur für vollen Zugriff (inkl. Skript-Ausführung) + nötig, sondern bei WebDAV auch sicherheitsrelevant: `mount.davfs` erlaubt einem + unprivilegierten Nutzer das Mounten einer `user`-Zeile laut `man mount.davfs` nur, wenn + `uid=` auf ihn selbst zeigt - ohne das dürfte jedes Mitglied der Gruppe `davfs2` jedes + konfigurierte Paar mounten, nicht nur sein eigenes (`setup fstab` verweigert daher Paare ohne + `owner_user`). + + **Bekannte, bewusst nicht behobene Lücke bei CIFS im Nutzerkontext:** `man mount.cifs` + bestätigt, dass `uid=`/`gid=` dort **ausschließlich** die simulierte Datei-Ownership nach dem + Mount betreffen - anders als bei davfs2 gibt es **keinen** Mechanismus, der das Mount-*Recht* + einer `user`-fstab-Zeile auf eine bestimmte Person einschränkt. Jeder lokale Nutzer, der + unprivilegiert mounten darf, kann daher aktuell jedes konfigurierte CIFS-Nutzer-Kontext-Paar + mounten (nicht nur sein eigenes) und dabei dessen gespeicherte Zugangsdaten für die Dauer des + Mounts mitbenutzen. Das ist eine strukturelle Grenze von `mount(8)`/`mount.cifs`, keine Lücke, + die sich über Mount-Optionen schließen ließe. **Konsequenz:** CIFS-Nutzer-Kontext-Paare nur + auf Einzelnutzer-Maschinen oder unter sich gegenseitig bereits vertrauenden lokalen Nutzern + einsetzen. davfs2 ist von diesem Problem nicht betroffen (siehe oben); bei NFS gibt es keine + clientseitige `uid=`/`gid=`-Option, Zugriff bestimmt dort ausschließlich der Server über die + tatsächlichen Datei-Eigentümer/-Rechte des Exports - wer die Freigabe mounten kann, sieht + dadurch nicht automatisch fremde Daten. +- **davfs2-Konfiguration**: SmartMount setzt in `davfs2.conf` automatisch `gui_optimize 1` + (bündelt PROPFIND-Anfragen, wichtig für grafische Dateimanager) sowie `buf_size 16384` + (deutlich über dem Standard von 16 KiB) - Letzteres behebt ein bekanntes Praxisproblem, bei + dem `ls` in Verzeichnissen mit vielen Dateien einen leeren/unvollständigen Inhalt zeigt, + obwohl einzelne Dateien direkt geöffnet werden können (der FUSE-readdir-Puffer wird bei zu + kleinem `buf_size` stillschweigend abgeschnitten). +- **Zugangsdaten-Verschlüsselung**: Turso hat aktuell keine produktionsreife eingebaute + Verschlüsselung, daher verschlüsselt SmartMount Passwörter selbst (AES-256-GCM) vor der + Ablage. Der Master-Schlüssel wird bevorzugt im OS-Keyring (GNOME Keyring/KWallet über + secret-service) abgelegt; ist keins verfügbar (typisch für den root/System-Dienst sowie + Headless-Systeme), wird automatisch auf eine Schlüsseldatei (`chmod 600`, neben der + Konfiguration) zurückgegriffen. +- **`mac2ip`-Integration**: SmartMount ruft `mac2ip --json --auto-trust-networks ` auf. + `--auto-trust-networks` lässt mac2ip die eigene "nmap-Scan in diesem Netzwerk erlauben?"- + Rückfrage automatisch bejahen und dauerhaft in seiner eigenen Cache-Datenbank merken - das + erspart SmartMount, das Konfigurationsschema eines fremden Tools zu kennen oder dort direkt + hineinzuschreiben. + +--- + +## Struktur des Repositories + +```text +├── .cargo/ +│ └── config.toml # Cargo-Konfiguration (Linker für Cross-Compiling, Registry) +├── .gitea/ +│ └── workflows/ # CI/CD-Pipelines (Build, Tests, Security-Scans, Releases) +├── scripts/ +│ ├── get-build-number.py # Dynamische Ermittlung der nächsten Paket-Revisionsnummer +│ ├── package-arch.py # Erstellung von Arch Linux .pkg.tar.zst Paketen +│ └── report-security-issue.py # Security-Scan-Ergebnisse als Gitea-Issue melden +├── src/ +│ ├── main.rs # Dünner Einstiegspunkt (CLI-Parsing, Dispatch) +│ ├── lib.rs # Bibliotheks-Wurzel +│ ├── cli/ # `clap`-Subcommands +│ ├── config/ # Konfigurationsschema + CRUD (config-ctdra) +│ ├── crypto/ # Verschlüsselung + Master-Key-Auflösung +│ ├── db/ # Verschlüsselte Zugangsdaten (Turso) +│ ├── mount/ # WebDAV/SMB/NFS-Backends, dynamischer Dispatch +│ ├── network/ # Erreichbarkeit, mac2ip-Integration +│ ├── reconcile/ # Watchdog-Entscheidungslogik (lokal/Cloud-Umschaltung) +│ ├── systemd/ # systemd-Unit-Generierung/-Installation +│ └── fstab/ # Einmaliges root-Setup für Nutzer-Mounts +├── tests/ # Cross-Modul-Integrationstests +├── Cargo.toml # Cargo Manifest & Paketierungsmetadaten (deb, rpm, arch) +├── LICENSE # Lizenzdatei (GPL-3.0-or-later) +├── AGENTS.md # Richtlinien und Leitfaden für KI-Coding-Agenten +└── README.md # Diese Datei +``` + +--- + +## Lokale Entwicklung + +```bash +cargo build +cargo test +cargo build --release + +# Debian-Paket bauen (.deb) +cargo deb + +# RPM-Paket bauen (.rpm) +cargo generate-rpm + +# Arch Linux-Paket bauen (.pkg.tar.zst) +python3 scripts/package-arch.py --arch x86_64 --pkgrel 1 +``` + +--- + +## CI/CD Workflow-Übersicht + +| Branch | Workflow | Paket-Kanal | Release-Typ | +| :--- | :--- | :--- | :--- | +| `main` | `.gitea/workflows/main.yaml` | `stable` | Offizielles Release (`v`) | +| `testing` | `.gitea/workflows/testing.yaml` | `testing` | Pre-Release (`v-preview`) | + +Zusätzlich: automatisierte Security-Scans (Trivy, OSV-Scanner, TruffleHog), Renovate für +Abhängigkeits-Updates, sowie automatisiertes `cargo fmt`/`cargo clippy --fix` auf dem +`dev`-Branch. + +--- + +## Lizenz + +[GPL-3.0-or-later](LICENSE).