Feature: Entfernt 'service'-Befehl, Cron-Fallback wird automatisch mitpaketiert, doctor prüft distro- und statusgenau nach
Testing Build, Publish & Preview Release / Build, Publish Packages (Testing) & Create Preview Release (push) Skipped
TruffleHog Secret Scan / TruffleHog (push) Successful in 17s
Security Scans / Trivy & OSV-Scanner (push) Successful in 27s
Code Quality (Auto-Format & Clippy-Fix) / Formatierung & Clippy automatisch beheben (push) Successful in 36s
Auto Patch-Version-Bump (Dev → Testing PR) / Erkenne relevante Änderungen im PR (pull_request) Successful in 11s
TruffleHog Secret Scan / TruffleHog (pull_request) Successful in 18s
Auto Patch-Version-Bump (Dev → Testing PR) / Patch-Version erhöhen & auf Dev pushen (pull_request) Successful in 11s
Security Scans / Trivy & OSV-Scanner (pull_request) Successful in 35s
Unit-Tests / Unit-Tests (pull_request) Successful in 4m4s
Testing Build, Publish & Preview Release / Build, Publish Packages (Testing) & Create Preview Release (push) Skipped
TruffleHog Secret Scan / TruffleHog (push) Successful in 17s
Security Scans / Trivy & OSV-Scanner (push) Successful in 27s
Code Quality (Auto-Format & Clippy-Fix) / Formatierung & Clippy automatisch beheben (push) Successful in 36s
Auto Patch-Version-Bump (Dev → Testing PR) / Erkenne relevante Änderungen im PR (pull_request) Successful in 11s
TruffleHog Secret Scan / TruffleHog (pull_request) Successful in 18s
Auto Patch-Version-Bump (Dev → Testing PR) / Patch-Version erhöhen & auf Dev pushen (pull_request) Successful in 11s
Security Scans / Trivy & OSV-Scanner (pull_request) Successful in 35s
Unit-Tests / Unit-Tests (pull_request) Successful in 4m4s
Kein 'smart-mount service crontab'-Subcommand mehr - das neue interne Hilfsprogramm setup-cron (src/bin/setup-cron.rs, installiert nach /usr/lib/smart-mount/setup-cron) richtet den Cron-Fallback beim Paketieren automatisch über die postinst-Skripte ein, sofern kein systemd verfügbar ist; postrm/das Arch-.install-Skriptlet entfernen den Cron-Eintrag beim Deinstallieren ebenso automatisch wieder. systemd::binary_path() ist dafür jetzt fest auf '/usr/bin/smart-mount' gesetzt statt current_exe() zu nutzen, da der Cron-Eintrag nun von einem separaten Prozess geschrieben wird. 'smart-mount doctor' zeigt Installationshinweise jetzt nur noch für die über /etc/os-release erkannte Distribution (Debian/Fedora/Arch) statt immer alle drei Varianten zu nennen, und der Scheduler-Check prüft echten Status statt bloßer Tool-Präsenz: systemctl is-enabled/is-active für die smart-mount-Units sowie tatsächliche Existenz von /etc/cron.d/smart-mount plus laufendem Cron-Daemon-Prozess. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+19
-4
@@ -75,6 +75,10 @@ assets = [
|
||||
["target/completions/smart-mount.bash", "usr/share/bash-completion/completions/smart-mount", "644"],
|
||||
["target/completions/_smart-mount", "usr/share/zsh/vendor-completions/_smart-mount", "644"],
|
||||
["target/completions/smart-mount.fish", "usr/share/fish/vendor_completions.d/smart-mount.fish", "644"],
|
||||
# Kein 'smart-mount service'-Subcommand mehr - packaging/deb/postinst ruft dieses interne
|
||||
# Hilfsprogramm (siehe src/bin/setup-cron.rs) stattdessen automatisch auf und richtet damit
|
||||
# bei Bedarf (kein systemd, aber /etc/cron.d vorhanden) den Cron-Fallback ein.
|
||||
["target/release/setup-cron", "usr/lib/smart-mount/setup-cron", "755"],
|
||||
]
|
||||
|
||||
[package.metadata.generate-rpm]
|
||||
@@ -90,22 +94,33 @@ assets = [
|
||||
{ source = "target/completions/smart-mount.bash", dest = "/usr/share/bash-completion/completions/smart-mount", mode = "644" },
|
||||
{ source = "target/completions/_smart-mount", dest = "/usr/share/zsh/site-functions/_smart-mount", mode = "644" },
|
||||
{ source = "target/completions/smart-mount.fish", dest = "/usr/share/fish/vendor_completions.d/smart-mount.fish", mode = "644" },
|
||||
# Kein 'smart-mount service'-Subcommand mehr - post_install_script ruft dieses interne
|
||||
# Hilfsprogramm (siehe src/bin/setup-cron.rs) stattdessen automatisch auf und richtet damit
|
||||
# bei Bedarf (kein systemd, aber /etc/cron.d vorhanden) den Cron-Fallback ein.
|
||||
{ source = "target/release/setup-cron", dest = "/usr/lib/smart-mount/setup-cron", mode = "755" },
|
||||
]
|
||||
requires = { "mac2ip" = "*" }
|
||||
suggests = { "davfs2" = "*", "cifs-utils" = "*", "nfs-utils" = "*" }
|
||||
# postinst/postrm-Äquivalente: richten den paketierten systemd-Dienst automatisch ein/entfernen
|
||||
# (Gegenstück zu packaging/deb/postinst+postrm). $1 in %postun: Anzahl verbleibender Versionen
|
||||
# nach diesem Schritt - 0 nur bei vollständiger Deinstallation, nicht bei einem Upgrade.
|
||||
# postinst/postrm-Äquivalente: richten den paketierten systemd-Dienst (bzw. ohne systemd den
|
||||
# Cron-Fallback über setup-cron) automatisch ein/entfernen (Gegenstück zu
|
||||
# packaging/deb/postinst+postrm). $1 in %postun: Anzahl verbleibender Versionen nach diesem
|
||||
# Schritt - 0 nur bei vollständiger Deinstallation, nicht bei einem Upgrade. Die Cron-Datei wird
|
||||
# in %postun direkt per "rm -f" entfernt statt über setup-cron selbst: zu diesem Zeitpunkt sind
|
||||
# die Paketdateien - und damit auch setup-cron selbst - bereits entfernt.
|
||||
post_install_script = """
|
||||
if command -v systemctl >/dev/null 2>&1; then
|
||||
systemctl daemon-reload || true
|
||||
systemctl enable --now smart-mount-mount.service smart-mount-watch.timer || true
|
||||
fi
|
||||
/usr/lib/smart-mount/setup-cron || true
|
||||
"""
|
||||
post_uninstall_script = """
|
||||
if [ "$1" = "0" ] && command -v systemctl >/dev/null 2>&1; then
|
||||
if [ "$1" = "0" ]; then
|
||||
if command -v systemctl >/dev/null 2>&1; then
|
||||
systemctl disable --now smart-mount-mount.service smart-mount-watch.timer smart-mount-watch.service || true
|
||||
systemctl daemon-reload || true
|
||||
fi
|
||||
rm -f /etc/cron.d/smart-mount
|
||||
fi
|
||||
"""
|
||||
|
||||
|
||||
@@ -26,8 +26,9 @@ werden (z. B. über ein separates Sync-Tool).
|
||||
- `cifs-utils` für SMB/CIFS-Laufwerke
|
||||
- `nfs-common` (Debian/Ubuntu) bzw. `nfs-utils` (Fedora/Arch) für NFS-Laufwerke
|
||||
- `systemd`: smart-mount richtet sich beim Installieren des Pakets automatisch als
|
||||
System-Dienst ein (siehe "Automatischer Start beim Systemstart" unten). Ohne systemd bleibt
|
||||
`smart-mount service crontab` als manueller Fallback.
|
||||
System-Dienst ein (siehe "Automatischer Start beim Systemstart" unten). Ohne systemd, aber mit
|
||||
`/etc/cron.d`, wird beim Installieren automatisch ein Cron-Fallback eingerichtet - kein
|
||||
manueller Schritt nötig.
|
||||
|
||||
---
|
||||
|
||||
@@ -79,11 +80,6 @@ smart-mount status --json
|
||||
# Ein Reconcile-Durchlauf (lokal/Cloud-Umschaltung) - für systemd-Timer/Cron gedacht
|
||||
sudo smart-mount watch
|
||||
|
||||
# Cron-Fallback für Systeme ohne (genutzten) systemd - siehe "Automatischer Start beim
|
||||
# Systemstart" unten
|
||||
sudo smart-mount service crontab
|
||||
sudo smart-mount service crontab --remove
|
||||
|
||||
# Voraussetzungen prüfen (Binaries, Scheduler) - deckt gebündelt ab, was man sonst erst
|
||||
# einzeln beim Mount-Fehlschlag entdecken würde
|
||||
smart-mount doctor
|
||||
@@ -168,21 +164,25 @@ sudo systemctl edit smart-mount-watch.timer
|
||||
|
||||
### Cron-Fallback (Systeme ohne systemd)
|
||||
|
||||
```bash
|
||||
sudo smart-mount service crontab # einrichten
|
||||
sudo smart-mount service crontab --remove # wieder entfernen
|
||||
```
|
||||
|
||||
Erfordert Root (wie `mount`/`watch` - Mounten läuft immer als root, siehe oben) und schreibt
|
||||
`/etc/cron.d/smart-mount` - das Intervall folgt `settings.watch_interval_secs`. Ist
|
||||
`/etc/cron.d` nicht vorhanden, werden stattdessen die beiden äquivalenten Zeilen zum manuellen
|
||||
Eintragen ausgegeben:
|
||||
Ist beim Installieren/Upgraden des Pakets kein `systemctl` gefunden, aber `/etc/cron.d`
|
||||
vorhanden, schreibt das Paket **automatisch** `/etc/cron.d/smart-mount` (kein manueller Schritt,
|
||||
kein eigenes `smart-mount`-Subcommand) - das Intervall folgt `settings.watch_interval_secs`
|
||||
(Standard 120s):
|
||||
|
||||
```cron
|
||||
@reboot smart-mount mount --all
|
||||
*/2 * * * * smart-mount watch
|
||||
@reboot root /usr/bin/smart-mount mount --all
|
||||
*/2 * * * * root /usr/bin/smart-mount watch
|
||||
```
|
||||
|
||||
Zuständig dafür ist `setup-cron` (`src/bin/setup-cron.rs`, installiert nach
|
||||
`/usr/lib/smart-mount/setup-cron`) - ein internes Hilfsprogramm, kein Teil der öffentlichen
|
||||
`smart-mount`-CLI, das die postinst-Skripte des Pakets (bzw. das `.INSTALL`-Skriptlet bei Arch)
|
||||
automatisch aufrufen; beim Deinstallieren wird `/etc/cron.d/smart-mount` ebenso automatisch
|
||||
wieder entfernt. Ist weder systemd noch `/etc/cron.d` vorhanden, meldet `setup-cron` das nur
|
||||
auf der Konsole (Paketinstallation schlägt dadurch nicht fehl) - die periodische Ausführung
|
||||
muss dann manuell eingerichtet werden, z. B. über eine eigene Crontab-Zeile analog zu obigem
|
||||
Beispiel.
|
||||
|
||||
---
|
||||
|
||||
## Architekturentscheidungen
|
||||
@@ -268,7 +268,9 @@ Eintragen ausgegeben:
|
||||
│ └── report-security-issue.py # Security-Scan-Ergebnisse als Gitea-Issue melden
|
||||
├── src/
|
||||
│ ├── main.rs # Dünner Einstiegspunkt (CLI-Parsing, Dispatch)
|
||||
│ ├── lib.rs # Bibliotheks-Wurzel
|
||||
│ ├── lib.rs # Bibliotheks-Wurzel (inkl. `pub mod cli`)
|
||||
│ ├── bin/ # Interne Hilfsprogramme (kein Teil der `smart-mount`-CLI):
|
||||
│ │ # generate-completions (Build-Zeit), setup-cron (Paket-Installation)
|
||||
│ ├── cli/ # `clap`-Subcommands
|
||||
│ ├── config/ # Konfigurationsschema + CRUD (config-ctdra)
|
||||
│ ├── crypto/ # Verschlüsselung + Master-Key-Auflösung
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
#!/bin/sh
|
||||
# Richtet smart-mount beim Installieren/Upgraden des Pakets automatisch als
|
||||
# System-systemd-Dienst ein (Gegenstueck: postrm). Laeuft nur beim eigentlichen
|
||||
# "configure"-Schritt (siehe Debian Policy Manual, Abschnitt 6.5), nicht bei
|
||||
# "abort-upgrade"/"abort-remove" etc.
|
||||
# System-systemd-Dienst ein (Gegenstueck: postrm); ist kein systemd verfuegbar, richtet
|
||||
# stattdessen setup-cron (siehe src/bin/setup-cron.rs) automatisch den Cron-Fallback ein,
|
||||
# sofern /etc/cron.d existiert. Laeuft nur beim eigentlichen "configure"-Schritt (siehe
|
||||
# Debian Policy Manual, Abschnitt 6.5), nicht bei "abort-upgrade"/"abort-remove" etc.
|
||||
set -e
|
||||
|
||||
if [ "$1" = "configure" ] && command -v systemctl >/dev/null 2>&1; then
|
||||
if [ "$1" = "configure" ]; then
|
||||
if command -v systemctl >/dev/null 2>&1; then
|
||||
systemctl daemon-reload || true
|
||||
systemctl enable --now smart-mount-mount.service smart-mount-watch.timer || true
|
||||
fi
|
||||
/usr/lib/smart-mount/setup-cron || true
|
||||
fi
|
||||
|
||||
exit 0
|
||||
|
||||
+11
-4
@@ -1,13 +1,20 @@
|
||||
#!/bin/sh
|
||||
# Entfernt den beim Installieren eingerichteten systemd-Dienst wieder (Gegenstueck:
|
||||
# Entfernt den beim Installieren eingerichteten systemd-Dienst sowie einen eventuell von
|
||||
# setup-cron (siehe src/bin/setup-cron.rs) angelegten Cron-Fallback wieder (Gegenstueck:
|
||||
# postinst). Nur bei tatsaechlicher Entfernung ("remove"/"purge"), nicht bei einem
|
||||
# Upgrade (dort ersetzt dpkg die Unit-Dateien einfach durch die neue Version, ohne den
|
||||
# laufenden Dienst zwischenzeitlich zu deaktivieren).
|
||||
# Upgrade (dort ersetzt dpkg die Unit-/Paket-Dateien einfach durch die neue Version, ohne
|
||||
# den laufenden Dienst/Cron-Eintrag zwischenzeitlich zu deaktivieren). Die Cron-Datei wird
|
||||
# hier direkt per "rm -f" entfernt statt ueber setup-cron selbst aufgerufen zu werden: zum
|
||||
# Zeitpunkt, zu dem "postrm remove" laeuft, hat dpkg die Paketdateien - und damit auch
|
||||
# setup-cron selbst - bereits entfernt (siehe Debian Policy Manual, Abschnitt 6.5).
|
||||
set -e
|
||||
|
||||
if { [ "$1" = "remove" ] || [ "$1" = "purge" ]; } && command -v systemctl >/dev/null 2>&1; then
|
||||
if { [ "$1" = "remove" ] || [ "$1" = "purge" ]; }; then
|
||||
if command -v systemctl >/dev/null 2>&1; then
|
||||
systemctl disable --now smart-mount-mount.service smart-mount-watch.timer smart-mount-watch.service || true
|
||||
systemctl daemon-reload || true
|
||||
fi
|
||||
rm -f /etc/cron.d/smart-mount
|
||||
fi
|
||||
|
||||
exit 0
|
||||
|
||||
+58
-22
@@ -82,30 +82,55 @@ def collect_completions(completions_dir="target/completions"):
|
||||
return mapping
|
||||
|
||||
|
||||
def build_install_scriptlet(installable, all_units):
|
||||
def find_helper_binary(name, helper, target_triple=None):
|
||||
"""Sucht eine optionale interne Hilfs-Binary (eigenes Cargo-Binary-Target unter
|
||||
'src/bin/<helper>.rs', z. B. fuer einen Cron-Fallback) am selben kompilierten Ort wie die
|
||||
Haupt-Binary - generisch ueber den fest bekannten Hilfsprogramm-Namen, ohne den
|
||||
Anwendungsnamen selbst zu kennen. Gibt None zurueck, falls nicht gefunden (z. B. weil das
|
||||
abgeleitete Projekt kein solches Hilfsprogramm hat)."""
|
||||
candidates = []
|
||||
if target_triple:
|
||||
candidates.append(f"target/{target_triple}/release/{helper}")
|
||||
candidates.append(f"target/release/{helper}")
|
||||
for p in candidates:
|
||||
if os.path.exists(p):
|
||||
return p
|
||||
return None
|
||||
|
||||
|
||||
def build_install_scriptlet(name, installable, all_units, has_cron_helper):
|
||||
"""Erzeugt den Inhalt einer Arch-'.INSTALL'-Datei (siehe `man PKGBUILD`, Abschnitt
|
||||
'install'), die den paketierten systemd-Dienst beim Installieren aktiviert/startet und beim
|
||||
Entfernen wieder deaktiviert/stoppt - rein generisch anhand der tatsächlich gefundenen
|
||||
Unit-Dateien, ohne Anwendungsnamen hart zu codieren."""
|
||||
'install'): aktiviert/startet paketierte systemd-Units beim Installieren/Upgraden und
|
||||
deaktiviert sie beim Entfernen, und ruft - sofern vorhanden - generisch das interne
|
||||
Cron-Fallback-Hilfsprogramm ('usr/lib/<name>/setup-cron', siehe src/bin/setup-cron.rs) beim
|
||||
Installieren/Upgraden auf bzw. entfernt dessen Cron-Datei beim Entfernen wieder. Rein anhand
|
||||
der tatsächlich gefundenen Unit-Dateien/Hilfsprogramme zusammengesetzt, ohne den
|
||||
Anwendungsnamen selbst hart zu codieren (der als `name`-Parameter hereinkommt)."""
|
||||
installable_str = " ".join(installable)
|
||||
all_units_str = " ".join(all_units)
|
||||
return f"""post_install() {{
|
||||
systemctl daemon-reload >/dev/null 2>&1 || true
|
||||
systemctl enable --now {installable_str} >/dev/null 2>&1 || true
|
||||
}}
|
||||
|
||||
post_upgrade() {{
|
||||
systemctl daemon-reload >/dev/null 2>&1 || true
|
||||
}}
|
||||
systemd_enable = (
|
||||
" systemctl daemon-reload >/dev/null 2>&1 || true\n"
|
||||
f" systemctl enable --now {installable_str} >/dev/null 2>&1 || true\n"
|
||||
if installable
|
||||
else ""
|
||||
)
|
||||
systemd_reload = " systemctl daemon-reload >/dev/null 2>&1 || true\n" if all_units else ""
|
||||
systemd_disable = (
|
||||
f" systemctl disable --now {all_units_str} >/dev/null 2>&1 || true\n" if all_units else ""
|
||||
)
|
||||
# Ein zusaetzlicher Cron-Eintrag ist redundant, wenn systemd verfuegbar ist - das
|
||||
# Hilfsprogramm selbst prueft das (siehe src/bin/setup-cron.rs), hier wird es einfach
|
||||
# unconditional aufgerufen.
|
||||
cron_install = f" /usr/lib/{name}/setup-cron >/dev/null 2>&1 || true\n" if has_cron_helper else ""
|
||||
cron_remove = f" rm -f /etc/cron.d/{name}\n" if has_cron_helper else ""
|
||||
|
||||
pre_remove() {{
|
||||
systemctl disable --now {all_units_str} >/dev/null 2>&1 || true
|
||||
}}
|
||||
|
||||
post_remove() {{
|
||||
systemctl daemon-reload >/dev/null 2>&1 || true
|
||||
}}
|
||||
"""
|
||||
return (
|
||||
"post_install() {\n" + systemd_enable + cron_install + "}\n\n"
|
||||
"post_upgrade() {\n" + systemd_reload + cron_install + "}\n\n"
|
||||
"pre_remove() {\n" + systemd_disable + cron_remove + "}\n\n"
|
||||
"post_remove() {\n" + systemd_reload + "}\n"
|
||||
)
|
||||
|
||||
|
||||
def build_package(target_triple=None, target_arch=None, pkgrel=None):
|
||||
@@ -184,6 +209,16 @@ def build_package(target_triple=None, target_arch=None, pkgrel=None):
|
||||
check=True,
|
||||
)
|
||||
|
||||
setup_cron_path = find_helper_binary(name, "setup-cron", target_triple)
|
||||
has_cron_helper = setup_cron_path is not None
|
||||
if has_cron_helper:
|
||||
helper_dir = os.path.join(build_dir, f"usr/lib/{name}")
|
||||
os.makedirs(helper_dir, exist_ok=True)
|
||||
subprocess.run(
|
||||
["install", "-m", "755", setup_cron_path, os.path.join(helper_dir, "setup-cron")],
|
||||
check=True,
|
||||
)
|
||||
|
||||
installed_size = subprocess.check_output(["du", "-sb", build_dir]).decode().split()[0]
|
||||
builddate = str(int(time.time()))
|
||||
|
||||
@@ -203,7 +238,8 @@ def build_package(target_triple=None, target_arch=None, pkgrel=None):
|
||||
pkginfo_lines.append(f"depend = {dep}")
|
||||
for optdep in optdepends:
|
||||
pkginfo_lines.append(f"optdepend = {optdep}")
|
||||
if all_units:
|
||||
needs_install_scriptlet = bool(all_units) or has_cron_helper
|
||||
if needs_install_scriptlet:
|
||||
pkginfo_lines.append(f"install = {name}.install")
|
||||
pkginfo_lines.append("makepkgopt = strip\n")
|
||||
|
||||
@@ -211,10 +247,10 @@ def build_package(target_triple=None, target_arch=None, pkgrel=None):
|
||||
f.write("\n".join(pkginfo_lines))
|
||||
|
||||
tar_members = [".PKGINFO", "usr"]
|
||||
if all_units:
|
||||
if needs_install_scriptlet:
|
||||
install_script_name = f"{name}.install"
|
||||
with open(os.path.join(build_dir, install_script_name), "w") as f:
|
||||
f.write(build_install_scriptlet(installable_units, all_units))
|
||||
f.write(build_install_scriptlet(name, installable_units, all_units, has_cron_helper))
|
||||
tar_members.append(install_script_name)
|
||||
|
||||
os.makedirs("target/arch", exist_ok=True)
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
//! Richtet den Cron-Fallback für den periodischen `watch`-Lauf automatisch ein, sofern kein
|
||||
//! systemd verfügbar ist - kein `smart-mount service`-Subcommand mehr (siehe
|
||||
//! `smart_mount::cli::Commands`), sondern ein separates, nicht auf `PATH` liegendes
|
||||
//! Hilfsprogramm (installiert nach `/usr/lib/smart-mount/setup-cron`), das ausschließlich vom
|
||||
//! postinst-Skript der .deb/.rpm/.pkg.tar.zst-Pakete mit dem Argument `install` aufgerufen wird
|
||||
//! (siehe `packaging/deb/postinst`, `[package.metadata.generate-rpm].post_install_script` in
|
||||
//! `Cargo.toml`, sowie `scripts/package-arch.py`). Das Entfernen eines zuvor installierten
|
||||
//! Cron-Eintrags beim Deinstallieren übernehmen die jeweiligen postrm-Skripte/Scriptlets direkt
|
||||
//! (ein einfaches `rm -f`) statt dieses Programm mit `remove` aufzurufen: zum Zeitpunkt, zu dem
|
||||
//! `postrm remove` bei Debian bzw. `%postun` bei RPM laufen, sind die Paketdateien - und damit
|
||||
//! auch dieses Hilfsprogramm selbst - bereits von der Platte entfernt.
|
||||
//!
|
||||
//! Nutzt `smart_mount::systemd`/`smart_mount::config` als einzige Quelle der
|
||||
//! Cron-Installationslogik (kein separat gepflegtes Duplikat in Shell), da beide als `pub mod`
|
||||
//! Teil der Library sind und so auch von diesem zusätzlichen Binary-Target aus der
|
||||
//! Cargo-eigenen `src/bin/`-Autodiscovery erreichbar sind.
|
||||
|
||||
use smart_mount::config;
|
||||
use smart_mount::systemd;
|
||||
|
||||
fn main() {
|
||||
// Ein zusätzlicher Cron-Eintrag wäre redundant/unerwünscht, wenn systemd verfügbar ist - die
|
||||
// paketierten systemd-Units (siehe packaging/systemd/) übernehmen dann bereits alles.
|
||||
if systemd::is_available() {
|
||||
return;
|
||||
}
|
||||
|
||||
config::init();
|
||||
let watch_interval_secs = config::pairs::load()
|
||||
.map(|cfg| cfg.settings.watch_interval_secs)
|
||||
.unwrap_or(120);
|
||||
|
||||
match systemd::install_cron(watch_interval_secs) {
|
||||
Ok(systemd::CronInstallOutcome::SystemFile(path)) => {
|
||||
println!(
|
||||
"smart-mount: cron fallback installed at '{}'",
|
||||
path.display()
|
||||
);
|
||||
}
|
||||
Ok(systemd::CronInstallOutcome::Unavailable) => {
|
||||
eprintln!(
|
||||
"smart-mount: neither systemd nor '/etc/cron.d' found - the periodic 'watch' \
|
||||
call must be set up manually, e.g.:\n{}",
|
||||
systemd::crontab_equivalent(watch_interval_secs)
|
||||
);
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("smart-mount: could not install the cron fallback: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@
|
||||
pub mod doctor;
|
||||
pub mod drive;
|
||||
pub mod mount_cmd;
|
||||
pub mod service;
|
||||
pub mod status;
|
||||
pub mod watch;
|
||||
|
||||
@@ -74,13 +73,6 @@ pub enum Commands {
|
||||
},
|
||||
/// A single reconcile pass (local/cloud switching) - meant for systemd timers/cron.
|
||||
Watch,
|
||||
/// Sets up (or removes) the cron fallback for systems without (or not using) systemd -
|
||||
/// the systemd service itself is installed/removed automatically by the .deb/.rpm/
|
||||
/// .pkg.tar.zst package, not via this CLI.
|
||||
Service {
|
||||
#[command(subcommand)]
|
||||
action: service::ServiceAction,
|
||||
},
|
||||
/// Checks prerequisites (binaries, scheduler).
|
||||
Doctor {
|
||||
/// Output as JSON instead of text - for scripts.
|
||||
@@ -98,7 +90,6 @@ fn command_label(cmd: &Commands) -> &'static str {
|
||||
Commands::Unmount { .. } => "unmount",
|
||||
Commands::Status { .. } => "status",
|
||||
Commands::Watch => "watch",
|
||||
Commands::Service { .. } => "service",
|
||||
Commands::Doctor { .. } => "doctor",
|
||||
}
|
||||
}
|
||||
@@ -115,7 +106,6 @@ pub async fn dispatch(cli: Cli) -> anyhow::Result<()> {
|
||||
Commands::Unmount { name, all } => mount_cmd::run_unmount(name, all).await,
|
||||
Commands::Status { name, json } => status::run(name, json).await,
|
||||
Commands::Watch => watch::run().await,
|
||||
Commands::Service { action } => service::run(action),
|
||||
Commands::Doctor { json } => doctor::run(json).await,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
//! `smart-mount service crontab [--remove]`.
|
||||
|
||||
use clap::Subcommand;
|
||||
|
||||
use crate::config;
|
||||
use crate::systemd;
|
||||
|
||||
#[derive(Subcommand)]
|
||||
pub enum ServiceAction {
|
||||
/// Sets up (or, with '--remove', tears down) periodic execution via
|
||||
/// '/etc/cron.d/smart-mount' - a manual fallback for systems that don't use the packaged
|
||||
/// systemd service (see 'packaging/systemd/' in the source tree). Requires root, same as
|
||||
/// 'mount'/'watch' - mounting always runs as root.
|
||||
Crontab {
|
||||
/// Removes a previously installed cron entry instead of installing one.
|
||||
#[arg(long)]
|
||||
remove: bool,
|
||||
},
|
||||
}
|
||||
|
||||
pub fn run(action: ServiceAction) -> anyhow::Result<()> {
|
||||
crate::cli::require_root("service crontab")?;
|
||||
match action {
|
||||
ServiceAction::Crontab { remove: false } => install(),
|
||||
ServiceAction::Crontab { remove: true } => uninstall(),
|
||||
}
|
||||
}
|
||||
|
||||
fn install() -> anyhow::Result<()> {
|
||||
let cfg = config::pairs::load()?;
|
||||
let interval = cfg.settings.watch_interval_secs;
|
||||
|
||||
match systemd::install_cron(interval)? {
|
||||
systemd::CronInstallOutcome::SystemFile(path) => {
|
||||
logger_ctdra::info(
|
||||
"service",
|
||||
&format!("cron entry written: {}", path.display()),
|
||||
);
|
||||
println!("Cron entry written: {}", path.display());
|
||||
}
|
||||
systemd::CronInstallOutcome::Unavailable => {
|
||||
println!(
|
||||
"No cron mechanism found ('/etc/cron.d' is missing) - here are the lines for manual entry:"
|
||||
);
|
||||
print!("{}", systemd::crontab_equivalent(interval));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn uninstall() -> anyhow::Result<()> {
|
||||
match systemd::uninstall_cron()? {
|
||||
systemd::CronUninstallOutcome::Removed => {
|
||||
logger_ctdra::info("service", "cron entry removed");
|
||||
println!("Cron entry removed.");
|
||||
}
|
||||
systemd::CronUninstallOutcome::NotPresent => {
|
||||
println!("Nothing to remove - no cron entry was installed.")
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
+218
-19
@@ -1,12 +1,86 @@
|
||||
//! Diagnose-Checks für `smart-mount doctor` - prüft die im Laufe der Entwicklung
|
||||
//! angesammelten Voraussetzungen (Binaries, Scheduler) gebündelt an einer Stelle, statt sie
|
||||
//! einzeln erst beim Mount-Fehlschlag zu entdecken.
|
||||
//!
|
||||
//! Installationshinweise (fehlendes Paket, Scheduler-Fallback) sind bewusst je nach
|
||||
//! erkannter Distribution unterschiedlich (siehe [`PackageFamily`]): auf einem Debian-System
|
||||
//! soll `doctor` nur den `apt`-Hinweis zeigen, nicht zusätzlich Fedora/Arch-Varianten, die dort
|
||||
//! ohnehin nicht anwendbar sind.
|
||||
|
||||
use std::collections::HashSet;
|
||||
|
||||
use crate::config::{AppConfig, LocalAddress, MountKind};
|
||||
use crate::mount;
|
||||
|
||||
/// Grob erkannte Paketmanager-Familie des laufenden Systems, allein zur Auswahl der passenden
|
||||
/// Installationshinweise in `doctor` - keine vollständige Distributions-Erkennung.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum PackageFamily {
|
||||
/// Debian, Ubuntu und Ableitungen (`apt`).
|
||||
Debian,
|
||||
/// Fedora, RHEL/CentOS und Ableitungen (`dnf`).
|
||||
Fedora,
|
||||
/// Arch Linux und Ableitungen (Manjaro, EndeavourOS, ...) (`pacman`).
|
||||
Arch,
|
||||
/// Nicht erkannt - Installationshinweise bleiben generisch (nur Paketname, kein Befehl).
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl PackageFamily {
|
||||
fn install_command(self, package: &str) -> Option<String> {
|
||||
match self {
|
||||
PackageFamily::Debian => Some(format!("apt install {package}")),
|
||||
PackageFamily::Fedora => Some(format!("dnf install {package}")),
|
||||
PackageFamily::Arch => Some(format!("pacman -S {package}")),
|
||||
PackageFamily::Unknown => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Liest `/etc/os-release` (auf praktisch jedem modernen Linux-System vorhanden, siehe
|
||||
/// `man os-release`) und leitet daraus die [`PackageFamily`] ab.
|
||||
fn detect_package_family() -> PackageFamily {
|
||||
std::fs::read_to_string("/etc/os-release")
|
||||
.map(|contents| parse_package_family(&contents))
|
||||
.unwrap_or(PackageFamily::Unknown)
|
||||
}
|
||||
|
||||
/// Reine, testbare Parse-Funktion: sammelt `ID=`/`ID_LIKE=` aus dem `os-release`-Inhalt (deckt
|
||||
/// z. B. Ubuntu `ID=ubuntu ID_LIKE=debian` oder Manjaro `ID=manjaro ID_LIKE=arch` mit ab, nicht
|
||||
/// nur die jeweilige "reine" Distribution) und ordnet sie grob einer [`PackageFamily`] zu.
|
||||
fn parse_package_family(os_release: &str) -> PackageFamily {
|
||||
let mut fields = String::new();
|
||||
for line in os_release.lines() {
|
||||
if let Some(value) = line
|
||||
.strip_prefix("ID=")
|
||||
.or_else(|| line.strip_prefix("ID_LIKE="))
|
||||
{
|
||||
fields.push(' ');
|
||||
fields.push_str(value.trim_matches('"'));
|
||||
}
|
||||
}
|
||||
let fields = fields.to_lowercase();
|
||||
if fields.contains("debian") {
|
||||
PackageFamily::Debian
|
||||
} else if fields.contains("fedora") || fields.contains("rhel") {
|
||||
PackageFamily::Fedora
|
||||
} else if fields.contains("arch") {
|
||||
PackageFamily::Arch
|
||||
} else {
|
||||
PackageFamily::Unknown
|
||||
}
|
||||
}
|
||||
|
||||
/// Formatiert einen Installationshinweis für `package`, inkl. des passenden Befehls für die
|
||||
/// erkannte [`PackageFamily`] (kein Befehl bei [`PackageFamily::Unknown`] - dann nur der
|
||||
/// Paketname, ohne eine der drei Varianten zu raten).
|
||||
fn install_hint(package: &str, family: PackageFamily) -> String {
|
||||
match family.install_command(package) {
|
||||
Some(cmd) => format!("install package '{package}' ({cmd})"),
|
||||
None => format!("install package '{package}'"),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum CheckStatus {
|
||||
Ok,
|
||||
@@ -45,16 +119,17 @@ fn fail(label: impl Into<String>, detail: impl Into<String>) -> CheckResult {
|
||||
|
||||
/// Führt alle Checks gegen die aktuelle Konfiguration aus.
|
||||
pub fn run_checks(cfg: &AppConfig) -> Vec<CheckResult> {
|
||||
let family = detect_package_family();
|
||||
let mut results = Vec::new();
|
||||
|
||||
results.push(check_timeout_binary());
|
||||
results.push(check_scheduler());
|
||||
results.push(check_scheduler(family));
|
||||
results.push(check_mount_base_dir(&cfg.settings.mount_base_dir));
|
||||
|
||||
let used_kinds = used_mount_kinds(cfg);
|
||||
for kind in [MountKind::WebDav, MountKind::Smb, MountKind::Nfs] {
|
||||
if used_kinds.contains(&kind) {
|
||||
results.push(check_backend(kind));
|
||||
results.push(check_backend(kind, family));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,35 +181,99 @@ fn check_binary(name: &str, binary: &str, install_hint: &str) -> CheckResult {
|
||||
}
|
||||
}
|
||||
|
||||
fn check_backend(kind: MountKind) -> CheckResult {
|
||||
fn check_backend(kind: MountKind, family: PackageFamily) -> CheckResult {
|
||||
let backend = mount::backend_for(kind);
|
||||
match backend.check_available() {
|
||||
Ok(()) => ok(format!("Backend: {}", backend.name()), "available"),
|
||||
Err(e) => fail(format!("Backend: {}", backend.name()), e.to_string()),
|
||||
if backend.check_available().is_ok() {
|
||||
return ok(format!("Backend: {}", backend.name()), "available");
|
||||
}
|
||||
|
||||
let (missing_binary, package) = match kind {
|
||||
MountKind::WebDav => ("mount.davfs", "davfs2"),
|
||||
MountKind::Smb => ("mount.cifs", "cifs-utils"),
|
||||
MountKind::Nfs => (
|
||||
"mount.nfs/mount.nfs4",
|
||||
match family {
|
||||
PackageFamily::Debian => "nfs-common",
|
||||
_ => "nfs-utils",
|
||||
},
|
||||
),
|
||||
};
|
||||
fail(
|
||||
format!("Backend: {}", backend.name()),
|
||||
format!(
|
||||
"'{missing_binary}' not found - {}",
|
||||
install_hint(package, family)
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fn check_scheduler() -> CheckResult {
|
||||
let systemd = crate::systemd::is_available();
|
||||
let cron_d = std::path::Path::new("/etc/cron.d").is_dir();
|
||||
let crontab = mount::binary_available("crontab");
|
||||
/// Prüft, ob die periodische Ausführung tatsächlich eingerichtet ist UND läuft - nicht nur, ob
|
||||
/// `systemctl`/ein Cron-Mechanismus grundsätzlich auf dem System vorhanden wäre (das allein
|
||||
/// sagt nichts darüber aus, ob smart-mounts eigene Units/Cron-Eintrag auch tatsächlich
|
||||
/// installiert sind, siehe [`crate::systemd::is_unit_installed`]/[`crate::systemd::is_cron_installed`]).
|
||||
fn check_scheduler(family: PackageFamily) -> CheckResult {
|
||||
let timer_installed = crate::systemd::is_unit_installed("smart-mount-watch.timer");
|
||||
let mount_installed = crate::systemd::is_unit_installed("smart-mount-mount.service");
|
||||
|
||||
if systemd {
|
||||
if timer_installed || mount_installed {
|
||||
let timer_enabled = crate::systemd::is_unit_enabled("smart-mount-watch.timer");
|
||||
let timer_active = crate::systemd::is_unit_active("smart-mount-watch.timer");
|
||||
let mount_enabled = crate::systemd::is_unit_enabled("smart-mount-mount.service");
|
||||
|
||||
return if timer_enabled && timer_active && mount_enabled {
|
||||
ok(
|
||||
"Scheduler",
|
||||
"systemd found - the packaged .deb/.rpm/.pkg.tar.zst installs/enables the smart-mount systemd service automatically",
|
||||
)
|
||||
} else if cron_d || crontab {
|
||||
warn(
|
||||
"Scheduler",
|
||||
"no systemd, but cron found - run 'smart-mount service crontab' to set up the periodic 'watch' call",
|
||||
"systemd: 'smart-mount-watch.timer' is enabled and running, 'smart-mount-mount.service' is enabled",
|
||||
)
|
||||
} else {
|
||||
warn(
|
||||
"Scheduler",
|
||||
format!(
|
||||
"systemd units installed but not fully set up (smart-mount-watch.timer: enabled={timer_enabled} running={timer_active}, smart-mount-mount.service: enabled={mount_enabled}) - try 'sudo systemctl enable --now smart-mount-mount.service smart-mount-watch.timer'"
|
||||
),
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
if crate::systemd::is_available() {
|
||||
return fail(
|
||||
"Scheduler",
|
||||
"systemd found, but smart-mount's systemd units are not installed - was the package installed correctly?",
|
||||
);
|
||||
}
|
||||
|
||||
if crate::systemd::is_cron_installed() {
|
||||
return if crate::systemd::is_cron_daemon_running() {
|
||||
ok(
|
||||
"Scheduler",
|
||||
"cron: '/etc/cron.d/smart-mount' is installed, cron daemon is running",
|
||||
)
|
||||
} else {
|
||||
warn(
|
||||
"Scheduler",
|
||||
"cron: '/etc/cron.d/smart-mount' is installed, but no cron daemon process was found - is cron running?",
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
if std::path::Path::new("/etc/cron.d").is_dir() {
|
||||
return warn(
|
||||
"Scheduler",
|
||||
"no systemd, cron is available but smart-mount's entry ('/etc/cron.d/smart-mount') is missing - was the package installed correctly?",
|
||||
);
|
||||
}
|
||||
|
||||
let cron_package = match family {
|
||||
PackageFamily::Debian => "cron",
|
||||
_ => "cronie",
|
||||
};
|
||||
fail(
|
||||
"Scheduler",
|
||||
"neither systemd nor cron found - periodic 'watch' must be set up manually",
|
||||
format!(
|
||||
"neither systemd nor cron found - periodic 'watch' must be set up manually ({})",
|
||||
install_hint(cron_package, family)
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn check_mount_base_dir(dir: &std::path::Path) -> CheckResult {
|
||||
@@ -165,6 +304,66 @@ mod tests {
|
||||
use crate::config::{CloudSide, DrivePair, GlobalSettings, LocalSide};
|
||||
use std::net::Ipv4Addr;
|
||||
|
||||
#[test]
|
||||
fn parse_package_family_detects_debian_and_derivatives() {
|
||||
assert_eq!(
|
||||
parse_package_family("ID=debian\nVERSION_ID=\"13\"\n"),
|
||||
PackageFamily::Debian
|
||||
);
|
||||
assert_eq!(
|
||||
parse_package_family("ID=ubuntu\nID_LIKE=debian\n"),
|
||||
PackageFamily::Debian
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_package_family_detects_fedora_and_rhel_derivatives() {
|
||||
assert_eq!(parse_package_family("ID=fedora\n"), PackageFamily::Fedora);
|
||||
assert_eq!(
|
||||
parse_package_family("ID=centos\nID_LIKE=\"rhel fedora\"\n"),
|
||||
PackageFamily::Fedora
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_package_family_detects_arch_and_derivatives() {
|
||||
assert_eq!(parse_package_family("ID=arch\n"), PackageFamily::Arch);
|
||||
assert_eq!(
|
||||
parse_package_family("ID=manjaro\nID_LIKE=arch\n"),
|
||||
PackageFamily::Arch
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_package_family_falls_back_to_unknown() {
|
||||
assert_eq!(parse_package_family(""), PackageFamily::Unknown);
|
||||
assert_eq!(parse_package_family("ID=gentoo\n"), PackageFamily::Unknown);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn install_hint_includes_package_manager_command_for_known_families() {
|
||||
assert_eq!(
|
||||
install_hint("davfs2", PackageFamily::Debian),
|
||||
"install package 'davfs2' (apt install davfs2)"
|
||||
);
|
||||
assert_eq!(
|
||||
install_hint("davfs2", PackageFamily::Fedora),
|
||||
"install package 'davfs2' (dnf install davfs2)"
|
||||
);
|
||||
assert_eq!(
|
||||
install_hint("davfs2", PackageFamily::Arch),
|
||||
"install package 'davfs2' (pacman -S davfs2)"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn install_hint_omits_command_for_unknown_family() {
|
||||
assert_eq!(
|
||||
install_hint("davfs2", PackageFamily::Unknown),
|
||||
"install package 'davfs2'"
|
||||
);
|
||||
}
|
||||
|
||||
fn sample_pair(owner_user: Option<&str>, mac: bool) -> DrivePair {
|
||||
DrivePair {
|
||||
id: "pair-1".into(),
|
||||
|
||||
+98
-21
@@ -1,13 +1,13 @@
|
||||
//! Cron-Fallback (`smart-mount service crontab`) für Systeme ohne (oder ohne genutzten)
|
||||
//! systemd.
|
||||
//! Cron-Fallback für Systeme ohne (oder ohne genutzten) systemd.
|
||||
//!
|
||||
//! Die eigentliche systemd-Einrichtung passiert NICHT mehr zur Laufzeit über dieses Modul,
|
||||
//! sondern über die paketierten, statischen Unit-Dateien (siehe `packaging/systemd/` im
|
||||
//! Quellbaum) sowie die postinst/postrm-Skripte der .deb/.rpm/.pkg.tar.zst-Pakete - smart-mount
|
||||
//! richtet sich beim Installieren des Pakets automatisch als System-systemd-Dienst ein und
|
||||
//! entfernt sich beim Deinstallieren wieder. Dieses Modul bleibt für Systeme ohne systemd (oder
|
||||
//! zum bewussten Umgehen von systemd) als manueller Cron-Weg bestehen - immer systemweit
|
||||
//! (`/etc/cron.d/smart-mount`), da Mounten ohnehin immer Root-Rechte braucht (siehe
|
||||
//! Die eigentliche systemd-Einrichtung passiert NICHT zur Laufzeit über dieses Modul, sondern
|
||||
//! über die paketierten, statischen Unit-Dateien (siehe `packaging/systemd/` im Quellbaum)
|
||||
//! sowie die postinst/postrm-Skripte der .deb/.rpm/.pkg.tar.zst-Pakete. Dieses Modul stellt die
|
||||
//! Logik für den Cron-Fallback bereit - aufgerufen wird sie NICHT interaktiv über ein
|
||||
//! `smart-mount`-Subcommand, sondern automatisch vom separaten `setup-cron`-Hilfsprogramm
|
||||
//! (siehe `src/bin/setup-cron.rs`), das ebenfalls über die postinst-Skripte der Pakete beim
|
||||
//! Installieren läuft, sofern kein systemd verfügbar ist (siehe [`is_available`]). Immer
|
||||
//! systemweit (`/etc/cron.d/smart-mount`), da Mounten ohnehin immer Root-Rechte braucht (siehe
|
||||
//! [`crate::cli::require_root`]); eine persönliche Nutzer-Crontab liefe ins Leere.
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
@@ -18,19 +18,96 @@ const CRON_D_PATH: &str = "/etc/cron.d/smart-mount";
|
||||
const CRON_BEGIN_MARKER: &str = "# BEGIN smart-mount managed block";
|
||||
const CRON_END_MARKER: &str = "# END smart-mount managed block";
|
||||
|
||||
fn binary_path() -> String {
|
||||
std::env::current_exe()
|
||||
.ok()
|
||||
.and_then(|p| p.to_str().map(str::to_string))
|
||||
.unwrap_or_else(|| "/usr/bin/smart-mount".to_string())
|
||||
/// Fester Installationspfad der Haupt-Binary (siehe `[package.metadata.deb/generate-rpm].assets`
|
||||
/// bzw. `scripts/package-arch.py`, die immer nach `/usr/bin/<name>` installieren) - smart-mount
|
||||
/// läuft ausschließlich als paketierter System-Dienst, es gibt keinen anderen Installationsort.
|
||||
/// Bewusst NICHT über `std::env::current_exe()`: der Cron-Eintrag wird vom separaten
|
||||
/// `setup-cron`-Hilfsprogramm geschrieben (siehe [`crate::systemd`]-Moduldoku), dessen eigener
|
||||
/// Pfad hier falsch wäre.
|
||||
fn binary_path() -> &'static str {
|
||||
"/usr/bin/smart-mount"
|
||||
}
|
||||
|
||||
/// Ob `systemctl` auf diesem System vorhanden ist - rein informativ für `doctor`; die
|
||||
/// eigentliche systemd-Einrichtung läuft über die Paketierung (siehe Moduldoku).
|
||||
/// Ob `systemctl` auf diesem System vorhanden ist - entscheidet für `doctor` UND für
|
||||
/// `setup-cron` (siehe Moduldoku), ob der Cron-Fallback überhaupt installiert werden soll: die
|
||||
/// eigentliche systemd-Einrichtung läuft über die Paketierung, ein zusätzlicher Cron-Eintrag
|
||||
/// wäre dort redundant.
|
||||
pub fn is_available() -> bool {
|
||||
crate::mount::binary_available("systemctl")
|
||||
}
|
||||
|
||||
/// `systemctl is-enabled`/`is-active <unit>` liefert bei Erfolg wie bei Misserfolg exakt eine
|
||||
/// Ausgabezeile auf stdout (z. B. "enabled"/"disabled"/"static"/"not-found" bzw.
|
||||
/// "active"/"inactive"/"failed") - der Exit-Code allein reicht nicht (z. B. ist `is-active`
|
||||
/// auch für eine deaktivierte, aber existierende Unit `1`). `None` bei fehlendem `systemctl`
|
||||
/// oder einem sonstigen Ausführungsfehler.
|
||||
fn systemctl_query(subcommand: &str, unit: &str) -> Option<String> {
|
||||
std::process::Command::new("systemctl")
|
||||
.args([subcommand, unit])
|
||||
.output()
|
||||
.ok()
|
||||
.map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string())
|
||||
}
|
||||
|
||||
/// Ob `systemctl` die Unit überhaupt kennt (eine geladene Unit-Datei existiert), unabhängig
|
||||
/// davon, ob sie aktiviert/aktiv ist - für `doctor`, um "installiert, aber nicht
|
||||
/// aktiviert/gestartet" von "gar nicht installiert" unterscheiden zu können. Eine komplett
|
||||
/// unbekannte Unit liefert bei `is-enabled` je nach systemd-Version "not-found" oder eine leere
|
||||
/// Ausgabe (Fehler auf stderr) - beides ungleich einem der hier aufgeführten, eine tatsächlich
|
||||
/// geladene Unit-Datei voraussetzenden Zustände.
|
||||
pub fn is_unit_installed(unit: &str) -> bool {
|
||||
matches!(
|
||||
systemctl_query("is-enabled", unit).as_deref(),
|
||||
Some(
|
||||
"enabled"
|
||||
| "disabled"
|
||||
| "static"
|
||||
| "alias"
|
||||
| "linked"
|
||||
| "generated"
|
||||
| "transient"
|
||||
| "masked"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/// Ob die Unit dauerhaft aktiviert ist (startet automatisch, siehe `man systemctl`,
|
||||
/// Abschnitt "is-enabled").
|
||||
pub fn is_unit_enabled(unit: &str) -> bool {
|
||||
systemctl_query("is-enabled", unit).as_deref() == Some("enabled")
|
||||
}
|
||||
|
||||
/// Ob die Unit aktuell läuft (bei einem Timer: wartet auf die nächste Auslösung). Bei einem
|
||||
/// `oneshot`-Service wie `smart-mount-mount.service` (läuft einmal beim Boot und beendet sich
|
||||
/// danach) ist "inactive" direkt nach einem erfolgreichen Lauf der korrekte, erwartete Zustand -
|
||||
/// dafür ist [`is_unit_installed`]/[`is_unit_enabled`] aussagekräftiger.
|
||||
pub fn is_unit_active(unit: &str) -> bool {
|
||||
systemctl_query("is-active", unit).as_deref() == Some("active")
|
||||
}
|
||||
|
||||
/// Ob ein von [`install_cron`] verwalteter Cron-Eintrag aktuell existiert - für `doctor`, um
|
||||
/// zwischen "kein Cron-Mechanismus vorhanden" und "vorhanden, aber smart-mounts Eintrag fehlt"
|
||||
/// (z. B. weil das Paket nicht sauber installiert wurde) unterscheiden zu können.
|
||||
pub fn is_cron_installed() -> bool {
|
||||
Path::new(CRON_D_PATH).exists()
|
||||
}
|
||||
|
||||
/// Ob aktuell ein Cron-Daemon-Prozess läuft - rein prozessbasiert über `pgrep` geprüft (statt
|
||||
/// z. B. über `systemctl is-active cron.service`), damit es unabhängig davon funktioniert, ob
|
||||
/// systemd überhaupt vorhanden ist: genau im Cron-Fallback-Fall (kein/kein genutztes systemd,
|
||||
/// siehe Moduldoku) ist das der einzige portable Weg. Prüft beide gängigen Prozessnamen
|
||||
/// (Debian/Ubuntu: `cron`, Fedora/Arch: `crond`), ohne selbst eine Distributions-Erkennung zu
|
||||
/// brauchen.
|
||||
pub fn is_cron_daemon_running() -> bool {
|
||||
["cron", "crond"].iter().any(|name| {
|
||||
std::process::Command::new("pgrep")
|
||||
.args(["-x", name])
|
||||
.output()
|
||||
.map(|o| o.status.success())
|
||||
.unwrap_or(false)
|
||||
})
|
||||
}
|
||||
|
||||
/// Erzeugt die Crontab-Äquivalente zu den paketierten systemd-Units, für Systeme ohne
|
||||
/// (genutzten) systemd. `watch_interval_secs` ist derselbe Wert wie
|
||||
/// `settings.watch_interval_secs`.
|
||||
@@ -49,11 +126,11 @@ pub enum CronInstallOutcome {
|
||||
Unavailable,
|
||||
}
|
||||
|
||||
/// Richtet die periodische Ausführung direkt über `/etc/cron.d/smart-mount` ein - manueller
|
||||
/// Fallback für Systeme ohne (genutzten) systemd, sofern `/etc/cron.d` existiert - sonst
|
||||
/// [`CronInstallOutcome::Unavailable`] statt eines Fehlers, der Aufrufer zeigt dann
|
||||
/// [`crontab_equivalent`] zur manuellen Einrichtung. Erfordert Root (siehe Moduldoku) - der
|
||||
/// Aufrufer (`cli::service`) prüft das bereits vorab.
|
||||
/// Richtet die periodische Ausführung direkt über `/etc/cron.d/smart-mount` ein - Fallback für
|
||||
/// Systeme ohne (genutzten) systemd, sofern `/etc/cron.d` existiert - sonst
|
||||
/// [`CronInstallOutcome::Unavailable`] statt eines Fehlers, der Aufrufer (`setup-cron`, siehe
|
||||
/// Moduldoku) zeigt dann [`crontab_equivalent`] zur manuellen Einrichtung. Erfordert Root
|
||||
/// (siehe Moduldoku) - beim Aufruf aus dem postinst-Skript der Pakete ohnehin gegeben.
|
||||
pub fn install_cron(watch_interval_secs: u64) -> Result<CronInstallOutcome> {
|
||||
if !Path::new("/etc/cron.d").is_dir() {
|
||||
return Ok(CronInstallOutcome::Unavailable);
|
||||
|
||||
Reference in New Issue
Block a user