Avvisa se server backup non raggiungibile

This commit is contained in:
2026-07-01 12:51:32 +02:00
parent 3573b4c149
commit ad7265183c
8 changed files with 70 additions and 17 deletions

View File

@@ -46,6 +46,12 @@ python -m bakrest.tray_app
La tray app legge lo stato prodotto dal servizio e offre il comando `Backup e spegni`. Se il backup fallisce, lo spegnimento non viene eseguito.
Quando il servizio rileva che il server di backup non e' raggiungibile, la tray app mostra una finestra con:
```text
Accendi il server di backup o verifica che sia connesso alla rete
```
## Backup senza GUI per Task Scheduler
Lo stesso entry point della tray puo' eseguire il backup senza mostrare icone o finestre:
@@ -93,6 +99,7 @@ robocopy_path = "robocopy"
[backup.server_check]
type = "tcp"
port = 445
interval_seconds = 600
```
La copia ricrea il percorso relativo sotto ogni share. Se una cartella monitorata e' `D:\Lavori`, il file `D:\Lavori\Cliente\a.psd` viene copiato in:

View File

@@ -37,6 +37,8 @@ Il servizio Windows deve:
- verificare periodicamente la raggiungibilita' del server di backup;
- segnalare lo stato del server alla tray app.
Il controllo di raggiungibilita' del server deve avvenire ogni 10 minuti, salvo diversa configurazione.
La libreria Python consigliata per il monitoraggio e' `watchdog`, perche' su Windows usa API native di sistema ed evita un polling continuo e inefficiente.
### Tray app
@@ -177,6 +179,12 @@ La tray app deve mostrare chiaramente se il server risulta spento o non raggiung
La non raggiungibilita' del server deve essere registrata nei log giornalieri.
Poiche' un servizio Windows non deve aprire direttamente finestre nella sessione utente, la finestra di avviso deve essere mostrata dalla tray app quando legge uno stato server non raggiungibile. Il messaggio richiesto e':
```text
Accendi il server di backup o verifica che sia connesso alla rete
```
## Strategia di monitoraggio
Non e' consigliato monitorare tutto il disco filtrando solo per estensione, ad esempio `C:\` con filtro `.jpg`, `.png`, `.mp4`. Questo approccio puo' generare molti eventi inutili, includere directory di sistema, rallentare il sistema e creare un registro troppo rumoroso.

View File

@@ -57,7 +57,7 @@ shutdown_command = "shutdown /s /t 0"
[backup.server_check]
type = "tcp"
port = 445
interval_seconds = 300
interval_seconds = 600
timeout_seconds = 3
[stability]

View File

@@ -123,6 +123,9 @@ def migrate_config_data(data: dict[str, Any]) -> bool:
if isinstance(server_check, dict) and int(server_check.get("port", 22)) == 22:
server_check["port"] = 445
changed = True
if isinstance(server_check, dict) and int(server_check.get("interval_seconds", 300)) == 300:
server_check["interval_seconds"] = 600
changed = True
destinations = backup.get("remote_destinations")
if not isinstance(destinations, list):

View File

@@ -57,7 +57,7 @@ shutdown_command = "shutdown /s /t 0"
[backup.server_check]
type = "tcp"
port = 445
interval_seconds = 300
interval_seconds = 600
timeout_seconds = 3
[stability]

View File

@@ -4,6 +4,7 @@ import argparse
import json
import logging
import threading
import time
import tkinter as tk
from tkinter import messagebox
from pathlib import Path
@@ -23,6 +24,8 @@ class BakRestTrayApp:
self.config = config
self.logger = logger
self.registry = ChangeRegistry(config.storage.change_registry)
self.stop_event = threading.Event()
self.last_alert_reachable: bool | None = None
self.icon = pystray.Icon(
"BakRestTray",
_create_icon_image(),
@@ -32,6 +35,7 @@ class BakRestTrayApp:
def run(self) -> None:
self.logger.info("BakRestTray started")
threading.Thread(target=self._server_status_monitor, daemon=True).start()
self.icon.run()
def _build_menu(self) -> pystray.Menu:
@@ -49,14 +53,19 @@ class BakRestTrayApp:
return f"Server: {server} | File in attesa: {pending}"
def _read_server_status(self) -> str:
status = self._read_server_status_data()
if status is None:
return "sconosciuto"
return "raggiungibile" if status.get("reachable") else "non raggiungibile"
def _read_server_status_data(self) -> dict[str, object] | None:
path = self.config.storage.status_file
if not path.exists():
return "sconosciuto"
return None
try:
data = json.loads(path.read_text(encoding="utf-8"))
return json.loads(path.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError):
return "errore stato"
return "raggiungibile" if data.get("reachable") else "non raggiungibile"
return None
def _backup_and_shutdown(self, icon: pystray.Icon, item: pystray.MenuItem) -> None:
threading.Thread(target=self._backup_and_shutdown_worker, daemon=True).start()
@@ -81,8 +90,29 @@ class BakRestTrayApp:
def _quit(self, icon: pystray.Icon, item: pystray.MenuItem) -> None:
self.logger.info("BakRestTray stopped")
self.stop_event.set()
self.icon.stop()
def _server_status_monitor(self) -> None:
interval = min(max(self.config.backup.server_check.interval_seconds, 60), 600)
while not self.stop_event.wait(interval):
status = self._read_server_status_data()
if status is None:
continue
reachable = bool(status.get("reachable"))
if reachable:
self.last_alert_reachable = True
continue
if self.last_alert_reachable is not False:
self.last_alert_reachable = False
self.logger.warning("Showing backup server unreachable alert")
_show_error_message(
"Bak&Rest",
"Accendi il server di backup o verifica che sia connesso alla rete",
)
def _create_icon_image() -> Image.Image:
image = Image.new("RGBA", (64, 64), (26, 38, 52, 255))

View File

@@ -84,7 +84,11 @@ class WatchdogRuntime:
def _run_health_loop(self) -> None:
interval = self.config.backup.server_check.interval_seconds
self._publish_server_status()
while not self.stop_event.wait(interval):
self._publish_server_status()
def _publish_server_status(self) -> None:
status = check_server(self.config.backup)
pending_count = self.registry.pending_count()
write_status(self.config.storage.status_file, status, pending_count)

View File

@@ -66,6 +66,7 @@ def test_migrate_legacy_rsync_config_to_robocopy_defaults() -> None:
assert data["backup"]["engine"] == "robocopy"
assert data["backup"]["robocopy_path"] == "robocopy"
assert data["backup"]["server_check"]["port"] == 445
assert data["backup"]["server_check"]["interval_seconds"] == 600
assert data["backup"]["remote_destinations"] == []