2de226f1fd
docker-compose.yml (produzione) non impostava APP_ENV: la guardia _refuse_default_secrets_outside_dev in src/backend/config.py non scattava mai perché il default è "dev". Aggiunto APP_ENV: production al servizio server; docker-compose.dev.yml imposta APP_ENV: dev in modo esplicito. Tabella env in docs/DEPLOYMENT.md aggiornata. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
696 lines
21 KiB
Markdown
696 lines
21 KiB
Markdown
# TieMeasureFlow Deployment Guide
|
|
|
|
This guide covers deploying TieMeasureFlow in development, staging, and production environments.
|
|
|
|
## Table of Contents
|
|
|
|
1. [Prerequisites](#prerequisites)
|
|
2. [Environment Setup](#environment-setup)
|
|
3. [Database Setup](#database-setup)
|
|
4. [Server Deployment](#server-deployment)
|
|
5. [Client Deployment](#client-deployment)
|
|
6. [Production Deployment](#production-deployment)
|
|
7. [Docker Deployment](#docker-deployment)
|
|
8. [SSL/HTTPS](#ssltls)
|
|
9. [Backup Strategy](#backup-strategy)
|
|
10. [Troubleshooting](#troubleshooting)
|
|
|
|
## Prerequisites
|
|
|
|
### System Requirements
|
|
|
|
- **OS**: Linux, macOS, or Windows
|
|
- **Python**: 3.11 or higher
|
|
- **MySQL**: 8.0 or higher
|
|
- **Node.js**: 16+ (optional, for Tailwind CSS compilation)
|
|
- **Disk Space**: 500 MB minimum
|
|
- **RAM**: 2 GB minimum
|
|
|
|
### Software Installation
|
|
|
|
#### Linux/macOS
|
|
|
|
```bash
|
|
# Python 3.11+
|
|
python3 --version
|
|
|
|
# MySQL 8.0
|
|
mysql --version
|
|
|
|
# Node.js (optional)
|
|
node --version
|
|
npm --version
|
|
```
|
|
|
|
#### Windows
|
|
|
|
Download and install:
|
|
- [Python 3.11+](https://www.python.org/downloads/)
|
|
- [MySQL Community Server](https://dev.mysql.com/downloads/mysql/)
|
|
- [Node.js](https://nodejs.org/) (optional)
|
|
|
|
---
|
|
|
|
## Environment Setup
|
|
|
|
### 1. Clone Repository
|
|
|
|
```bash
|
|
git clone <repository-url>
|
|
cd TieMeasureFlow
|
|
```
|
|
|
|
### 2. Create .env File
|
|
|
|
Copy the example and customize for your environment:
|
|
|
|
```bash
|
|
cp .env.example .env
|
|
```
|
|
|
|
### 3. Configure .env
|
|
|
|
Edit `.env` with your settings:
|
|
|
|
```env
|
|
# =====================================================================
|
|
# DATABASE
|
|
# =====================================================================
|
|
DB_HOST=localhost
|
|
DB_PORT=3306
|
|
DB_NAME=tiemeasureflow
|
|
DB_USER=tmflow
|
|
DB_PASSWORD=change_me_in_production
|
|
|
|
# =====================================================================
|
|
# SERVER (FastAPI)
|
|
# =====================================================================
|
|
SERVER_HOST=0.0.0.0
|
|
SERVER_PORT=8000
|
|
SERVER_SECRET_KEY=change-this-to-a-random-secret-key-with-32-chars
|
|
|
|
# =====================================================================
|
|
# CLIENT (Flask)
|
|
# =====================================================================
|
|
CLIENT_HOST=0.0.0.0
|
|
CLIENT_PORT=5000
|
|
|
|
# =====================================================================
|
|
# CORS
|
|
# =====================================================================
|
|
SERVER_CORS_ORIGINS=http://localhost:5000,http://127.0.0.1:5000
|
|
|
|
# =====================================================================
|
|
# FILE UPLOAD
|
|
# =====================================================================
|
|
UPLOAD_DIR=uploads
|
|
MAX_UPLOAD_SIZE_MB=50
|
|
|
|
# =====================================================================
|
|
# RATE LIMITING
|
|
# =====================================================================
|
|
RATE_LIMIT_LOGIN=5
|
|
RATE_LIMIT_GENERAL=100
|
|
|
|
# =====================================================================
|
|
# SSL/HTTPS (Production only)
|
|
# =====================================================================
|
|
SSL_CERTFILE=
|
|
SSL_KEYFILE=
|
|
```
|
|
|
|
### Environment Variables Reference
|
|
|
|
| Variable | Type | Default | Description |
|
|
|----------|------|---------|-------------|
|
|
| `DB_HOST` | string | localhost | MySQL server hostname |
|
|
| `DB_PORT` | int | 3306 | MySQL server port |
|
|
| `DB_NAME` | string | tiemeasureflow | Database name |
|
|
| `DB_USER` | string | tmflow | Database user |
|
|
| `DB_PASSWORD` | string | change_me_in_production | Database password **[CHANGE IN PROD]** |
|
|
| `SERVER_HOST` | string | 0.0.0.0 | API server bind address |
|
|
| `SERVER_PORT` | int | 8000 | API server port |
|
|
| `SERVER_SECRET_KEY` | string | change-this-to... | Secret key for sessions **[CHANGE IN PROD]** |
|
|
| `CLIENT_HOST` | string | 0.0.0.0 | Flask client bind address |
|
|
| `CLIENT_PORT` | int | 5000 | Flask client port |
|
|
| `SERVER_CORS_ORIGINS` | string | http://localhost:5000 | Comma-separated CORS origins |
|
|
| `UPLOAD_DIR` | string | uploads | Directory for file uploads (resolved against the project root) |
|
|
| `MAX_UPLOAD_SIZE_MB` | int | 50 | Maximum upload file size in MB |
|
|
| `RATE_LIMIT_LOGIN` | int | 5 | Login requests per minute, per real client IP |
|
|
| `RATE_LIMIT_GENERAL` | int | 300 | General requests per minute, per real client IP (post-V2.0.0; was 100 in V1.0.x) |
|
|
| `STATION_CODE` | string | (empty) | **Per-tablet** code identifying the station this Flask client serves. Must match a station created in the admin UI. Empty = the client refuses `/measure/select` with HTTP 503 "Stazione non configurata". |
|
|
| `SSL_CERTFILE` | string | (empty) | Path to SSL certificate (production) |
|
|
| `SSL_KEYFILE` | string | (empty) | Path to SSL private key (production) |
|
|
|
|
Introdotte dalla revisione del 21/09/2026:
|
|
|
|
| Variable | Type | Default | Description |
|
|
|----------|------|---------|-------------|
|
|
| `APP_ENV` | string | dev | Ambiente di esecuzione. `dev`/`test` saltano il controllo sui segreti di default; qualsiasi altro valore lo attiva. `docker-compose.yml` lo imposta a `production`, `docker-compose.dev.yml` a `dev` |
|
|
| `APP_DOMAIN` | string | *(nessuno, obbligatoria)* | Dominio pubblico dell'installazione, usato nelle rule Traefik di `docker-compose.yml` (solo produzione) |
|
|
| `TRUSTED_PROXY_COUNT` | int | 1 | Quanti hop di reverse proxy fidati stanno davanti al server, per calcolare l'IP reale del tablet |
|
|
| `WEB_CONCURRENCY` | int | 1 | Numero di worker Uvicorn del server; il `Dockerfile` ne avvia 4 di default indipendentemente da questa variabile |
|
|
| `SESSION_COOKIE_SECURE` | bool | true | Cookie di sessione Flask con flag Secure (richiede HTTPS). `false` per le installazioni on-premise su LAN in HTTP semplice (`docker-compose.dev.yml`) |
|
|
| `VISION_RUN_TIMEOUT_S` | int | 100 | Timeout, in secondi, di un'esecuzione del worker di visione |
|
|
| `VISION_MAX_CONCURRENCY` | int | 2 | Esecuzioni di visione in parallelo accettate dal worker |
|
|
| `VISION_ENGINE_VERSION` | string | *(nessuno, obbligatoria alla build)* | Commit di `vendor/visionsuite` stampato nell'immagine `vision` al momento della build — vedi [Docker Deployment](#docker-deployment) |
|
|
|
|
---
|
|
|
|
## Database Setup
|
|
|
|
### 1. Create MySQL Database and User
|
|
|
|
```bash
|
|
mysql -u root -p
|
|
```
|
|
|
|
```sql
|
|
-- Create database
|
|
CREATE DATABASE tiemeasureflow CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
|
|
|
-- Create user
|
|
CREATE USER 'tmflow'@'localhost' IDENTIFIED BY 'secure_password_here';
|
|
|
|
-- Grant permissions
|
|
GRANT ALL PRIVILEGES ON tiemeasureflow.* TO 'tmflow'@'localhost';
|
|
FLUSH PRIVILEGES;
|
|
|
|
-- Exit
|
|
EXIT;
|
|
```
|
|
|
|
### 2. Run Database Migrations
|
|
|
|
The monorepo is gestito con `uv`, non con `requirements.txt` per sottocartella: si esegue dalla radice del repository, con il flag `-c` perché `alembic.ini` vive in `src/backend/migrations/`.
|
|
|
|
```bash
|
|
uv run alembic -c src/backend/migrations/alembic.ini upgrade head
|
|
```
|
|
|
|
Questo è lo stesso comando che il container del server esegue da solo a ogni avvio (`CMD` del `Dockerfile`, prima di `uvicorn`): un deployment Docker non richiede questo passo a mano. La prima migrazione crea lo schema base anche su un database completamente vuoto — non serve più creare le tabelle a parte prima di lanciare `upgrade head`.
|
|
|
|
### 3. Create Initial Admin User
|
|
|
|
Non c'è più uno script Python da lanciare a mano: il primo utente amministratore si crea dalla pagina `/api/setup` (protetta da `SETUP_PASSWORD`, vedi sopra), che inizializza il database, crea l'admin con le credenziali fornite nel form e, opzionalmente, carica i dati demo — vedi [`README.md`](../README.md#setup-iniziale).
|
|
|
|
Una volta creato il primo amministratore, l'endpoint di setup si disattiva da solo: le richieste successive di inizializzazione o creazione utenti ricevono `403 Forbidden`, anche con `SETUP_PASSWORD` corretta. Per rieseguirlo — ad esempio per riconfigurare una stazione da zero — si imposta `SETUP_ALLOW_AFTER_INIT=true` nell'ambiente del server, si esegue l'operazione, e si toglie la variabile: non è pensata per restare attiva in modo permanente.
|
|
|
|
---
|
|
|
|
## Server Deployment
|
|
|
|
Docker Compose (sezione successiva) è il metodo raccomandato e quello che gira
|
|
in produzione. Questa sezione descrive l'avvio **senza Docker**, utile per
|
|
sviluppare sul runner senza ricostruire un'immagine a ogni modifica.
|
|
|
|
### Development Server
|
|
|
|
```bash
|
|
uv sync --extra server --extra dev
|
|
uv run uvicorn src.backend.main:app --reload --host 0.0.0.0 --port 8000
|
|
```
|
|
|
|
Runs at `http://0.0.0.0:8000`
|
|
|
|
API Documentation:
|
|
- Swagger UI: `http://localhost:8000/docs`
|
|
- ReDoc: `http://localhost:8000/redoc`
|
|
|
|
### Production, senza Docker
|
|
|
|
Lo stesso comando che gira dentro il container (`Dockerfile`), lanciato dalla
|
|
radice del repository con l'interprete e il venv pinnati da `uv`:
|
|
|
|
```bash
|
|
uv run uvicorn src.backend.main:app \
|
|
--host 0.0.0.0 --port 8000 --workers ${WEB_CONCURRENCY:-4} \
|
|
--proxy-headers --forwarded-allow-ips='*'
|
|
```
|
|
|
|
### systemd Service (Linux)
|
|
|
|
Create `/etc/systemd/system/tiemeasureflow-api.service`:
|
|
|
|
```ini
|
|
[Unit]
|
|
Description=TieMeasureFlow API Server
|
|
After=network.target mysql.service
|
|
|
|
[Service]
|
|
Type=simple
|
|
User=tiemeasureflow
|
|
WorkingDirectory=/opt/tiemeasureflow
|
|
EnvironmentFile=/opt/tiemeasureflow/.env
|
|
Environment="WEB_CONCURRENCY=4"
|
|
ExecStart=/opt/tiemeasureflow/.venv/bin/uv run uvicorn src.backend.main:app \
|
|
--host 0.0.0.0 --port 8000 --workers ${WEB_CONCURRENCY} \
|
|
--proxy-headers --forwarded-allow-ips='*'
|
|
|
|
Restart=on-failure
|
|
RestartSec=10
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
```
|
|
|
|
Enable and start:
|
|
|
|
```bash
|
|
sudo systemctl daemon-reload
|
|
sudo systemctl enable tiemeasureflow-api.service
|
|
sudo systemctl start tiemeasureflow-api.service
|
|
sudo systemctl status tiemeasureflow-api.service
|
|
```
|
|
|
|
View logs:
|
|
|
|
```bash
|
|
sudo journalctl -u tiemeasureflow-api -f
|
|
```
|
|
|
|
---
|
|
|
|
## Client Deployment
|
|
|
|
Anche qui, Docker Compose è il metodo raccomandato; questa sezione è per lo
|
|
sviluppo o un'installazione manuale senza container.
|
|
|
|
### Development Server
|
|
|
|
```bash
|
|
uv sync --extra client --extra dev
|
|
cd src/frontend/flask_app && uv run --project ../../.. flask run --host 0.0.0.0 --port 5000
|
|
```
|
|
|
|
Runs at `http://0.0.0.0:5000`
|
|
|
|
### Compile Tailwind CSS (Optional)
|
|
|
|
```bash
|
|
cd src/frontend/flask_app
|
|
npx tailwindcss -i static/css/input.css -o static/css/tailwind.css --watch
|
|
```
|
|
|
|
### Production, senza Docker
|
|
|
|
Lo stesso comando del `Dockerfile.frontend` (gunicorn richiede la cwd dentro
|
|
`flask_app` per risolvere `app:create_app()`):
|
|
|
|
```bash
|
|
cd src/frontend/flask_app && uv run --project ../../.. gunicorn \
|
|
--workers 5 --threads 4 --worker-class gthread --timeout 60 \
|
|
--bind 0.0.0.0:5000 --forwarded-allow-ips='*' \
|
|
"app:create_app()"
|
|
```
|
|
|
|
### systemd Service (Linux)
|
|
|
|
Create `/etc/systemd/system/tiemeasureflow-web.service`:
|
|
|
|
```ini
|
|
[Unit]
|
|
Description=TieMeasureFlow Web Client
|
|
After=network.target tiemeasureflow-api.service
|
|
|
|
[Service]
|
|
Type=simple
|
|
User=tiemeasureflow
|
|
WorkingDirectory=/opt/tiemeasureflow/src/frontend/flask_app
|
|
EnvironmentFile=/opt/tiemeasureflow/.env
|
|
ExecStart=/opt/tiemeasureflow/.venv/bin/uv run --project /opt/tiemeasureflow gunicorn \
|
|
--workers 5 --threads 4 --worker-class gthread --timeout 60 \
|
|
--bind 0.0.0.0:5000 --forwarded-allow-ips='*' \
|
|
"app:create_app()"
|
|
|
|
Restart=on-failure
|
|
RestartSec=10
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
```
|
|
|
|
---
|
|
|
|
## Production Deployment
|
|
|
|
### Security Checklist
|
|
|
|
- [ ] Change all default passwords in `.env` — con `docker-compose.yml`, `DB_ROOT_PASSWORD`, `DB_PASSWORD`, `SERVER_SECRET_KEY`, `CLIENT_SECRET_KEY` non hanno più un default: `docker compose config` si rifiuta di partire se mancano (introdotto dalla revisione del 21/09/2026)
|
|
- [ ] Generate random `SERVER_SECRET_KEY` (32+ characters)
|
|
- [ ] Set `APP_DOMAIN` to the real public domain (used by the Traefik router rules)
|
|
- [ ] Set CORS origins to actual client domains
|
|
- [ ] Enable SSL/HTTPS (see SSL/TLS section)
|
|
- [ ] Configure firewall rules
|
|
- [ ] Set up regular database backups
|
|
- [ ] Configure log rotation
|
|
- [ ] Use strong database credentials
|
|
- [ ] Restrict file upload sizes
|
|
- [ ] Keep dependencies updated
|
|
|
|
### Recommended Architecture
|
|
|
|
Il progetto usa due reverse proxy diversi a seconda dell'installazione, non uno
|
|
generico da configurare a mano:
|
|
|
|
- **On-premise, rete LAN, HTTP semplice** (`docker-compose.dev.yml`): Nginx,
|
|
configurazione già pronta in [`nginx/nginx.conf`](../nginx/nginx.conf). È il
|
|
caso di una stazione di misura sul reparto, senza certificato TLS.
|
|
- **Produzione, dominio pubblico** (`docker-compose.yml`): Traefik, con
|
|
certificati Let's Encrypt automatici. La configurazione sta nelle label
|
|
Docker del servizio, non in un file a parte — vedi la sezione
|
|
[Docker Deployment](#docker-deployment) sotto.
|
|
|
|
```
|
|
┌──────────────────────────┐
|
|
On-premise (LAN) │ Nginx (:80) │
|
|
└─────────────┬────────────┘
|
|
│
|
|
Produzione (dominio pubblico) → Traefik (:80/:443, TLS Let's Encrypt)
|
|
│
|
|
┌───────────┴───────────┐
|
|
│ │
|
|
┌─────▼─────┐ ┌──────▼─────┐
|
|
│ server │ │ client │
|
|
│ :8000 │ │ :5000 │
|
|
└─────┬──────┘ └────────────┘
|
|
│
|
|
┌─────▼──────┐ ┌─────────────┐
|
|
│ vision │ │ MySQL 8.0 │
|
|
│ :8100 │ │ :3306 │
|
|
│ (interno) │ └─────────────┘
|
|
└─────────────┘
|
|
```
|
|
|
|
Il worker di visione (`vision`) non pubblica porte verso l'esterno: lo
|
|
raggiunge solo il `server`, sulla rete Docker interna `tmflow-net`.
|
|
|
|
---
|
|
|
|
## Docker Deployment
|
|
|
|
Docker Compose è il metodo raccomandato ed è quello usato in produzione. Il
|
|
progetto ha **tre Dockerfile alla radice** del repository — non uno a
|
|
sottocartella:
|
|
|
|
| File | Servizio | Base |
|
|
|---|---|---|
|
|
| `Dockerfile` | `server` (FastAPI) | `python:3.11.16-slim` |
|
|
| `Dockerfile.frontend` | `client` (Flask) | `python:3.11.16-slim`, TailwindCSS compilato in uno stage `node:20.20.2-bookworm-slim` |
|
|
| `Dockerfile.vision` | `vision` (worker di visione) | `python:3.13.15-slim` |
|
|
|
|
E due file compose:
|
|
|
|
- **`docker-compose.dev.yml`** — sviluppo/on-premise, con Nginx (vedi
|
|
[Recommended Architecture](#recommended-architecture) sopra). 4 servizi:
|
|
`mysql`, `server`, `vision`, `client`, `nginx`.
|
|
- **`docker-compose.yml`** — produzione, con Traefik e TLS automatico. 4
|
|
servizi: `mysql`, `server`, `vision`, `client`. Richiede la rete Docker
|
|
esterna `traefik` (vedi `networks.traefik-net` nel file) e le variabili
|
|
`APP_DOMAIN`, `DB_ROOT_PASSWORD`, `DB_PASSWORD`, `SERVER_SECRET_KEY`,
|
|
`CLIENT_SECRET_KEY` valorizzate — senza default, `docker compose config` si
|
|
rifiuta di procedere se mancano.
|
|
|
|
Il worker di visione dipende dal submodule `vendor/visionsuite`
|
|
(`git clone --recurse-submodules`, vedi il [Quick Start del README](../README.md#quick-start-con-docker))
|
|
e richiede `VISION_ENGINE_VERSION` valorizzata **prima** della build, altrimenti
|
|
`Dockerfile.vision` fallisce di proposito:
|
|
|
|
```bash
|
|
# Sviluppo / on-premise
|
|
VISION_ENGINE_VERSION=$(git -C vendor/visionsuite rev-parse HEAD) \
|
|
docker compose -f docker-compose.dev.yml up -d --build
|
|
|
|
# Produzione
|
|
VISION_ENGINE_VERSION=$(git -C vendor/visionsuite rev-parse HEAD) \
|
|
docker compose -f docker-compose.yml up -d --build
|
|
```
|
|
|
|
Comandi comuni (sintassi `docker compose` v2, senza trattino — il plugin, non
|
|
il vecchio `docker-compose` standalone):
|
|
|
|
```bash
|
|
docker compose -f docker-compose.yml ps
|
|
docker compose -f docker-compose.yml logs -f server
|
|
docker compose -f docker-compose.yml down
|
|
```
|
|
|
|
---
|
|
|
|
## SSL/TLS
|
|
|
|
Le due installazioni gestiscono il TLS in due modi diversi, e nessuno dei due
|
|
richiede un certificato generato a mano:
|
|
|
|
- **Produzione** (`docker-compose.yml`): Traefik ottiene e rinnova da solo un
|
|
certificato Let's Encrypt per `APP_DOMAIN`, tramite il `certresolver`
|
|
configurato nelle label del servizio (`tls.certresolver=mytlschallenge`).
|
|
Serve solo che `APP_DOMAIN` punti davvero al server (DNS) e che le porte
|
|
80/443 siano raggiungibili da internet per la sfida ACME.
|
|
- **On-premise, rete LAN** (`docker-compose.dev.yml`): Nginx serve in HTTP
|
|
semplice, senza TLS — è il caso descritto in
|
|
[Recommended Architecture](#recommended-architecture). Chi ha comunque
|
|
bisogno di un certificato (es. una CA interna) lo aggiunge scommentando la
|
|
sezione SSL già presente, ma commentata, in
|
|
[`nginx/nginx.conf`](../nginx/nginx.conf).
|
|
|
|
Un'installazione manuale (senza Docker, sezioni [Server](#server-deployment) e
|
|
[Client Deployment](#client-deployment) sopra) che debba terminare il TLS da
|
|
sola, dietro il proprio reverse proxy, può ancora valorizzare
|
|
`SSL_CERTFILE`/`SSL_KEYFILE` in `.env` — variabili lasciate per questo caso,
|
|
non lette da uvicorn/gunicorn nei comandi mostrati sopra: vanno passate al
|
|
proprio reverse proxy o gestite fuori da questo progetto.
|
|
|
|
---
|
|
|
|
## Backup Strategy
|
|
|
|
### Daily Database Backups
|
|
|
|
Create backup script `/opt/tiemeasureflow/backup.sh`:
|
|
|
|
```bash
|
|
#!/bin/bash
|
|
|
|
BACKUP_DIR="/opt/tiemeasureflow/backups"
|
|
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
|
DB_NAME="tiemeasureflow"
|
|
DB_USER="tmflow"
|
|
|
|
mkdir -p $BACKUP_DIR
|
|
|
|
# MySQL dump
|
|
mysqldump -u $DB_USER -p$MYSQL_PASSWORD $DB_NAME \
|
|
| gzip > $BACKUP_DIR/db_$TIMESTAMP.sql.gz
|
|
|
|
# Keep only last 30 days
|
|
find $BACKUP_DIR -name "db_*.sql.gz" -mtime +30 -delete
|
|
|
|
# Upload to S3 (optional)
|
|
# aws s3 cp $BACKUP_DIR/db_$TIMESTAMP.sql.gz s3://your-bucket/backups/
|
|
|
|
echo "Backup completed: $BACKUP_DIR/db_$TIMESTAMP.sql.gz"
|
|
```
|
|
|
|
Schedule with crontab:
|
|
|
|
```bash
|
|
crontab -e
|
|
```
|
|
|
|
Add line:
|
|
|
|
```cron
|
|
0 2 * * * /opt/tiemeasureflow/backup.sh >> /var/log/tiemeasureflow/backup.log 2>&1
|
|
```
|
|
|
|
### Restore from Backup
|
|
|
|
```bash
|
|
# Decompress
|
|
gunzip < backups/db_20250207_020000.sql.gz | \
|
|
mysql -u tmflow -p tiemeasureflow
|
|
```
|
|
|
|
---
|
|
|
|
## Troubleshooting
|
|
|
|
### Database Connection Failed
|
|
|
|
```
|
|
sqlalchemy.exc.OperationalError: (asyncmy.errors.ProgrammingError) (2003, "Can't connect to MySQL server...")
|
|
```
|
|
|
|
**Check:**
|
|
```bash
|
|
# Verify MySQL is running (con Docker: docker compose ps mysql, ha già un healthcheck)
|
|
mysql -u tmflow -p -e "SELECT 1"
|
|
|
|
# Check .env variables — non stampare mai la password: si controlla il valore, non la connection string
|
|
grep DB_ .env
|
|
```
|
|
|
|
### Port Already in Use
|
|
|
|
```
|
|
OSError: [Errno 48] Address already in use
|
|
```
|
|
|
|
**Solution:**
|
|
```bash
|
|
# Find process on port 8000
|
|
lsof -i :8000
|
|
kill -9 <PID>
|
|
|
|
# Or use different port
|
|
uv run uvicorn src.backend.main:app --port 8001
|
|
```
|
|
|
|
### Import Errors
|
|
|
|
```
|
|
ModuleNotFoundError: No module named 'fastapi'
|
|
```
|
|
|
|
**Solution:**
|
|
```bash
|
|
uv sync --extra server --extra client --extra dev
|
|
```
|
|
|
|
### File Upload Not Working
|
|
|
|
```
|
|
uploads: Permission denied
|
|
```
|
|
|
|
**Solution:**
|
|
```bash
|
|
# Create uploads directory with correct permissions (radice del repo, non server/uploads)
|
|
mkdir -p uploads/images uploads/pdfs uploads/logos uploads/reports
|
|
chmod 755 uploads
|
|
```
|
|
|
|
### Migrations Failed
|
|
|
|
```
|
|
alembic.util.exc.CommandError: Can't locate revision identified by...
|
|
```
|
|
|
|
**Solution:**
|
|
```bash
|
|
# Check migration status
|
|
uv run alembic -c src/backend/migrations/alembic.ini current
|
|
|
|
# Reset migrations (CAUTION - deletes data)
|
|
uv run alembic -c src/backend/migrations/alembic.ini downgrade base
|
|
uv run alembic -c src/backend/migrations/alembic.ini upgrade head
|
|
```
|
|
|
|
### API Key Not Working
|
|
|
|
```
|
|
detail: "Invalid API key"
|
|
```
|
|
|
|
**Solution:**
|
|
```bash
|
|
# Regenerate API key for user (admin endpoint)
|
|
curl -X POST http://localhost:8000/api/users/1/regenerate-key \
|
|
-H "X-API-Key: admin_token"
|
|
```
|
|
|
|
---
|
|
|
|
## Performance Tuning
|
|
|
|
### MySQL
|
|
|
|
Edit `/etc/mysql/mysql.conf.d/mysqld.cnf`:
|
|
|
|
```ini
|
|
[mysqld]
|
|
# Connection pool
|
|
max_connections = 100
|
|
|
|
# Buffer sizes
|
|
innodb_buffer_pool_size = 256M
|
|
innodb_log_file_size = 100M
|
|
|
|
# Query optimization
|
|
query_cache_size = 0
|
|
query_cache_type = 0
|
|
|
|
# Logging
|
|
slow_query_log = 1
|
|
long_query_time = 2
|
|
```
|
|
|
|
Restart:
|
|
```bash
|
|
sudo systemctl restart mysql
|
|
```
|
|
|
|
### Application Workers
|
|
|
|
Increase Gunicorn workers (rule: 2 * CPU_cores + 1):
|
|
|
|
```bash
|
|
gunicorn --workers 9 ... # For 4-core system
|
|
```
|
|
|
|
### Nginx Caching
|
|
|
|
Add to nginx config:
|
|
|
|
```nginx
|
|
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=api_cache:10m max_size=1g inactive=60m;
|
|
|
|
location /api/statistics/ {
|
|
proxy_cache api_cache;
|
|
proxy_cache_valid 200 10m;
|
|
add_header X-Cache-Status $upstream_cache_status;
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Monitoring & Logging
|
|
|
|
### Application Logs
|
|
|
|
```bash
|
|
# API server
|
|
tail -f /var/log/tiemeasureflow/api_error.log
|
|
|
|
# Web client
|
|
tail -f /var/log/tiemeasureflow/web_error.log
|
|
|
|
# System
|
|
journalctl -u tiemeasureflow-api -f
|
|
```
|
|
|
|
### Health Check Endpoint
|
|
|
|
```bash
|
|
curl http://localhost:8000/api/health
|
|
```
|
|
|
|
Response:
|
|
```json
|
|
{
|
|
"status": "ok",
|
|
"service": "TieMeasureFlow API",
|
|
"version": "0.1.0"
|
|
}
|
|
```
|
|
|
|
### Database Connection Pooling
|
|
|
|
Monitor with:
|
|
|
|
```bash
|
|
# MySQL connections
|
|
mysql -e "SHOW PROCESSLIST;" | grep tiemeasureflow
|
|
```
|