Aggiunge un pannello che registra ogni passaggio del caricamento: file
scelto con la sua dimensione, limite del server, avanzamento ogni 10%,
codice HTTP della risposta, errori, timeout e interruzioni. Sotto, i log
lato server dell'operazione in corso.
Serve perche' i modi in cui un caricamento muore sono quasi tutti muti:
superato il limite nginx chiude la connessione dopo aver accettato zero
byte, e nessun evento porta un messaggio. Senza un diario, la barra si
pianta a una percentuale qualsiasi e la pagina non ha niente da dire. Un
battito ogni 15 secondi distingue "lento" da "fermo".
Le virgolette doppie restano bandite dentro l'x-data: chiudono
l'attributo HTML anche dentro un commento, e hanno gia' riversato il
codice a video due volte.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Le porte erano legate a 127.0.0.1, quindi l'applicazione non era raggiungibile
per IP: andava bene solo accedendo da un dominio via Traefik. Ora interfaccia
e porta si decidono separatamente nel .env, per ogni servizio.
APP_BIND=0.0.0.0 APP_PORT=8080 app esposta (default)
DB_BIND=127.0.0.1 DB_PORT_HOST=3306 database solo da localhost
MAILPIT_BIND=127.0.0.1 solo col profilo dev
Il database resta su localhost per due motivi: ci si arriva comunque con un
tunnel SSH senza esporlo a Internet, e su un host con piu' progetti la 3306
va spesso in conflitto (per questo e' cambiabile con DB_PORT_HOST).
Redis non pubblica nulla: non serve da fuori.
SESSION_SECURE_COOKIE passa a false come default, perche' ora l'accesso
tipico e' in HTTP per IP. Con true il cookie di sessione e' marcato "secure"
e il browser non lo rimanda su HTTP: verificato, il login risponde 419 Page
Expired e si torna alla schermata di accesso. Il tranello e' che provandolo
su localhost non si manifesta, perche' i browser considerano localhost un
contesto sicuro: emerge solo accedendo per IP. E' documentato in
.env.example insieme al sintomo esatto.
Verificato: app raggiungibile dall'IP di rete, porta del database chiusa
dall'esterno e aperta da localhost, login e pagine funzionanti via IP.
Nota: senza dominio il traffico e' in chiaro. Il README segnala il passaggio
a HTTPS come consigliato, con le tre variabili da cambiare.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
I due file compose andavano tenuti allineati a mano, e gli anchor
(x-app-env, <<:) sono sintassi che va conosciuta per riuscire a leggere il
file. Per un deploy che deve restare semplice era complessita' non
giustificata.
Ora c'e' un solo docker-compose.yml, esplicito, che copre produzione e
sviluppo:
- ogni servizio dichiara quello che usa, senza merge key
- mailpit e' dietro il profilo "dev", quindi non parte mai in produzione
- la porta e' legata a 127.0.0.1 per default: in produzione il traffico
arriva da Traefik sulla rete interna, in locale basta localhost:8080.
Per esporla davvero: APP_PORT=0.0.0.0:8080
- .env diventa l'unica fonte di configurazione, senza valori ridichiarati
Compromesso accettato: sparendo i valori fissati nel compose, DB_HOST e
REDIS_HOST devono contenere i nomi dei servizi (mariadb, redis) e non un
indirizzo. E' segnalato in .env.example, nel README e in HANDOFF.md.
Per le modifiche al codice senza ricostruire l'immagine il README documenta
un docker-compose.override.yml, che Compose applica da solo ed e' gia'
ignorato da git.
Verificato: sei servizi healthy, login e pagine a 200, redeploy idempotente,
porta esposta solo su localhost, mailpit assente senza il profilo dev.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
I due piu' gravi potevano causare perdita di dati silenziosa.
AppServiceProvider chiamava key:generate --force a ogni richiesta quando
APP_KEY risultava vuota. Poiche' nome e cognome dei proclamatori sono
cifrati con quella chiave, ogni riga scritta sotto una chiave poi
sostituita diventava irrecuperabile. La chiave e' ora risolta una sola
volta all'avvio del container e il provider solleva un'eccezione se manca,
invece di rigenerarla.
importTerritoryPdfFolder() caricava i PDF, costruiva l'elenco dei file e
poi lo scartava senza mai accodare il job: nessun errore, nessun territorio
aggiornato, e i file restavano su disco a crescita illimitata perche' la
pulizia viveva solo nel finally del job mai eseguito.
Sicurezza:
- i permessi non venivano riapplicati alle richieste /livewire/update, per
cui un permesso revocato restava attivo fino al reload della pagina
- host header injection: nginx passava l'Host del client come
X-Forwarded-Host e Laravel si fidava di tutti i proxy, abbastanza per far
generare a route() link verso domini arbitrari, inclusi i PDF pubblici
che contengono un codice di accesso valido
- lo schema forwarded e' ora deciso dal server e non dall'header del client
- campagna_id in Rientra e' #[Locked]: era derivato lato server ma
manipolabile dal client, permettendo di conteggiare un rientro su una
campagna arbitraria
- sortField del registro passava non validato a orderBy(), con 500 su
colonna inesistente
- SESSION_SECURE_COOKIE ora vale true fuori da local
- limiti su dimensione e numero di voci negli import ZIP
Integrita' e prestazioni:
- il default di assignment_link_ttl_hours era rimasto 24 dopo la
conversione da ore a mesi: i link PDF pubblici duravano due anni invece
di uno mese
- il seeder usava syncPermissions(), che a un secondo giro azzerava i
permessi personalizzati dei ruoli; ora e' additivo
- pdftoppm senza timeout poteva bloccare il worker indefinitamente
- nomi file deterministici nell'import: "1 A" e "1-A" scrivevano sullo
stesso PDF, sovrascrivendosi
- Setting::flushCache() dopo le cancellazioni massive, che non emettono
eventi e lasciavano la cache puntata a una riga inesistente
- riga settings creata dal seeder, per evitare due singleton concorrenti
- rimosse due migrazioni ridondanti che rompevano migrate:rollback
- indici su assigned_at, (attivo, deleted_at) e deleted_at
- risolti gli N+1 negli elenchi territori e proclamatori
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Il Dockerfile compilava l'applicazione in /app-src mentre il runtime girava
da /var/www/html, popolato solo dal bind mount del sorgente: l'immagine
costruita non veniva mai usata. L'entrypoint rieseguiva composer install e
npm run build a ogni avvio, dentro il container. Su Dokploy, senza sorgente
sull'host, questo significava build a runtime a ogni deploy.
Ora vendor/ e public/build sono compilati in stage separati e copiati
nell'immagine finale. I container di produzione non montano il sorgente e
non eseguono build all'avvio: un redeploy e' build, migrazioni, online.
- Dockerfile multi-stage (composer, node, runtime)
- nginx e php-fpm nello stesso container via supervisor: con container
separati nginx avrebbe bisogno di una copia di public/ e del volume
storage solo per servire file statici
- clear_env = no in www.conf, senza cui php-fpm nasconde a Laravel tutte
le variabili d'ambiente
- configurazione da ambiente di processo invece di .env riscritto con sed;
il compose fissa solo i valori dettati dalla topologia
- fail-fast su APP_URL, DB_PASSWORD, DB_ROOT_PASSWORD, REDIS_PASSWORD
- servizio scheduler dedicato: schedule:work non veniva mai eseguito,
quindi la pulizia dell'audit log non e' mai partita
- solo il container app esegue le migrazioni, per evitare che due
container in gara lascino uno schema applicato a meta'
- healthcheck separati per worker e scheduler, che non espongono HTTP
- docker-compose.dev.yml per lo sviluppo locale
- documentazione unificata in README.md e HANDOFF.md
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Key saved to storage/app/.app_key (persistent volume)
- On redeploy: restored automatically from volume
- On first deploy: generated and saved
- No manual Dokploy env var needed