Docs: Dokumentiert SmartMount 2.0.0 in der README

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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FZ3VzCWgbQRMyFEEKPvnZz
This commit is contained in:
2026-09-15 00:50:30 +02:00
co-authored by Claude Sonnet 5
parent b64b81a5c1
commit 2a4619c8e8
+305 -8
View File
@@ -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 <id> --name "Neuer Name" --local-ip 192.168.1.99
smart-mount drive edit <id> --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 <id>
# Ein einzelnes Paar oder alle einbinden/aushängen
smart-mount mount --name <id>
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`
(`<mount_base_dir>/<pair-id>`) - 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/<Nutzer>/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/<Nutzer>`
sowie `/run/media/<Nutzer>/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 <mac>` 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<VERSION>`) |
| `testing` | `.gitea/workflows/testing.yaml` | `testing` | Pre-Release (`v<VERSION>-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).