# 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:
```text
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':
```text
https://gitea.alessandrobonvicini.it/administrator/bak-and-rest.git
```
## Configurazione
La configurazione e' TOML.
La GUI si avvia con:
```powershell
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:
```toml
[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:
```text
%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:
```text
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:
```powershell
fsutil usn createjournal m=134217728 a=33554432 D:
```
Verifica:
```powershell
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:
```toml
[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:
```text
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:
```text
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:
```text
C:\ProgramData\BakRest\logs\YYYY-MM-DD-server-notifier.log
```
Stato:
```text
C:\ProgramData\BakRest\status.json
C:\ProgramData\BakRest\notifier-state.json
```
## Task Scheduler: notifier
Registrazione consigliata:
```powershell
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:
```text
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:
```xml
InteractiveToken
```
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:
```xml
Password
```
cioe' "esegui anche se l'utente non e' connesso", usando credenziali memorizzate da Windows.
Registrazione di test, senza spegnimento:
```powershell
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:
```powershell
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:
```powershell
schtasks /Query /TN "BakRest\BackupOnLogoff" /XML |
Select-String "UserId|LogonType|Winlogon|7002|Arguments"
```
Risultato atteso:
```xml
Password
```
Durante i test deve esserci:
```xml
-m bakrest.tray_app --nogui --no-shutdown
```
In produzione deve esserci:
```xml
-m bakrest.tray_app --nogui
```
### Problema 3: utente locale `.\pettirosso`
Problema osservato:
Registrando il task con `-UserName ".\pettirosso"`, `schtasks` restituiva:
```text
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:
```text
.\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:
```powershell
schtasks /Create /TN "..." /XML "tasks\file.xml" /F
```
Windows poteva restituire:
```text
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
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:
```text
C:\Python314\python.exe
```
ma sul PC reale Python era in:
```text
C:\Program Files\Python314\python.exe
```
Errore Task Scheduler:
```text
2147942402
```
che corrisponde a "file non trovato".
Soluzione adottata:
`Register-BakRestTask.ps1` usa il Python della shell corrente:
```powershell
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:
```text
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:
```powershell
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:
```powershell
python -m bakrest.config_app
```
Test backup senza spegnimento:
```powershell
python -m bakrest.tray_app --nogui --no-shutdown
```
Test notifier:
```powershell
python -m bakrest.server_notifier --always-alert
```
Aggiornamento deploy:
```powershell
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.