An empty ELECTRUM_FALLBACK_SERVERS (the .env.example default) silently made every corroboration pass, and with fallbacks the majority was taken over whoever answered, so one reachable fallback decided alone. TLS certificates were never checked. - _corroborate_majority passes only with at least 2 answers (MIN_QUORUM_RESPONSES) and a strict majority of the answers; no other server configured, or fewer than 2 answering, fails. ALLOW_NO_QUORUM=true keeps the old reduced rule. - config.validate_quorum_servers, called from the lifespan after parse_endpoints, refuses to start with fewer than 2 fallback servers unless ALLOW_NO_QUORUM=true (which logs a warning at every start). - Optional pinning: `host:port[:mode]:pin=<sha256>` per fallback and ELECTRUM_CERT_PIN for the primary; ElectrumClient compares the SHA-256 of the DER certificate after the TLS handshake and a mismatch fails like an unreachable server. A pin on a plaintext entry is a startup error. - .env.example now ships the four bootstrap servers and documents both new variables; docs/setup.md, spec, storico and BUGS.md updated. - Tests changed because the spec changed: the two "no other servers configured -> True" corroboration tests now assert that outcome only under allow_no_quorum. Note: a dev .env with fewer than 2 fallback servers will now refuse to start. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
136 lines
6.6 KiB
Markdown
136 lines
6.6 KiB
Markdown
# Setup
|
|
|
|
Passaggi da eseguire una tantum per preparare un'istanza di PLM Lottery, prima di
|
|
poterla avviare (in locale o via Docker). Per come avviarla poi ogni volta, vedi
|
|
[running-the-server.md](running-the-server.md).
|
|
|
|
## 1. Prerequisiti
|
|
|
|
- Python 3.12+ (serve solo per il workflow locale/venv — puoi saltarlo se usi solo Docker)
|
|
- Docker + Docker Compose (serve solo per il workflow a container)
|
|
- Un server Electrum raggiungibile per la rete PLM. Il server di bootstrap per lo
|
|
sviluppo è `santantonio.sytes.net:50002` (SSL) — va bene per i test, ma in
|
|
produzione conviene usarne uno di cui ci si fida o gestirne uno proprio.
|
|
- **Consigliato in produzione: più di un server.** Tutto passa da questa singola
|
|
connessione (accredito depositi, invio transazioni, conferme, altezza della
|
|
catena su cui si basa l'estrazione), quindi un solo server è il principale
|
|
punto di rottura della piattaforma. Elencane altri in
|
|
`ELECTRUM_FALLBACK_SERVERS` (vedi sotto): l'app li prova a rotazione, così un
|
|
server irraggiungibile costa un solo tentativo di riconnessione invece di un
|
|
disservizio.
|
|
|
|
## 2. Creare il file `.env`
|
|
|
|
Copia `.env.example` in `.env` e compila i segreti. Ogni valore sotto viene
|
|
generato una volta e non cambia più (ruotarlo invalida sessioni/dati cifrati
|
|
esistenti):
|
|
|
|
```bash
|
|
cp .env.example .env
|
|
```
|
|
|
|
| Variabile | Scopo | Come generarla |
|
|
|---|---|---|
|
|
| `XPRV_ENCRYPTION_KEY` | Chiave simmetrica che cifra a riposo la master xprv del server. **Perdere questa chiave significa perdere per sempre l'accesso ai fondi di tutti gli utenti.** | `python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"` |
|
|
| `JWT_SECRET` | Firma i token di sessione degli utenti. | `python -c "import secrets; print(secrets.token_urlsafe(32))"` |
|
|
| `ADMIN_TOKEN` | Token bearer richiesto sugli endpoint admin (header `X-Admin-Token`). | `python -c "import secrets; print(secrets.token_urlsafe(32))"` |
|
|
|
|
Le chiavi Electrum (`ELECTRUM_HOST`/`PORT`/`USE_SSL`) hanno default sensati in
|
|
`.env.example`. `DATABASE_URL` e `MASTER_KEY_PATH` non sono elencate lì:
|
|
hanno un default in `app/config.py`, e dentro il container vengono comunque
|
|
sovrascritte da `docker-compose.yml` per puntare a `./data/` (elenco completo
|
|
delle variabili in [architettura.md](architettura.md)).
|
|
|
|
`ENABLE_API_DOCS` (default `false`) controlla Swagger/ReDoc/l'OpenAPI JSON grezzo
|
|
su `/docs`, `/redoc` e `/openapi.json`: espongono l'intera superficie dell'API,
|
|
endpoint admin inclusi, quindi restano disattivati a meno di non impostarlo
|
|
esplicitamente a `true` — utile in locale, da evitare in produzione.
|
|
|
|
`ELECTRUM_FALLBACK_SERVERS` elenca i server di riserva, separati da virgola, nel
|
|
formato `host:porta` (TLS, il caso normale), `host:porta:notls` oppure
|
|
`host:porta:pin=<sha256>` (TLS con il certificato del server fissato). Esempio:
|
|
|
|
```
|
|
ELECTRUM_FALLBACK_SERVERS=nodo2.example.net:50002,nodo3.example.net:50002,nodo4.example.net:50002:pin=<sha256>
|
|
```
|
|
|
|
Vengono provati a rotazione dopo il primario e servono anche da quorum: ne
|
|
servono **almeno 2**, altrimenti l'app non parte (`ALLOW_NO_QUORUM=true` lo
|
|
permette, ma solo in sviluppo). `ELECTRUM_CERT_PIN` fissa allo stesso modo il
|
|
certificato del primario. Attenzione: un valore scritto male **blocca
|
|
l'avvio** dell'app — è voluto, meglio accorgersene subito che durante il
|
|
disservizio in cui il fallback serve davvero.
|
|
|
|
`JWT_SECRET` e `XPRV_ENCRYPTION_KEY` vengono verificati all'avvio: se sono vuoti
|
|
(o `JWT_SECRET` è più corto di 32 caratteri) il container si rifiuta di partire con
|
|
un errore esplicito, invece di avviarsi e rompersi al primo login. Nota: `.env`
|
|
contiene solo segreti e configurazione di infrastruttura — i parametri di
|
|
business (bet amount, durata round, fee, ecc.) si configurano dal pannello
|
|
admin dopo l'avvio, non qui — vedi [guida-admin.md](guida-admin.md).
|
|
|
|
**Non committare mai `.env`.** È già escluso da `.gitignore`.
|
|
|
|
## 3. Generare la master key
|
|
|
|
Il server deriva l'indirizzo di deposito di ogni utente (e l'indirizzo pool) da
|
|
un'unica master xprv, generata una volta e cifrata a riposo con
|
|
`XPRV_ENCRYPTION_KEY`. Questo passaggio va eseguito esattamente una volta per
|
|
ogni deployment, dopo aver impostato `XPRV_ENCRYPTION_KEY` in `.env`:
|
|
|
|
- **Locale/venv**: `PYTHONPATH=. python scripts/generate_master_key.py`
|
|
- **Docker**: `docker compose run --rm app python scripts/generate_master_key.py`
|
|
|
|
Questo scrive un file cifrato (`MASTER_KEY_PATH`, default `./master.xprv.enc` in
|
|
locale o `./data/keys/master.xprv.enc` con Docker). **Fai il backup di questo
|
|
file insieme a `XPRV_ENCRYPTION_KEY`** — uno dei due da solo è inutile, ma
|
|
perderli entrambi insieme significa perdere i fondi di tutti gli utenti senza
|
|
possibilità di recupero.
|
|
|
|
### Recuperare o portare una xprv esistente
|
|
|
|
Due script, entrambi manuali/una tantum, per lo scenario di disaster recovery
|
|
o per usare una xprv generata altrove (es. offline/air-gapped) invece di
|
|
farla generare al server:
|
|
|
|
- **`scripts/decrypt_master_key.py`**: decifra e stampa a schermo la xprv
|
|
già presente in `MASTER_KEY_PATH` (con fallback automatico su
|
|
`./data/keys/master.xprv.enc` se il path di `.env` non esiste in locale).
|
|
Chiede conferma esplicita prima di stampare.
|
|
- **`scripts/encrypt_master_key.py`**: cifra una xprv esterna e la scrive in
|
|
`MASTER_KEY_PATH` con lo stesso identico schema (Fernet +
|
|
`XPRV_ENCRYPTION_KEY`) usato da `generate_master_key.py`. La xprv va
|
|
incollata con input nascosto (non appare a schermo). Si rifiuta di
|
|
sovrascrivere un file esistente a meno di passare `--overwrite`.
|
|
|
|
Entrambi vanno eseguiti localmente (o dentro il container via
|
|
`docker compose run --rm app ...`), mai esposti da un endpoint API o dal
|
|
pannello admin: chi ottiene questa xprv ottiene il controllo dei fondi di
|
|
tutti gli utenti e del pool.
|
|
|
|
## 4. Installare le dipendenze (solo workflow locale/venv)
|
|
|
|
Salta questo passaggio se usi solo Docker — l'immagine installa le proprie
|
|
dipendenze durante la build.
|
|
|
|
```bash
|
|
python3 -m venv .venv
|
|
source .venv/bin/activate
|
|
pip install -e ".[dev]"
|
|
```
|
|
|
|
## 5. Applicare le migrazioni del database
|
|
|
|
- **Locale/venv**: `alembic upgrade head`
|
|
- **Docker**: le migrazioni vengono eseguite automaticamente all'avvio del
|
|
container (vedi il `CMD` del `Dockerfile`) — nessun passaggio manuale.
|
|
|
|
## 6. Impostare l'indirizzo delle fee
|
|
|
|
Prima che si possa aprire il primo round, un admin deve impostare
|
|
`fee_address` tramite il pannello admin o l'API — vedi
|
|
[guida-admin.md](guida-admin.md). Finché non è impostato **nessun round si
|
|
apre**, perché il pagamento non si potrebbe costruire.
|
|
|
|
A questo punto l'istanza è pronta per essere avviata — continua con
|
|
[running-the-server.md](running-the-server.md).
|