Files
bak-and-rest/DOCUMENTAZIONE_TECNICA.md

14 KiB

Bak&Rest - Documentazione tecnica e decisioni

Documento aggiornato al 2026-07-02.

Questo file raccoglie lo stato tecnico del progetto, le scelte adottate e i problemi incontrati durante la messa a punto su Windows.

Obiettivo operativo

Bak&Rest deve proteggere i file di lavoro creati o modificati durante la giornata.

Il flusso scelto e':

  1. Il servizio Windows BakRestWatchdog resta attivo durante la sessione.
  2. Il servizio osserva le cartelle configurate e registra in SQLite i file da copiare.
  3. Il server slave viene controllato durante la giornata da un task separato.
  4. Alla disconnessione dell'utente, Task Scheduler lancia il backup no-GUI.
  5. Il backup copia i file in coda verso una o piu' share SMB con robocopy.
  6. Ogni file viene tolto dalla coda appena la copia di quel file riesce.
  7. Se il backup termina correttamente, il comando puo' spegnere il computer.

Componenti

  • BakRestWatchdog: servizio Windows di monitoraggio file.
  • BakRestTray: applicazione tray e entry point del backup no-GUI.
  • BakRestConfig: interfaccia CustomTkinter per configurazione e coda.
  • bakrest.server_notifier: controllo periodico della raggiungibilita' dello slave.
  • scripts/Register-BakRestTask.ps1: registrazione robusta dei task schedulati.
  • tasks/BakRestBackupOnLogoff.xml: task backup alla disconnessione.
  • tasks/BakRestServerNotifierEvery30Minutes.xml: task notifier ogni 30 minuti.

File e cartelle di lavoro

Percorsi principali:

C:\ProgramData\BakRest\config.toml
C:\ProgramData\BakRest\changes.sqlite
C:\ProgramData\BakRest\status.json
C:\ProgramData\BakRest\notifier-state.json
C:\ProgramData\BakRest\logs\YYYY-MM-DD-service.log
C:\ProgramData\BakRest\logs\YYYY-MM-DD-backup-nogui.log
C:\ProgramData\BakRest\logs\YYYY-MM-DD-server-notifier.log

Il repository di sviluppo e' C:\devel\bak&rest.

Il deploy usato nei test e' C:\Programmi\bak-and-rest.

Il repository remoto Gitea e':

https://gitea.alessandrobonvicini.it/administrator/bak-and-rest.git

Configurazione

La configurazione e' TOML.

La GUI si avvia con:

python -m bakrest.config_app

Campi importanti:

  • [watch].target_user: utente reale a cui riferire variabili come %USERPROFILE%.
  • [watch].include_dirs: cartelle monitorate.
  • [watch].include_extensions: estensioni incluse.
  • [watch].exclude_dirs: cartelle escluse.
  • [watch].exclude_patterns: pattern esclusi.
  • [backup].engine = "robocopy": motore backup scelto.
  • [backup].remote_destinations: share SMB di destinazione.
  • [backup.server_check].type: controllo raggiungibilita' server.
  • [stability]: regole per non copiare file ancora in scrittura.

Scelta: servizio come LocalSystem e target_user

Problema incontrato:

Il servizio non partiva stabilmente con l'account utente pettirosso, mentre partiva con LocalSystem.

Effetto collaterale:

LocalSystem non espande %USERPROFILE%, %APPDATA% e variabili simili come l'utente reale. Senza correzione sarebbe stato necessario scrivere nella configurazione molti percorsi assoluti, difficili da manutenere.

Soluzione adottata:

La configurazione supporta [watch].target_user.

Esempio:

[watch]
target_user = "pettirosso"
include_dirs = [
  "%USERPROFILE%\\Documents",
  "%USERPROFILE%\\Desktop",
]

Con questa impostazione il servizio puo' restare LocalSystem, ma i percorsi utente vengono risolti come C:\Users\pettirosso\....

Sono supportati anche token portabili:

%DOCUMENTS%
%DOWNLOADS%
%PICTURES%
%MUSIC%
%VIDEOS%
%FAVORITES%

Sono stati gestiti anche alias italiani come Documenti, Immagini, Musica, Video, mappandoli alle cartelle reali di Windows.

Scelta: watchdog piu' USN Journal

Problema incontrato:

Monitorando cartelle molto ampie, per esempio D:\, la semplice navigazione con Explorer puo' generare eventi modified non realmente legati a modifiche di contenuto. Esempi tipici: anteprime, icone, indicizzazione, metadati.

Soluzioni valutate:

  • Baseline completa del disco: scartata perche' con decine di migliaia di file diventa pesante e fragile.
  • Snapshot progressivo: scartato perche' una cartella visitata per la prima volta durante la giornata avrebbe comunque potuto generare falsi positivi.
  • Solo watchdog per estensione: scartato perche' troppo rumoroso.

Soluzione adottata:

Il servizio continua a usare watchdog per ricevere eventi efficienti dal sistema operativo, ma prima di mettere in coda un file consulta il journal USN NTFS.

Entrano in coda solo eventi con reason coerenti con modifiche reali di contenuto o creazione/rinomina utile:

DATA_OVERWRITE
DATA_EXTEND
DATA_TRUNCATION
FILE_CREATE
RENAME_NEW_NAME

Eventi di soli metadati, security, basic info, close handle o indicizzazione vengono scartati.

Se USN non e' disponibile su un volume, il servizio registra un warning e usa un fallback conservativo.

Comando utile per abilitare USN su un volume:

fsutil usn createjournal m=134217728 a=33554432 D:

Verifica:

fsutil usn queryjournal D:

Problemi risolti in questa area:

  • d: veniva interpretato diversamente da D:\.
  • c: e C: potevano generare doppie inizializzazioni del reader USN.
  • Alcune cartelle localizzate in italiano non esistevano come path fisico.

Correzioni adottate:

  • Normalizzazione dei drive in forma D:\.
  • Normalizzazione dei volumi USN in forma \\.\D:.
  • Risoluzione delle cartelle utente reali invece dei nomi localizzati visibili in Explorer.

Scelta: robocopy su share SMB

Problema iniziale:

Si era valutato rsync, ma lo slave e' un server Windows 10. Usare rsync avrebbe richiesto installazione e manutenzione anche lato server.

Soluzione adottata:

Il motore predefinito e' robocopy, gia' presente su Windows, verso share SMB:

[backup]
engine = "robocopy"
remote_destinations = [
  "\\\\old-pc-pettirosso\\newpc-backup",
]
robocopy_path = "robocopy"

Comportamento voluto:

  • solo copie master -> slave;
  • creazioni e modifiche vengono copiate;
  • cancellazioni sul master non vengono propagate;
  • nessun uso di /MIR;
  • nessun uso di /PURGE.

Ogni file viene copiato ricreando il percorso relativo.

Esempio:

Origine:      D:\MARIA FOTO e VIDEO\IMG_4509.JPG
Destinazione: \\old-pc-pettirosso\newpc-backup\MARIA FOTO e VIDEO\IMG_4509.JPG

Coda backup e aggiornamento progressivo

Problema:

Se la coda venisse ripulita solo alla fine del backup, l'utente non potrebbe capire se il backup sta davvero procedendo.

Soluzione adottata:

Il backup marca un file come copiato subito dopo che quel singolo file e' stato copiato con successo su tutte le destinazioni configurate.

Effetto:

La lista nella GUI decresce durante il backup.

La coda e' persistente: se il PC viene spento senza backup, i file restano in stato pending e si accumulano con quelli del giorno successivo.

La GUI permette selezione multipla nella coda. Quando si rimuovono file dalla coda, la rimozione e' immediata: non serve salvare la configurazione. La GUI puo' anche proporre di escludere dal backup la cartella che contiene i file selezionati.

Stabilita' file prima della copia

Problema:

File grandi o documenti Office possono essere ancora in scrittura quando parte il backup.

Soluzione adottata:

Prima della copia il backup controlla che il file sia stabile:

  • eta' minima dall'ultima modifica;
  • dimensione invariata;
  • timestamp invariato;
  • file leggibile.

Conseguenza osservata:

Un primo lancio puo' saltare un file come instabile. Un secondo lancio poco dopo puo' copiarlo correttamente. Questo e' comportamento previsto, non un errore.

Controllo raggiungibilita' slave

Problema:

Il servizio Windows non deve aprire finestre nella sessione utente. Inoltre l'utente deve essere avvisato durante la giornata se lo slave e' spento o la share non e' disponibile.

Soluzione adottata:

Il controllo e' separato dal servizio.

bakrest.server_notifier viene eseguito da Task Scheduler ogni 30 minuti e al logon. Se lo slave non e' raggiungibile, mostra:

Accendi il server di backup o verifica che sia connesso alla rete

Problema risolto:

Un controllo TCP o un controllo ingenuo della share poteva dare risultati non coerenti. E' stato adottato un controllo Win32 sul percorso SMB usando gli attributi del file system, piu' aderente alla reale accessibilita' della share.

Log:

C:\ProgramData\BakRest\logs\YYYY-MM-DD-server-notifier.log

Stato:

C:\ProgramData\BakRest\status.json
C:\ProgramData\BakRest\notifier-state.json

Task Scheduler: notifier

Registrazione consigliata:

cd "C:\Programmi\bak-and-rest"
powershell -ExecutionPolicy Bypass -File .\scripts\Register-BakRestTask.ps1 -Task Notifier

Il task:

  • parte al logon;
  • si ripete ogni 30 minuti;
  • gira nella sessione utente;
  • mostra notifiche se la share non e' raggiungibile.

Task Scheduler: backup al logoff

Obiettivo:

Eseguire automaticamente il backup alla disconnessione, evitando che l'utente dimentichi di lanciare manualmente Backup e spegni.

Problema 1: evento Security 4647

Prima ipotesi:

Usare evento Security con EventID=4647, cioe' disconnessione avviata dall'utente.

Problema osservato:

L'evento veniva scritto nel registro, ma il task non veniva agganciato in modo affidabile.

Conclusione:

4647 e' troppo vicino alla chiusura della sessione e non e' un buon punto operativo per avviare il backup.

Problema 2: evento Winlogon 7002 con token interattivo

Seconda ipotesi:

Usare evento:

Log:      System
Provider: Microsoft-Windows-Winlogon
EventID:  7002

Il filtro XPath funzionava: Get-WinEvent trovava gli eventi.

Problema osservato:

Il task non partiva se registrato con:

<LogonType>InteractiveToken</LogonType>

Conclusione:

Al logoff il token interattivo dell'utente non e' piu' un punto affidabile per avviare un nuovo processo.

Soluzione finale

Il task backup viene registrato con:

<LogonType>Password</LogonType>

cioe' "esegui anche se l'utente non e' connesso", usando credenziali memorizzate da Windows.

Registrazione di test, senza spegnimento:

cd "C:\Programmi\bak-and-rest"
git pull
schtasks /Delete /TN "BakRest\BackupOnLogoff" /F
powershell -ExecutionPolicy Bypass -File .\scripts\Register-BakRestTask.ps1 -Task BackupOnLogoff -RunAsStoredCredentials -UserName ".\pettirosso" -NoShutdown

Registrazione definitiva, con spegnimento a fine backup riuscito:

cd "C:\Programmi\bak-and-rest"
git pull
schtasks /Delete /TN "BakRest\BackupOnLogoff" /F
powershell -ExecutionPolicy Bypass -File .\scripts\Register-BakRestTask.ps1 -Task BackupOnLogoff -RunAsStoredCredentials -UserName ".\pettirosso"

Verifica:

schtasks /Query /TN "BakRest\BackupOnLogoff" /XML |
  Select-String "UserId|LogonType|Winlogon|7002|Arguments"

Risultato atteso:

<LogonType>Password</LogonType>

Durante i test deve esserci:

<Arguments>-m bakrest.tray_app --nogui --no-shutdown</Arguments>

In produzione deve esserci:

<Arguments>-m bakrest.tray_app --nogui</Arguments>

Problema 3: utente locale .\pettirosso

Problema osservato:

Registrando il task con -UserName ".\pettirosso", schtasks restituiva:

Non e' stato effettuato alcun mapping tra nomi di account e ID di sicurezza (SID).

Causa:

Dentro lo XML, .\pettirosso non veniva sempre risolto in SID.

Soluzione adottata:

Register-BakRestTask.ps1 normalizza gli utenti locali:

.\pettirosso -> NOMEPC\pettirosso

Il comando puo' comunque ricevere .\pettirosso; lo script lo converte prima di scrivere XML e chiamare schtasks.

Problema: encoding XML Task Scheduler

Problema osservato:

Importando direttamente gli XML con:

schtasks /Create /TN "..." /XML "tasks\file.xml" /F

Windows poteva restituire:

XML attivita' non valido.
impossibile passare a un'altra codifica

Causa:

Task Scheduler e schtasks sono sensibili alla codifica dell'XML.

Soluzione adottata:

Usare sempre:

powershell -ExecutionPolicy Bypass -File .\scripts\Register-BakRestTask.ps1 -Task ...

Lo script:

  • legge XML sorgente UTF-8;
  • sostituisce i path Python e working directory;
  • scrive una copia temporanea UTF-16;
  • registra il task;
  • cancella il temporaneo.

Problema: path Python nei task

Problema osservato:

Gli XML contenevano path fissi come:

C:\Python314\python.exe

ma sul PC reale Python era in:

C:\Program Files\Python314\python.exe

Errore Task Scheduler:

2147942402

che corrisponde a "file non trovato".

Soluzione adottata:

Register-BakRestTask.ps1 usa il Python della shell corrente:

python -c "import sys; print(sys.executable)"

e lo inserisce dinamicamente nell'XML.

Problema: output robocopy e UnicodeDecodeError

Problema osservato:

robocopy produceva output con caratteri non decodificabili da cp1252, causando:

UnicodeDecodeError: 'charmap' codec can't decode byte ...

La copia del file riusciva, ma il thread di lettura output generava eccezioni rumorose.

Soluzione adottata:

La lettura di stdout/stderr del processo usa decodifica robusta con errori sostituiti. Il backup non dipende piu' dalla code page locale di robocopy.

Comandi operativi principali

Installazione servizio:

cd "C:\Programmi\bak-and-rest"
python -m pip install -e .
python -m bakrest.service install --startup auto
python -m bakrest.service start

Se il servizio risulta "segnato per l'eliminazione", chiudere console, Services.msc e finestre MMC aperte, attendere qualche secondo o riavviare.

Avvio GUI:

python -m bakrest.config_app

Test backup senza spegnimento:

python -m bakrest.tray_app --nogui --no-shutdown

Test notifier:

python -m bakrest.server_notifier --always-alert

Aggiornamento deploy:

cd "C:\Programmi\bak-and-rest"
git pull
python -m pip install -e .

Stato attuale delle scelte

Decisioni consolidate:

  • Configurazione TOML.
  • Registro cambiamenti SQLite.
  • Watchdog Python con filtro USN Journal.
  • Servizio Windows con pywin32.
  • GUI CustomTkinter.
  • Backup via robocopy verso share SMB.
  • Backup non distruttivo.
  • Notifier separato dal servizio.
  • Task backup al logoff su System/Winlogon/7002.
  • Task backup con LogonType=Password e credenziali memorizzate.
  • Script PowerShell per registrare task con path dinamici e XML UTF-16 temporaneo.

Aspetti ancora futuri:

  • Produzione degli .exe senza console con PyInstaller.
  • Eventuale installer unico.
  • Eventuale gestione piu' avanzata dei log e rotazione.
  • Eventuale modalita' audit per aiutare l'utente a rifinire include/esclude.