Finishes what 88221b0 started: sicurezza.md, audit-log.md and storico-bug.md
now describe the target behaviour too, the operator guides are corrected
against today's code, and api.md gains the missing /health/ready and
/admin/electrum sections plus every new error code in its catalog.
Harmonised names and owners across documents (the `resurrected_tx` pause
reason, the reconciler as the one that watches failed rows, the full
`winner_drawn` payload, a reason for an unanswered pre-payout pool check,
one corroboration-failure row per hash instead of one per tick).
Adds B-98 (coin selection stops at the first covering UTXO and refuses a
payable bet), tightens RND-40 to check both 1 and 400 participants and to
guard PUT /admin/config, and gives a stalled `closing` round a signal
(TX-29, closing_stalled).
Records three decisions confirmed on 2026-09-29: the draw's server_seed is
stored Fernet-encrypted until revealed (SEC-56, DAT-64), the chosen abuse
and key-check thresholds (SEC-49, SEC-54, SEC-55), and a broadcast
rejection is verified against every server before any coin is released,
so matching "already known" text is a shortcut, not a safety condition
(TX-04).
Spec only; no code changed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
5.6 KiB
Avviare il server
Presuppone che setup.md sia già stato completato (.env pronto,
master key generata). Le migrazioni del database non si applicano a mano: le
esegue il container a ogni avvio, prima di far partire il server.
Il server gira sempre via Docker, in sviluppo e in produzione allo stesso
modo — non esiste un modo supportato per lanciare uvicorn direttamente.
Il venv locale (.venv/) serve solo per i test, per scrivere le migrazioni
Alembic e per gli script una tantum di generazione chiavi (vedi
setup.md e la sezione "Commands" di
CLAUDE.md).
Docker + Caddy (unico workflow supportato)
mkdir -p data/db data/keys data/logs # una tantum, se non già presenti
docker compose up -d --build
- Caddy fa da reverse proxy davanti all'app e gestisce il TLS automaticamente
- App su
https://localhost/(o sul dominio configurato, vedi sotto) - Pannello admin su
https://localhost/admin - DB, master key cifrata e log persistono in
./data/sulla root del repo (bind mount, non volumi Docker opachi) — sopravvivono a stop/rebuild del container e sono ispezionabili/backup-abili direttamente
Modalità dev, senza dominio (certificato self-signed)
Non serve fare nulla: lasciando SITE_ADDRESS non impostata, Caddy usa
localhost di default. Rilevando che non è un hostname pubblico, genera da
solo un certificato dalla sua CA interna — il browser mostrerà un avviso di
sicurezza al primo accesso (normale, accettalo o usa curl -k).
Modalità produzione, con dominio reale
Scrivere il dominio in .env, accanto agli altri valori:
# .env
SITE_ADDRESS=lottery.tuodominio.it
e avviare come sempre:
docker compose up -d --build
Docker Compose legge .env da solo per sostituire ${SITE_ADDRESS} in
docker-compose.yml, quindi ogni avvio successivo usa lo stesso dominio.
Non passarlo solo sulla riga di comando
(SITE_ADDRESS=… docker compose up -d): il valore vale per quel comando e
basta, e il primo docker compose up -d --build lanciato senza di esso
riporta Caddy a localhost con un certificato autofirmato — il sito pubblico
smette di rispondere con un certificato valido. Per controllare:
grep '^SITE_ADDRESS=' .env.
Il DNS del dominio deve già puntare all'IP del server, con le porte 80 e 443 raggiungibili da internet. Caddy richiede e rinnova automaticamente un certificato Let's Encrypt reale — nessuna configurazione aggiuntiva.
Comandi utili
tail -f data/logs/app.log # log dell'applicazione (scheduler, Electrum, riconciliatori, errori)
docker compose logs -f app # solo output di avvio del container e di uvicorn (vedi sotto)
docker compose ps # stato dei container
docker compose stop # ferma senza rimuovere i container
docker compose down # ferma e rimuove i container (i dati in ./data/ restano)
Dove sono i log. I messaggi dell'applicazione — connessione Electrum,
scheduler dei round, riconciliatori, errori delle richieste — sono scritti
solo in ./data/logs/app.log (con rotazione: app.log.1 … app.log.5),
non sull'output del container. docker compose logs -f app mostra soltanto
l'output di avvio del container (controllo della master key, migrazioni
Alembic) e ciò che uvicorn scrive per conto suo (avvio, richieste HTTP): per
capire cosa sta facendo la lotteria si legge il file. Dettagli e messaggi da
conoscere in audit-log.md.
Nota sul
Caddyfile: è montato in sola lettura nel containercaddy(bind mount). Modificarlo non basta a farlo ripartire con la nuova configurazione —docker compose up -d --buildnon ricreacaddysolo perché il contenuto di un file montato è cambiato. Dopo una modifica alCaddyfileserve un passaggio in più:docker compose restart caddy(oppure, senza interrompere le connessioni esistenti:
docker compose exec caddy caddy reload --config /etc/caddy/Caddyfile).
Riavvii e deploy: prima la pausa
Oggi l'arresto del container non è pulito: con una pagina / aperta (uno
stream SSE attivo) uvicorn aspetta che lo stream si chiuda e Docker uccide il
processo dopo 10 secondi. Per il codice ogni docker compose up -d --build,
restart o down è quindi un crash: durante drawing blocca il round,
durante un invio lascia una transazione building da riconciliare.
Prima di qualunque riavvio o deploy (nuova versione, modifica di .env,
rotazione di un segreto):
- fare il backup del database;
- in
/admin→ Parametri → Manutenzione, premere "Interrompi dopo questo round"; - aspettare che il round in corso arrivi a
closed(GET /rounds/currentsenza più un round attivo) e che "Transazioni pendenti" non abbia righebuilding; - riavviare, controllare in
data/logs/app.logla rigaElectrum connected to …, poi "Riprendi lotteria".
La procedura completa, con i comandi e il perché di ogni passo, è OPS-10 in operativita.md ("Riavvio sicuro e deploy").
⚠️ Attenzione: riavvii automatici a metà round
docker-compose.yml imposta restart: unless-stopped sul container dell'app:
se crasha, riparte da solo. Questo però non è ancora sicuro in ogni caso: i
round in closing e paying_out riprendono da soli al riavvio, ma se il
crash avviene mentre un round è in drawing, lo scheduler non lo riprende e
il round resta bloccato (limite noto, vedi
decisioni-e-limiti.md; procedura in
operativita.md). Non trattare questo setup come
"non supervisionato" finché quel limite non è risolto.