From d5bff3d8e47598c0e78ac2dccff2c1e8490fb34e Mon Sep 17 00:00:00 2001 From: allebonvi Date: Wed, 1 Jul 2026 12:27:59 +0200 Subject: [PATCH] Aggiunge backup no-GUI per scheduler --- README.md | 38 +++++++++++++++++++++++++++ SPECIFICHE.md | 3 +++ src/bakrest/backup_nogui.py | 52 +++++++++++++++++++++++++++++++++++++ src/bakrest/tray_app.py | 19 +++++++++++++- tests/test_backup_nogui.py | 10 +++++++ 5 files changed, 121 insertions(+), 1 deletion(-) create mode 100644 src/bakrest/backup_nogui.py create mode 100644 tests/test_backup_nogui.py diff --git a/README.md b/README.md index 2cf4f66..4c0c8d4 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,44 @@ 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. +## Backup senza GUI per Task Scheduler + +Lo stesso entry point della tray puo' eseguire il backup senza mostrare icone o finestre: + +```powershell +python -m bakrest.tray_app --nogui +``` + +Per testare senza spegnere il PC: + +```powershell +python -m bakrest.tray_app --nogui --no-shutdown +``` + +In alternativa si puo' chiamare direttamente il modulo dedicato: + +```powershell +python -m bakrest.backup_nogui +``` + +Questo percorso e' pensato per Task Scheduler, per esempio su evento di disconnessione o fine sessione. Se il server non e' raggiungibile o il backup fallisce, il comando termina con errore e non spegne il PC. + +Azione Task Scheduler consigliata in sviluppo: + +```text +Programma: C:\Python314\python.exe +Argomenti: -m bakrest.tray_app --nogui +Avvia in: C:\devel\bak&rest +``` + +Per una prova senza spegnimento: + +```text +Programma: C:\Python314\python.exe +Argomenti: -m bakrest.tray_app --nogui --no-shutdown +Avvia in: C:\devel\bak&rest +``` + ## Avvio GUI configurazione ```powershell diff --git a/SPECIFICHE.md b/SPECIFICHE.md index 3d3fad4..1a250cb 100644 --- a/SPECIFICHE.md +++ b/SPECIFICHE.md @@ -58,6 +58,8 @@ La tray app deve: La tray app e' il componente piu' adatto per eseguire `rsync` e richiedere lo spegnimento, perche' queste sono azioni esplicite dell'utente. +La stessa logica di backup deve essere disponibile anche senza interfaccia grafica, tramite opzione `--nogui`, per permettere l'esecuzione da Task Scheduler alla disconnessione o fine sessione. In modalita' no-GUI non devono comparire icone tray o finestre di messaggio: gli errori devono essere registrati nei log e il processo deve restituire un codice di uscita diverso da zero. + ### GUI CustomTkinter La GUI di configurazione deve permettere di: @@ -398,6 +400,7 @@ dist/ BakRestWatchdog.exe BakRestTray.exe BakRestConfig.exe + BakRestNoGui.exe ``` ## Decisioni aperte diff --git a/src/bakrest/backup_nogui.py b/src/bakrest/backup_nogui.py new file mode 100644 index 0000000..d8ce757 --- /dev/null +++ b/src/bakrest/backup_nogui.py @@ -0,0 +1,52 @@ +from __future__ import annotations + +import argparse + +from .backup import run_backup, shutdown_after_backup +from .config import load_config +from .logging_setup import close_logger, configure_logging + + +def run_nogui_backup(shutdown: bool = True) -> int: + config = load_config() + logger = configure_logging(config.storage.logs_dir, "backup-nogui") + logger.info("BakRest no-GUI backup started") + try: + result = run_backup(config, logger) + if not result.success: + logger.error("BakRest no-GUI backup failed: %s", result.message) + return _exit_code_for_error(result.error_code) + + logger.info("BakRest no-GUI backup completed: %s", result.message) + if shutdown: + shutdown_after_backup(config, logger) + else: + logger.info("Shutdown skipped by command line") + return 0 + finally: + close_logger(logger) + + +def main() -> int: + parser = argparse.ArgumentParser(description="Run Bak&Rest backup without GUI") + parser.add_argument( + "--no-shutdown", + action="store_true", + help="Run the backup but do not shut down the computer", + ) + args = parser.parse_args() + return run_nogui_backup(shutdown=not args.no_shutdown) + + +def _exit_code_for_error(error_code: str | None) -> int: + if error_code == "server_unreachable": + return 20 + if error_code == "rsync_start_failed": + return 30 + if error_code == "rsync_failed": + return 31 + return 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/src/bakrest/tray_app.py b/src/bakrest/tray_app.py index 956d8d8..d3a327f 100644 --- a/src/bakrest/tray_app.py +++ b/src/bakrest/tray_app.py @@ -1,5 +1,6 @@ from __future__ import annotations +import argparse import json import logging import threading @@ -11,6 +12,7 @@ from PIL import Image, ImageDraw import pystray from .backup import run_backup, shutdown_after_backup +from .backup_nogui import run_nogui_backup from .config import AppConfig, load_config from .logging_setup import configure_logging from .registry import ChangeRegistry @@ -101,7 +103,22 @@ def _show_error_message(title: str, message: str) -> None: root.destroy() -def main() -> int: +def main(argv: list[str] | None = None) -> int: + parser = argparse.ArgumentParser(description="Bak&Rest tray app") + parser.add_argument( + "--nogui", + action="store_true", + help="Run backup and shutdown without tray icon", + ) + parser.add_argument( + "--no-shutdown", + action="store_true", + help="With --nogui, run the backup but do not shut down the computer", + ) + args = parser.parse_args(argv) + if args.nogui: + return run_nogui_backup(shutdown=not args.no_shutdown) + config = load_config() logger = configure_logging(config.storage.logs_dir, "tray") app = BakRestTrayApp(config, logger) diff --git a/tests/test_backup_nogui.py b/tests/test_backup_nogui.py new file mode 100644 index 0000000..c92efd0 --- /dev/null +++ b/tests/test_backup_nogui.py @@ -0,0 +1,10 @@ +from __future__ import annotations + +from bakrest.backup_nogui import _exit_code_for_error + + +def test_nogui_exit_codes_are_specific() -> None: + assert _exit_code_for_error("server_unreachable") == 20 + assert _exit_code_for_error("rsync_start_failed") == 30 + assert _exit_code_for_error("rsync_failed") == 31 + assert _exit_code_for_error(None) == 1