diff --git a/src/bakrest/backup.py b/src/bakrest/backup.py
index c781598..9e3d594 100644
--- a/src/bakrest/backup.py
+++ b/src/bakrest/backup.py
@@ -10,7 +10,7 @@ from pathlib import Path
from .config import AppConfig
from .registry import ChangeEvent, ChangeRegistry
-from .server_check import check_server
+from .server_check import check_backup_target
@dataclass(frozen=True)
@@ -23,7 +23,7 @@ class BackupResult:
def run_backup(config: AppConfig, logger: logging.Logger) -> BackupResult:
- status = check_server(config.backup)
+ status = check_backup_target(config.backup)
if not status.reachable:
message = f"Server backup non raggiungibile: {status.message}"
logger.error(message)
diff --git a/src/bakrest/server_check.py b/src/bakrest/server_check.py
index 8aa3f79..04a4552 100644
--- a/src/bakrest/server_check.py
+++ b/src/bakrest/server_check.py
@@ -2,6 +2,7 @@ from __future__ import annotations
import json
import socket
+import subprocess
from dataclasses import asdict, dataclass
from datetime import datetime, timezone
from pathlib import Path
@@ -31,6 +32,49 @@ def check_server(config: BackupConfig) -> ServerStatus:
return ServerStatus(False, checked_at, str(exc))
+def check_backup_target(config: BackupConfig) -> ServerStatus:
+ if config.engine != "robocopy":
+ return check_server(config)
+
+ checked_at = datetime.now(timezone.utc).isoformat()
+ if not config.remote_destinations:
+ return ServerStatus(False, checked_at, "No backup shares configured")
+
+ server_status = check_server(config)
+ if not server_status.reachable:
+ return server_status
+
+ unreachable = [
+ destination
+ for destination in config.remote_destinations
+ if not share_is_reachable(destination, config.server_check.timeout_seconds)
+ ]
+ if unreachable:
+ return ServerStatus(
+ False,
+ checked_at,
+ "Backup share unreachable: " + ", ".join(unreachable),
+ )
+
+ return ServerStatus(True, checked_at, "Backup shares reachable")
+
+
+def share_is_reachable(destination: str, timeout_seconds: int) -> bool:
+ probe = destination.rstrip("\\/") + "\\."
+ command = ["cmd.exe", "/d", "/c", f'if exist "{probe}" (exit /b 0) else (exit /b 1)']
+ try:
+ completed = subprocess.run(
+ command,
+ capture_output=True,
+ text=True,
+ timeout=max(timeout_seconds, 1),
+ check=False,
+ )
+ except (OSError, subprocess.TimeoutExpired):
+ return False
+ return completed.returncode == 0
+
+
def write_status(path: Path, status: ServerStatus, pending_count: int) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
payload = asdict(status)
diff --git a/src/bakrest/server_notifier.py b/src/bakrest/server_notifier.py
index a1ffea3..3921ea5 100644
--- a/src/bakrest/server_notifier.py
+++ b/src/bakrest/server_notifier.py
@@ -9,7 +9,7 @@ from tkinter import messagebox
from .config import load_config
from .logging_setup import close_logger, configure_logging
from .registry import ChangeRegistry
-from .server_check import ServerStatus, check_server, write_status
+from .server_check import ServerStatus, check_backup_target, write_status
ALERT_MESSAGE = "Accendi il server di backup o verifica che sia connesso alla rete"
@@ -19,7 +19,7 @@ def run_server_notifier(always_alert: bool = False) -> int:
config = load_config()
logger = configure_logging(config.storage.logs_dir, "server-notifier")
try:
- status = check_server(config.backup)
+ status = check_backup_target(config.backup)
pending_count = ChangeRegistry(config.storage.change_registry).pending_count()
write_status(config.storage.status_file, status, pending_count)
diff --git a/tasks/BakRestBackupOnLogoff.xml b/tasks/BakRestBackupOnLogoff.xml
index b885288..976a4cc 100644
--- a/tasks/BakRestBackupOnLogoff.xml
+++ b/tasks/BakRestBackupOnLogoff.xml
@@ -39,7 +39,7 @@
C:\Python314\python.exe
-m bakrest.tray_app --nogui
- C:\devel\bak&rest
+ C:\programmi\bak-and-rest
diff --git a/tasks/BakRestServerNotifierEvery30Minutes.xml b/tasks/BakRestServerNotifierEvery30Minutes.xml
index 2c2acab..f2ed808 100644
--- a/tasks/BakRestServerNotifierEvery30Minutes.xml
+++ b/tasks/BakRestServerNotifierEvery30Minutes.xml
@@ -50,7 +50,7 @@
C:\Python314\python.exe
-m bakrest.server_notifier
- C:\devel\bak&rest
+ C:\programmi\bak-and-rest
diff --git a/tests/test_server_check.py b/tests/test_server_check.py
new file mode 100644
index 0000000..f72b1ff
--- /dev/null
+++ b/tests/test_server_check.py
@@ -0,0 +1,32 @@
+from __future__ import annotations
+
+import subprocess
+
+from bakrest.server_check import share_is_reachable
+
+
+def test_share_is_reachable_returns_true_on_zero_exit(monkeypatch) -> None:
+ def fake_run(*args, **kwargs):
+ return subprocess.CompletedProcess(args[0], 0)
+
+ monkeypatch.setattr("bakrest.server_check.subprocess.run", fake_run)
+
+ assert share_is_reachable("\\\\server\\share", 3)
+
+
+def test_share_is_reachable_returns_false_on_nonzero_exit(monkeypatch) -> None:
+ def fake_run(*args, **kwargs):
+ return subprocess.CompletedProcess(args[0], 1)
+
+ monkeypatch.setattr("bakrest.server_check.subprocess.run", fake_run)
+
+ assert not share_is_reachable("\\\\server\\share", 3)
+
+
+def test_share_is_reachable_returns_false_on_timeout(monkeypatch) -> None:
+ def fake_run(*args, **kwargs):
+ raise subprocess.TimeoutExpired(args[0], 3)
+
+ monkeypatch.setattr("bakrest.server_check.subprocess.run", fake_run)
+
+ assert not share_is_reachable("\\\\server\\share", 3)