2 Commits
Author SHA1 Message Date
Florian WildeandClaude Opus 5.5 4369faf144 fix(sftp): nach erfolgreichem Test als erreichbar anzeigen
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 22:54:46 +02:00
Florian WildeandClaude Opus 5.5 0a317e2aa9 feat(mounts): Klartext-Hinweis bei mount error(95) – keine gemeinsame SMB-Version
Aufgefallen beim echten Synology-NAS (Maximales SMB-Protokoll: SMB2).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 22:50:19 +02:00
6 changed files with 27 additions and 0 deletions
+6
View File
@@ -8,6 +8,12 @@ Die Version steht an genau einer Stelle: `src/netmover/__init__.py`.
## [Unreleased]
### Geändert
- SFTP-Anbindungen gelten nach erfolgreichem Test als „erreichbar“ (vorher bis
zum ersten Regellauf „nicht geprüft“).
- Klartext-Hinweis bei `mount error(95)` (keine gemeinsame SMB-Version) mit
Synology-Einstellung; aufgefallen bei der Anbindung des echten NAS.
## [0.1.1] – 2026-10-10
Korrekturen aus dem ersten Praxistest auf einer Debian-13-VM mit echten
+1
View File
@@ -327,6 +327,7 @@ findmnt -R /mnt/netmover # was ist wirklich eingehängt?
|---|---|
| `mount error(13): Permission denied` (CIFS) | Benutzer oder Passwort falsch, oder keine Rechte auf die Freigabe. |
| `mount error(2): No such file or directory` | Freigabename falsch geschrieben (Groß/klein!). |
| `mount error(95): Operation not supported` | Keine gemeinsame SMB-Version (im Kernel-Log: „Dialect not supported by server“). Am NAS das maximale SMB-Protokoll auf SMB3 stellen oder Zusatzoption `vers=2.0`. |
| `mount error(112): Host is down` | NAS nicht erreichbar, oder SMB1 erwartet: `vers=3.0` bzw. `vers=2.1` probieren. |
| `… helper program` / `unknown filesystem type` | `apt install cifs-utils nfs-common` |
| `Operation not permitted` / `only root can do that` | Kein `CAP_SYS_ADMIN`: unprivilegierter LXC oder Docker ohne `cap_add`. |
+7
View File
@@ -58,6 +58,13 @@ def explain_mount_error(fstype: str, message: str) -> str:
if "helper program" in low or "unknown filesystem type" in low:
pkg = "cifs-utils" if fstype == "cifs" else "nfs-common"
hint = f"Das Paket „{pkg}“ fehlt: apt install {pkg}"
elif fstype == "cifs" and ("error(95)" in low or "operation not supported" in low):
hint = (
"Server und Client finden keine gemeinsame SMB-Version. Am besten am NAS "
"SMB2/SMB3 erlauben (Synology: Systemsteuerung → Dateidienste → SMB → "
"Erweiterte Einstellungen → Maximales SMB-Protokoll: SMB3). Alternativ "
"Zusatzoption vers=2.0 – vers=1.0 nur im Notfall, SMB1 ist unsicher."
)
elif "permission denied" in low and fstype == "cifs":
hint = "Benutzername/Passwort falsch oder der Benutzer hat keine Rechte auf die Freigabe."
elif "permission denied" in low or "access denied by server" in low:
+5
View File
@@ -507,6 +507,11 @@ class MountManager:
if cfg.type is MountType.SFTP and m.host_key is None and report.can_read:
m.host_key = report.host_key
event(log, "sftp.host_key_pinned", mount=m.name, fp=report.host_key_fingerprint)
# SFTP hat keinen Mount-Zustand – ein erfolgreicher Test heißt „erreichbar“.
if cfg.type is MountType.SFTP and report.ok and m.state is not MountState.MOUNTED:
m.state = MountState.MOUNTED
m.state_since = utcnow()
m.last_error = None
# -- Lebenszeichen und Wiederherstellung --------------------------------------
+6
View File
@@ -110,6 +110,12 @@ def test_is_mounted_reads_mountinfo(tmp_path: Path) -> None:
),
("nfs4", "mount.nfs4: access denied by server while mounting nas:/x", "NFS-Berechtigungen"),
("cifs", "mount error(112): Host is down", "nicht erreichbar"),
# Praxisfall: Synology mit „Maximales SMB-Protokoll: SMB2“
(
"cifs",
"mount error(95): Operation not supported\nRefer to the mount.cifs(8) manual page",
"vers=2.0",
),
("cifs", "mount: only root can do that", "CAP_SYS_ADMIN"),
],
)
+2
View File
@@ -147,6 +147,8 @@ def test_manager_pins_host_key_and_checks_alive(
with session_factory() as s:
mm = s.get(Mount, mid)
assert mm is not None and mm.host_key == server.host_key_string
# Praxisbefund: nach erfolgreichem Test „erreichbar“ statt „nicht geprüft“
assert mm.state is MountState.MOUNTED
assert manager.check_alive(mid) is None
with session_factory() as s: