Files
TieMeasureFlow/docs/DEPLOYMENT.md
T
Adriano 4de7d78b66 docs: document stations end-to-end (user guide + API + deployment)
Stations were the headline V2.0.0 feature but had no user-facing
documentation outside the architecture page. Filled the gap across
the three operational docs.

USER_GUIDE.md
- New entries in "Key Concepts": Station and Station assignment.
- New "Recipes you see are filtered by station" subsection in the
  MeasurementTec workflow, explaining why the Select Recipe page may
  legitimately show fewer recipes than expected and what the
  "Stazione non configurata" error means at the operator level.
- New "Station Management" section under Admin Workflow covering:
  the mental model, station create/edit/delete, the two-column
  recipe-assignment modal, the immutable-code rule, the role of the
  ST-DEFAULT seed station, and the tablet deployment cheat sheet.
- Admin role description updated to mention stations.

DEPLOYMENT.md
- Environment Variables Reference: added STATION_CODE row and noted
  that an empty value triggers the deliberate fail-fast HTTP 503 on
  /measure/select. Updated RATE_LIMIT_GENERAL default (300, per the
  V2.0.0 perf change). Clarified UPLOAD_DIR resolves against the
  project root.

API.md
- New "Stations" endpoint section listing all eight routes with
  request/response examples and the 401/403/404/409 error contract:
  GET / POST /stations, GET /stations/{id}, PUT /stations/{id},
  DELETE /stations/{id}, GET /stations/{id}/recipes,
  GET /stations/by-code/{code}/recipes (the operator-facing one used
  by the Flask client), POST /stations/{id}/recipes,
  DELETE /stations/{id}/recipes/{recipe_id}.
- TOC updated with the new "Stations" anchor.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 17:26:43 +02:00

17 KiB

TieMeasureFlow Deployment Guide

This guide covers deploying TieMeasureFlow in development, staging, and production environments.

Table of Contents

  1. Prerequisites
  2. Environment Setup
  3. Database Setup
  4. Server Deployment
  5. Client Deployment
  6. Production Deployment
  7. Docker Deployment
  8. SSL/HTTPS
  9. Backup Strategy
  10. 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

# Python 3.11+
python3 --version

# MySQL 8.0
mysql --version

# Node.js (optional)
node --version
npm --version

Windows

Download and install:


Environment Setup

1. Clone Repository

git clone <repository-url>
cd TieMeasureFlow

2. Create .env File

Copy the example and customize for your environment:

cp .env.example .env

3. Configure .env

Edit .env with your settings:

# =====================================================================
# 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)

Database Setup

1. Create MySQL Database and User

mysql -u root -p
-- 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

cd server
pip install -r requirements.txt
alembic upgrade head

This creates all required tables:

  • users - System users
  • recipes - Measurement recipes
  • recipe_versions - Immutable recipe versions
  • recipe_tasks - Tasks within recipes
  • recipe_subtasks - Subtasks within tasks (individual measurements)
  • measurements - Recorded measurements
  • access_logs - API access audit trail
  • system_settings - Configuration key-value pairs
  • recipe_version_audit - Recipe change history

3. Create Initial Admin User (Optional)

Use the Flask client to create the first admin user, or run:

cd server
python -c "
from database import init_db, SessionLocal
from services.auth_service import create_user
import asyncio

async def init():
    await init_db()
    async with SessionLocal() as db:
        await create_user(
            db,
            username='admin',
            password='change_me_first_login',
            display_name='Admin',
            email='admin@example.com',
            roles=['Maker', 'MeasurementTec', 'Metrologist'],
            is_admin=True
        )
        await db.commit()
        print('Admin user created: admin / change_me_first_login')

asyncio.run(init())
"

Server Deployment

Development Server

cd server
pip install -r requirements.txt
uvicorn 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 Server (Gunicorn + Uvicorn)

Install Gunicorn:

pip install gunicorn

Start server:

cd server
gunicorn main:app \
  --workers 4 \
  --worker-class uvicorn.workers.UvicornWorker \
  --bind 0.0.0.0:8000 \
  --access-logfile /var/log/tiemeasureflow/access.log \
  --error-logfile /var/log/tiemeasureflow/error.log \
  --log-level info

Production Server (Waitress - Windows)

Install Waitress:

pip install waitress

Start server:

cd server
waitress-serve --host=0.0.0.0 --port=8000 main:app

systemd Service (Linux)

Create /etc/systemd/system/tiemeasureflow-api.service:

[Unit]
Description=TieMeasureFlow API Server
After=network.target mysql.service

[Service]
Type=notify
User=tiemeasureflow
WorkingDirectory=/opt/tiemeasureflow/server
Environment="PATH=/opt/tiemeasureflow/venv/bin"
ExecStart=/opt/tiemeasureflow/venv/bin/gunicorn main:app \
  --workers 4 \
  --worker-class uvicorn.workers.UvicornWorker \
  --bind 0.0.0.0:8000

Restart=on-failure
RestartSec=10

[Install]
WantedBy=multi-user.target

Enable and start:

sudo systemctl daemon-reload
sudo systemctl enable tiemeasureflow-api.service
sudo systemctl start tiemeasureflow-api.service
sudo systemctl status tiemeasureflow-api.service

View logs:

sudo journalctl -u tiemeasureflow-api -f

Client Deployment

Development Server

cd client
pip install -r requirements.txt
flask run --host 0.0.0.0 --port 5000

Runs at http://0.0.0.0:5000

Compile Tailwind CSS (Optional)

cd client
npx tailwindcss -i static/css/input.css -o static/css/tailwind.css --watch

Production Server (Gunicorn)

pip install gunicorn
cd client
gunicorn --workers 4 --bind 0.0.0.0:5000 app:app

systemd Service (Linux)

Create /etc/systemd/system/tiemeasureflow-web.service:

[Unit]
Description=TieMeasureFlow Web Client
After=network.target tiemeasureflow-api.service

[Service]
Type=notify
User=tiemeasureflow
WorkingDirectory=/opt/tiemeasureflow/client
Environment="PATH=/opt/tiemeasureflow/venv/bin"
Environment="FLASK_ENV=production"
ExecStart=/opt/tiemeasureflow/venv/bin/gunicorn \
  --workers 4 \
  --bind 0.0.0.0:5000 \
  app:app

Restart=on-failure
RestartSec=10

[Install]
WantedBy=multi-user.target

Production Deployment

Security Checklist

  • Change all default passwords in .env
  • Generate random SERVER_SECRET_KEY (32+ characters)
  • 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
┌─────────────────┐
│  Nginx Proxy    │ (Port 80/443)
└────────┬────────┘
         │
    ┌────┴────┐
    │          │
┌───▼──┐  ┌───▼──┐
│API   │  │Web   │
│:8000 │  │:5000 │
└──────┘  └──────┘
    │          │
    └────┬─────┘
         │
    ┌────▼────┐
    │ MySQL   │
    │ :3306   │
    └─────────┘

Nginx Configuration

Create /etc/nginx/sites-available/tiemeasureflow:

upstream tiemeasureflow_api {
    server 127.0.0.1:8000;
}

upstream tiemeasureflow_web {
    server 127.0.0.1:5000;
}

server {
    listen 80;
    server_name yourdomain.com;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name yourdomain.com;

    ssl_certificate /etc/ssl/certs/yourdomain.com.crt;
    ssl_certificate_key /etc/ssl/private/yourdomain.com.key;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;

    client_max_body_size 50M;

    # API
    location /api/ {
        proxy_pass http://tiemeasureflow_api;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    # Web Client
    location / {
        proxy_pass http://tiemeasureflow_web;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    # Static files (optional caching)
    location /static/ {
        proxy_pass http://tiemeasureflow_web;
        expires 1d;
        add_header Cache-Control "public, immutable";
    }
}

Enable and test:

sudo ln -s /etc/nginx/sites-available/tiemeasureflow /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl restart nginx

Docker Deployment

Docker Compose (Complete Stack)

The project includes a docker-compose.yml for easy deployment:

# Build images
docker-compose build

# Start services
docker-compose up -d

# View logs
docker-compose logs -f

# Stop services
docker-compose down

Manual Docker Build

API Server

FROM python:3.11-slim

WORKDIR /app/server

COPY server/requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY server/ .
COPY .env ..

EXPOSE 8000

CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

Build and run:

docker build -f server/Dockerfile -t tiemeasureflow-api .
docker run -p 8000:8000 --env-file .env tiemeasureflow-api

Web Client

FROM python:3.11-slim

WORKDIR /app/client

COPY client/requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY client/ .
COPY .env ..

EXPOSE 5000

CMD ["gunicorn", "--workers", "4", "--bind", "0.0.0.0:5000", "app:app"]

Build and run:

docker build -f client/Dockerfile -t tiemeasureflow-web .
docker run -p 5000:5000 --env-file .env tiemeasureflow-web

SSL/TLS

Self-Signed Certificate (Development)

openssl req -x509 -newkey rsa:4096 -keyout server.key -out server.crt -days 365 -nodes

Let's Encrypt (Production)

sudo apt install certbot python3-certbot-nginx
sudo certbot certonly --nginx -d yourdomain.com

Configure in .env

SSL_CERTFILE=/etc/ssl/certs/yourdomain.com.crt
SSL_KEYFILE=/etc/ssl/private/yourdomain.com.key

Start Server with SSL

cd server
gunicorn main:app \
  --workers 4 \
  --worker-class uvicorn.workers.UvicornWorker \
  --bind 0.0.0.0:8443 \
  --certfile /etc/ssl/certs/yourdomain.com.crt \
  --keyfile /etc/ssl/private/yourdomain.com.key

Backup Strategy

Daily Database Backups

Create backup script /opt/tiemeasureflow/backup.sh:

#!/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:

crontab -e

Add line:

0 2 * * * /opt/tiemeasureflow/backup.sh >> /var/log/tiemeasureflow/backup.log 2>&1

Restore from Backup

# 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:

# Verify MySQL is running
mysql -u tmflow -p -e "SELECT 1"

# Check .env variables
grep DB_ .env

# Test connection string
cd server && python -c "from config import settings; print(settings.database_url)"

Port Already in Use

OSError: [Errno 48] Address already in use

Solution:

# Find process on port 8000
lsof -i :8000
kill -9 <PID>

# Or use different port
uvicorn main:app --port 8001

Import Errors

ModuleNotFoundError: No module named 'fastapi'

Solution:

cd server
pip install -r requirements.txt

File Upload Not Working

/app/server/uploads: Permission denied

Solution:

# Create uploads directory with correct permissions
mkdir -p server/uploads
chmod 755 server/uploads

Migrations Failed

alembic.util.exc.CommandError: Can't locate revision identified by...

Solution:

cd server
# Check migration status
alembic current

# Reset migrations (CAUTION - deletes data)
alembic downgrade base
alembic upgrade head

API Key Not Working

detail: "Invalid API key"

Solution:

# 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:

[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:

sudo systemctl restart mysql

Application Workers

Increase Gunicorn workers (rule: 2 * CPU_cores + 1):

gunicorn --workers 9 ...  # For 4-core system

Nginx Caching

Add to nginx config:

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

# 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

curl http://localhost:8000/api/health

Response:

{
  "status": "ok",
  "service": "TieMeasureFlow API",
  "version": "0.1.0"
}

Database Connection Pooling

Monitor with:

# MySQL connections
mysql -e "SHOW PROCESSLIST;" | grep tiemeasureflow