++ update HANDOFF.md
This commit is contained in:
127
HANDOFF.md
127
HANDOFF.md
@@ -4,7 +4,7 @@ Stato del progetto per chi ci mette mano dopo. Per installazione, deploy e uso
|
||||
quotidiano vedi [README.md](README.md); qui c'è il contesto che il README non
|
||||
può dare: perché le cose sono come sono, e cosa resta aperto.
|
||||
|
||||
Ultimo aggiornamento: **25 luglio 2026**.
|
||||
Ultimo aggiornamento: **29 luglio 2026**.
|
||||
|
||||
---
|
||||
|
||||
@@ -20,8 +20,21 @@ redeploy, login, tutte le pagine, code e scheduler.
|
||||
| Primo avvio a zero-config | ✅ migrazioni + seed + admin automatici |
|
||||
| Redeploy senza interventi | ✅ ~25 secondi |
|
||||
| Sviluppo locale | ✅ stesso compose, profilo `dev` |
|
||||
| Backup / ripristino / azzeramento | ✅ verificati end-to-end in locale |
|
||||
| Ripristino da browser in linea | ⚠️ **limitato a 100 MB da Cloudflare** — vedi [Cosa resta aperto](#cosa-resta-aperto) |
|
||||
| Test automatici | ❌ **assenti** — vedi [Cosa resta aperto](#cosa-resta-aperto) |
|
||||
|
||||
**Punto in cui ci si è fermati (29/07/2026).** Su `develop` è pubblicato tutto:
|
||||
backup completo `.zip`, ripristino, azzeramento, cambio password forzato, barre
|
||||
di avanzamento reali e console di log (ultimo commit `f909791`, deploy
|
||||
verificato confrontando l'impronta degli asset). `MAX_UPLOAD_MB=2048` è
|
||||
impostato **solo** su develop; la produzione non è stata toccata e resta sul suo
|
||||
deploy precedente, senza quella variabile.
|
||||
|
||||
Il ripristino dall'interfaccia resta però impraticabile per pacchetti grandi
|
||||
finché non si decide come aggirare Cloudflare: è la prima voce di *Cosa resta
|
||||
aperto*.
|
||||
|
||||
---
|
||||
|
||||
## Da dove veniamo
|
||||
@@ -37,6 +50,42 @@ Da qui la ristrutturazione descritta sotto.
|
||||
|
||||
---
|
||||
|
||||
## Dove gira davvero
|
||||
|
||||
Due ambienti su Dokploy, stesso progetto `TerManager2`, entrambi alimentati dal
|
||||
repository Gitea `francesco/termanager2` con **autoDeploy attivo**.
|
||||
|
||||
| Ambiente | Branch | Dominio | Compose (`appName`) |
|
||||
|--------------|--------|-------------------------------------|----------------------------|
|
||||
| `production` | `main` | `termanager2.pyconetwork.it` | `termanager2-core-vizcxr` |
|
||||
| `develop` | `dev` | `demo-termanager.pyconetwork.it` | `termanager2-core-vbekis` |
|
||||
|
||||
Conseguenze pratiche, tutte imparate sul campo:
|
||||
|
||||
- **Il branch decide l'ambiente.** Pubblicare su `dev` non tocca la produzione,
|
||||
che segue `main`. È la separazione su cui contare quando si vuole provare
|
||||
qualcosa senza rischi.
|
||||
|
||||
- **Un "Rebuild deployment" NON scarica il codice nuovo**: ricostruisce il
|
||||
checkout già presente e ci mette una ventina di secondi. Per prendere l'ultimo
|
||||
commit serve un deploy vero (`compose.deploy` via API, o *Deploy*
|
||||
nell'interfaccia). Un rebuild che finisce troppo in fretta è il segnale che si
|
||||
sta ricostruendo il codice di prima.
|
||||
|
||||
- **Le variabili d'ambiente stanno in Dokploy, non nel repository.** Il `.env`
|
||||
dell'ambiente lo genera l'interfaccia: una variabile aggiunta a
|
||||
`.env.example` non arriva da sola sui deployment, va aggiunta a mano lì. È
|
||||
successo con `MAX_UPLOAD_MB`, presente in develop e — deliberatamente — non in
|
||||
produzione.
|
||||
|
||||
- **Verifica che il codice servito sia quello giusto** confrontando l'impronta
|
||||
degli asset: `public/build/assets/app-*.css` e `-*.js` hanno il nome derivato
|
||||
dal contenuto, quindi se coincide con quello della propria build, il frontend
|
||||
in linea è lo stesso sorgente. È il controllo più rapido dopo un deploy, e non
|
||||
richiede di autenticarsi.
|
||||
|
||||
---
|
||||
|
||||
## Decisioni architetturali
|
||||
|
||||
Le scelte non ovvie, con il motivo. Se una di queste ti sembra sbagliata, il
|
||||
@@ -378,6 +427,16 @@ Scelte non ovvie:
|
||||
più nulla per sempre), e il codice HTTP dentro il messaggio, perché 413 e 419
|
||||
non hanno un corpo JSON da cui ricavare una spiegazione.
|
||||
|
||||
- **La console di log non è un vezzo diagnostico.** Quasi tutti i modi in cui un
|
||||
caricamento muore sono muti: Cloudflare o nginx chiudono la connessione e
|
||||
nessun evento del browser porta con sé una spiegazione. Il pannello *Console*
|
||||
nella scheda di ripristino registra file scelto e dimensione, limite del
|
||||
server, avanzamento ogni 10%, codice HTTP della risposta, errori, timeout e
|
||||
interruzioni; sotto, i log lato server dell'operazione. Un battito ogni 15
|
||||
secondi scrive «nessun avanzamento da N s», che è l'unico modo di distinguere
|
||||
*lento* da *fermo*. Il pulsante *Copia tutto* esiste perché il primo passo di
|
||||
ogni diagnosi è farsi mandare quel testo.
|
||||
|
||||
- **Nell'azzeramento i file si cancellano prima del database.** Svuotare il
|
||||
disco è l'unico passo che può fallire per cause esterne (permessi sul
|
||||
volume), e fallire lì non ha ancora toccato niente. Nell'ordine inverso un
|
||||
@@ -530,6 +589,13 @@ Contesto utile se ti imbatti in comportamenti che sembrano regressioni.
|
||||
| Zip senza limiti | Zip bomb → esaurimento memoria | `TerritorioPdfImportDispatcher` |
|
||||
| Export XML senza `legacy_id` | Il re-import dell'export dell'app corrente perdeva territori e assegnazioni (FK non risolte) | `Livewire/Settings/XmlExchange` (`addLegacyIdAttribute`) |
|
||||
| Migrazioni thumbnail duplicate | `migrate:rollback` falliva a metà | rimosse 2 migrazioni ridondanti |
|
||||
| `addFile()` senza controllo esito | Backup con un file mancante, **silenzioso**: ci si accorge solo al ripristino | `Services/BackupArchive` |
|
||||
| Esito del ripristino con `Cache::pull` | L'amministratore non vedeva **mai** il riepilogo: la `fetch` che segue il 302 lo consumava prima della navigazione | `Auth/PasswordChangeController` |
|
||||
| Nessun blocco fra operazioni | Backup, ripristino e azzeramento potevano sovrapporsi sullo stesso database e disco | `Services/BackupOperationState` (`corrente()`) |
|
||||
| Job ucciso = operazione eterna | Timeout o crash del worker lasciavano lo stato "in corso" e il posto occupato | `failed()` sui tre job di backup |
|
||||
| Cancellazione file senza verifica | «N file cancellati» con i file ancora sul disco (permessi) | `BackupArchive::svuotaDiscoPubblico()` |
|
||||
| Eliminazione definitiva territorio | La riga spariva, mappa e miniatura restavano orfane sul volume per sempre | `Territori/TerritorioCestino` |
|
||||
| Virgolette doppie in `x-data` | Attributo HTML chiuso a metà: il JavaScript finiva a video come testo | `backup-manager.blade.php` |
|
||||
|
||||
---
|
||||
|
||||
@@ -537,6 +603,26 @@ Contesto utile se ti imbatti in comportamenti che sembrano regressioni.
|
||||
|
||||
In ordine di priorità.
|
||||
|
||||
### 0. Il ripristino da browser è limitato a 100 MB — decisione in sospeso
|
||||
|
||||
**È il punto da cui riprendere.** Cloudflare rifiuta i caricamenti oltre 100 MB
|
||||
(vedi *Trappole da conoscere*), quindi il ripristino dall'interfaccia non
|
||||
funziona per un pacchetto con molte mappe. L'applicazione è a posto: il muro sta
|
||||
davanti.
|
||||
|
||||
Da decidere, in ordine di rapidità:
|
||||
|
||||
1. Togliere il dominio da Cloudflare (DNS-only), anche solo su un sottodominio
|
||||
dedicato all'amministrazione — gratis e immediato.
|
||||
2. Caricare puntando direttamente all'IP dell'origine, scavalcando Cloudflare.
|
||||
3. Ripristino dai volumi (procedura nel README): nessun limite, serve accesso
|
||||
al server.
|
||||
4. Piano Cloudflare Business: 200 MB, spesso ancora insufficienti.
|
||||
|
||||
Resta da accertare **quanto pesa davvero il pacchetto** che si vuole
|
||||
ripristinare: sotto i 100 MB il problema non sarebbe la dimensione ma il timeout
|
||||
di 100 secondi di Cloudflare, e la soluzione sarebbe un'altra.
|
||||
|
||||
### 1. Nessun test automatico
|
||||
|
||||
`composer.json` richiede PHPUnit ma **la cartella `tests/` non esiste** e non
|
||||
@@ -606,6 +692,45 @@ verrebbe né rallentato né registrato. Un `throttle:30,1` costerebbe una riga.
|
||||
|
||||
## Trappole da conoscere
|
||||
|
||||
**Cloudflare taglia i caricamenti a 100 MB, e `MAX_UPLOAD_MB` non c'entra
|
||||
niente.** I domini pubblici passano da Cloudflare, che sul piano free rifiuta i
|
||||
corpi di richiesta oltre **100 MB**. Rifiuta sulla base del `Content-Length`,
|
||||
quindi il `413` arriva dopo che il browser ha spedito appena un megabyte: la
|
||||
barra di caricamento si pianta a una percentuale bassa e — senza i gestori
|
||||
aggiunti alla console — non compare alcun messaggio. È **il** motivo per cui il
|
||||
ripristino di un pacchetto grande sembra bloccarsi senza spiegazione.
|
||||
|
||||
Misurato il 29/07/2026 su `demo-termanager.pyconetwork.it`:
|
||||
|
||||
| Payload | Esito |
|
||||
|---------|----------------------------------------------------|
|
||||
| 150 MB | `413 Payload Too Large`, `Server: cloudflare`, 2,7 s |
|
||||
| 100 MB | `524` (timeout dell'origine dopo 100 s di Cloudflare) |
|
||||
|
||||
Il pacchetto **non raggiunge mai nginx**, quindi alzare `MAX_UPLOAD_MB` non
|
||||
sposta nulla: quel valore governa solo ciò che accade *dopo* Cloudflare. Le
|
||||
strade per superarlo sono togliere il dominio da Cloudflare (DNS-only, anche
|
||||
solo su un sottodominio d'amministrazione), caricare puntando direttamente
|
||||
all'IP dell'origine, oppure usare il ripristino dai volumi. Il piano Business
|
||||
arriva a 200 MB, che per un archivio con tutte le mappe è spesso ancora poco.
|
||||
|
||||
Nota sul `524`: anche restando sotto i 100 MB, Cloudflare chiude dopo 100
|
||||
secondi se l'origine non ha ancora risposto. Un caricamento lento può quindi
|
||||
fallire pur essendo dentro il limite di dimensione.
|
||||
|
||||
**Il JavaScript di `backup-manager.blade.php` vive dentro un attributo HTML.**
|
||||
`x-data` finisce alla **prima virgoletta doppia**, comprese quelle dentro un
|
||||
commento JavaScript: un banale `// un generico "errore"` ha chiuso l'attributo a
|
||||
metà e riversato tutto il codice restante a video come testo — è successo due
|
||||
volte. Nello stesso blocco, un `\'` scritto a mano nel sorgente Blade *chiude* la
|
||||
stringa invece di proteggere l'apostrofo, perché fra il file e l'attributo non
|
||||
c'è alcun livello di escape. Regole: nessuna virgoletta doppia in quel blocco,
|
||||
da nessuna parte; apostrofi italiani solo dentro backtick.
|
||||
|
||||
Un blocco `<script>` con `@script` eviterebbe la questione alla radice, ma
|
||||
**rompe il rilevamento della radice singola di Livewire** e restituisce 500 —
|
||||
provato. Il file porta l'avvertenza in testa; leggerla prima di intervenire.
|
||||
|
||||
**`APP_KEY` è il singolo punto di perdita dati.** I nomi dei proclamatori sono
|
||||
cifrati con questa chiave. Il backup del database senza la chiave è inutile per
|
||||
quei campi. Se non la imposti, l'entrypoint ne genera una e la salva nel volume
|
||||
|
||||
15
README.md
15
README.md
@@ -441,6 +441,21 @@ corpo su disco e PHP lavora su un file temporaneo — ma serve spazio libero nel
|
||||
volume. Oltre qualche giga conviene comunque il backup dei volumi qui sotto: un
|
||||
upload lunghissimo da browser è fragile.
|
||||
|
||||
> **Se il dominio passa da Cloudflare, comanda Cloudflare.** Sul piano free
|
||||
> rifiuta i corpi di richiesta oltre **100 MB**, e lo fa in base al
|
||||
> `Content-Length`: il `413` arriva dopo appena un megabyte spedito, quindi il
|
||||
> pacchetto **non raggiunge mai nginx** e `MAX_UPLOAD_MB` non sposta nulla.
|
||||
> C'è anche un tetto di 100 secondi: un caricamento lento può fallire con `524`
|
||||
> pur restando sotto il limite di dimensione.
|
||||
>
|
||||
> Per superarlo: togliere il dominio da Cloudflare (DNS-only), anche solo su un
|
||||
> sottodominio d'amministrazione; oppure caricare puntando direttamente all'IP
|
||||
> dell'origine; oppure usare il ripristino dai volumi qui sotto. Il piano
|
||||
> Business arriva a 200 MB, spesso ancora insufficienti.
|
||||
>
|
||||
> La **Console** nella scheda di ripristino mostra il codice HTTP ricevuto: se
|
||||
> leggi `413` con un pacchetto sotto `MAX_UPLOAD_MB`, il tetto è a monte.
|
||||
|
||||
### Zona pericolosa: azzera tutto
|
||||
|
||||
In fondo alla stessa pagina, **Scarica backup e azzera tutto** riporta
|
||||
|
||||
Reference in New Issue
Block a user