++ add backup totale applicativo
This commit is contained in:
54
HANDOFF.md
54
HANDOFF.md
@@ -248,6 +248,60 @@ Tre ritocchi minori ma con qualche implicazione:
|
||||
`permission:settings.manage` e il link in sidebar è sotto
|
||||
`@can('settings.manage')`.
|
||||
|
||||
### Backup completo `.zip`: dati + file, e cambio password forzato al restore
|
||||
|
||||
L'export XML da solo non ricostruisce un'installazione: `pdf_path`,
|
||||
`thumbnail_path` e `logo_path` sono percorsi sul disco `public`, quindi un
|
||||
ripristino su volumi vuoti lascia ogni territorio che punta a un file
|
||||
inesistente — link pubblici a 404 e miniature rotte. Il pacchetto `.zip`
|
||||
(`Services/BackupArchive`, `Http/Controllers/Settings/BackupController`) mette
|
||||
insieme `manifest.json`, `data.xml` (lo stesso XML dell'export) e `storage/…`
|
||||
con **tutti** i file del disco `public`. L'export XML resta, etichettato come
|
||||
parziale.
|
||||
|
||||
Scelte non ovvie:
|
||||
|
||||
- **Rotte HTTP, non Livewire.** Lo `streamDownload` di Livewire passa il file
|
||||
base64 dentro la risposta JSON: un archivio con i PDF dei territori non ci
|
||||
sta. `backup/download` e `backup/restore` sono due rotte normali nel gruppo
|
||||
`settings.manage`. Per lo stesso motivo `ZipArchive::addFile()` (che legge
|
||||
dal disco in `close()`) e gli stream in estrazione: nessun PDF passa intero
|
||||
per la memoria del processo.
|
||||
|
||||
- **Ordine del restore: staging → database → file.** I file si estraggono
|
||||
prima in `storage/app/backup-restore/<uuid>`, così un archivio corrotto o
|
||||
fuori limite fallisce mentre i dati correnti sono ancora intatti; poi
|
||||
`importXmlFromContent()` in transazione; poi la sostituzione dei file. Il
|
||||
disco `public` viene **svuotato**, non fuso: i percorsi vivono nelle righe
|
||||
appena riscritte, e tenere i vecchi file lascerebbe PDF orfani che nessuna
|
||||
schermata mostra e nessuna cancellazione raggiunge.
|
||||
|
||||
- **Limiti sull'archivio in ingresso.** Un `.zip` è compresso: senza tetti su
|
||||
numero di entry, dimensione della singola entry e totale decompresso, pochi
|
||||
MB possono riempire il volume (zip bomb). I nomi delle entry sono
|
||||
normalizzati e ogni `..` o percorso assoluto viene rifiutato (zip slip) —
|
||||
senza, un archivio ostile scriverebbe fuori dalla cartella di destinazione.
|
||||
|
||||
- **Dopo un restore tutti cambiano password.** Il pacchetto contiene gli hash
|
||||
bcrypt di ogni utente: chi lo ha avuto in mano ha potuto tentarne il crack
|
||||
offline, quindi da lì in poi nessuna vecchia password è più un segreto. Il
|
||||
restore accende `password_change_required` su **tutte** le righe di `users`
|
||||
(amministratore che sta ripristinando compreso) e azzera i `remember_token`,
|
||||
che sono credenziali persistenti e stanno pure loro nel backup. Il
|
||||
middleware `RequirePasswordChange` (in coda al gruppo `web`, quindi copre
|
||||
anche `/livewire/update` — Livewire segue le risposte redirezionate) dirotta
|
||||
su `password.change` tutto tranne quella pagina, il suo invio e il logout.
|
||||
|
||||
- **La schermata di cambio password non è Livewire e non chiede la password
|
||||
attuale.** Non è Livewire perché deve restare raggiungibile mentre il
|
||||
middleware respinge anche le POST di Livewire, e distinguere quale
|
||||
componente sta dietro una `/livewire/update` è fragile; due rotte HTTP si
|
||||
escludono per nome. Non chiede la password attuale perché il restore ha
|
||||
appena sovrascritto gli hash con quelli del backup: la "password attuale" di
|
||||
un account è quella dell'epoca del backup, che l'utente — l'amministratore
|
||||
per primo — può benissimo non ricordare. La garanzia è la sessione: a quella
|
||||
pagina ci si arriva già autenticati.
|
||||
|
||||
### Export/Import XML: backup completo e round-trip
|
||||
|
||||
`Livewire/Settings/XmlExchange` esporta e reimporta **tutti** i dati gestionali,
|
||||
|
||||
Reference in New Issue
Block a user