Files
plm-lottery/docs/setup.md
T
davideandClaude Sonnet 5.5 c1b192f6d1 Harden the container: non-root, pinned images and dependencies, the key in a file (B-91, SEC-51, SEC-52, SEC-53; closes B-71, B-72)
- app runs as uid 1000; a one-shot data-permissions service chowns ./data to it
  and closes it to others (works on an older root-owned install with no host sudo);
  files are created 0600 (umask 077)
- python:3.12-slim and caddy:2-alpine pinned by digest; dependencies installed from
  requirements.lock with --require-hashes
- the Fernet key is XPRV_ENCRYPTION_KEY_FILE (./secrets/xprv_encryption.key, a compose
  secret), out of every environment variable and of a ./data backup
- setup.md no longer presents a second way to run the server (B-72)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-18 00:54:23 +02:00

134 lines
7.0 KiB
Markdown

# Setup
Passaggi da eseguire una tantum per preparare un'istanza di PLM Lottery, prima di
poterla avviare. Il server gira sempre in Docker; il venv serve solo a test, migrazioni da generare e script una tantum. Per come avviarla poi ogni volta, vedi
[running-the-server.md](running-the-server.md).
## 1. Prerequisiti
- Docker + Docker Compose (il server gira solo così)
- Python 3.12+ (solo per gli strumenti locali: test, `alembic revision` e gli script delle chiavi)
- 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_FILE` | Percorso del file che contiene la chiave simmetrica che cifra a riposo la master xprv del server (`./secrets/xprv_encryption.key`, cartella `0700`, file `0600`; nel container è montato come secret). **Perdere questa chiave significa perdere per sempre l'accesso ai fondi di tutti gli utenti.** | `mkdir -p secrets && chmod 700 secrets && python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())" > secrets/xprv_encryption.key && chmod 600 secrets/xprv_encryption.key` |
| `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 la chiave in `XPRV_ENCRYPTION_KEY_FILE` 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
la chiave in `XPRV_ENCRYPTION_KEY_FILE`. Questo passaggio va eseguito esattamente una volta per
ogni deployment, dopo aver creato quel file e impostato `XPRV_ENCRYPTION_KEY_FILE` in `.env`:
Dal venv: `PYTHONPATH=. python scripts/generate_master_key.py`, con `MASTER_KEY_PATH=./data/keys/master.xprv.enc` in `.env`, così lo script scrive esattamente il file che legge il container.
Questo scrive un file cifrato (`MASTER_KEY_PATH`, in Docker `./data/keys/master.xprv.enc`). **Fai il backup di questo
file insieme a `secrets/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 gli strumenti locali (venv)
Servono per i test e per gli script delle chiavi; il server non ne ha bisogno,
perché l'immagine installa le proprie dipendenze (da `requirements.lock`).
```bash
python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
```
## 5. Applicare le migrazioni del database
Le migrazioni vengono eseguite automaticamente all'avvio del container (vedi il
`CMD` del `Dockerfile`) — nessun passaggio manuale, e `alembic upgrade head` non
si lancia mai a mano.
## 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).