- docs/api.md: komplette API-Referenz (1375 Zeilen, alle Endpunkte) - docs/architecture.md: Tech-Stack, DB-Schema, RLS-Architektur, Auth-Flow - docs/deployment.md: Setup, nginx, systemd, update.sh, Backup/Rollback - docs/development.md: Dev-Setup, Test-Workflow, Code-Konventionen, Fallstricke Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
11 KiB
TimeMaster – Deployment-Guide
Stand: 2026-05-24
Infrastruktur-Übersicht
| Server | IP | Rolle |
|---|---|---|
| Primary | 192.168.1.137 | Produktion, Tests, Primär-DB |
| Secondary | 192.168.1.164 | Replikat / Fallback |
Beide Server laufen Ubuntu 22.04 oder 24.04 LTS (amd64). Kein Docker in Phase 1 – alle Dienste laufen nativ als systemd-Units.
Voraussetzungen
Auf jedem Server müssen folgende Pakete installiert sein:
apt-get install -y \
python3 python3-venv python3-dev python3-pip \
postgresql postgresql-contrib \
redis-server \
nginx \
git curl build-essential libpq-dev
Node.js 20 für den Frontend-Build wird nur auf der Entwicklungsmaschine benötigt, nicht auf den Servern. Das Frontend wird lokal gebaut und als statisches dist/-Verzeichnis per rsync übertragen.
Erstes Setup (einmalig)
Das Setup-Skript setup_server.sh übernimmt alle Schritte vollautomatisch:
# Auf dem Server als root
bash /opt/timemaster/setup_server.sh
Was das Skript tut
Schritt 1 – System-Pakete:
apt-get update && apt-get upgrade -y sowie alle oben genannten Abhängigkeiten.
Schritt 2 – PostgreSQL:
CREATE ROLE timemaster LOGIN PASSWORD 'timemaster_secret_change_me';
CREATE DATABASE timemaster_db OWNER timemaster;
CREATE DATABASE timemaster_test OWNER timemaster; -- für pytest
GRANT ALL PRIVILEGES ON DATABASE timemaster_db TO timemaster;
Das Passwort in Produktion sofort nach Setup ändern (siehe .env).
Schritt 3 – Redis:
systemctl enable redis-server && systemctl start redis-server
Schritt 4 – Python venv:
cd /opt/timemaster/backend
python3 -m venv venv
source venv/bin/activate
pip install --upgrade pip
pip install -r requirements.txt
Schritt 5 – Alembic-Migrationen:
alembic upgrade head
Führt alle Migrationen von 0001 bis zur aktuellen Kopfversion aus.
Schritt 6 – nginx:
Legt /etc/nginx/sites-available/timemaster an, verlinkt nach sites-enabled und startet nginx.
Manuelles Setup der .env-Datei
Vor dem ersten Start muss /opt/timemaster/backend/.env angelegt werden:
cp /opt/timemaster/backend/.env.example /opt/timemaster/backend/.env
nano /opt/timemaster/backend/.env
Pflichtfelder in Produktion (siehe nächster Abschnitt).
systemd-Service aktivieren
cp /opt/timemaster/timemaster.service /etc/systemd/system/
systemctl daemon-reload
systemctl enable timemaster
systemctl start timemaster
Umgebungsvariablen (.env)
Datei: /opt/timemaster/backend/.env
# === App ===
APP_NAME=TimeMaster
APP_ENV=production # production | development
SECRET_KEY=<min. 32 zufällige Zeichen – openssl rand -hex 32>
FRONTEND_URL=https://yourdomain.com
ALLOWED_HOSTS=yourdomain.com,www.yourdomain.com
# === Datenbank ===
DATABASE_URL=postgresql+asyncpg://timemaster:<passwort>@localhost:5432/timemaster_db
# === Redis ===
REDIS_URL=redis://localhost:6379/0
# === JWT ===
ACCESS_TOKEN_EXPIRE_MINUTES=30
REFRESH_TOKEN_EXPIRE_DAYS=30
# === E-Mail (Resend.com) ===
RESEND_API_KEY=re_<key>
EMAIL_FROM=noreply@yourdomain.com
EMAIL_FROM_NAME=TimeMaster
# === Erster Super-Admin (wird beim ersten Start angelegt) ===
FIRST_SUPERADMIN_EMAIL=admin@yourdomain.com
FIRST_SUPERADMIN_PASSWORD=<starkes-passwort>
Pflicht in Produktion:
SECRET_KEYmuss einmalig und zufällig sein (min. 32 Zeichen). Die Applikation verweigert den Start mit dem Default-Wertchange-me-in-production.DATABASE_URLmit dem echten Passwort destimemaster-Datenbankusers.RESEND_API_KEYfür ausgehende E-Mails (Einladungen, Passwort-Reset, Willkommensmails).
Systemd-Service
Datei: /etc/systemd/system/timemaster.service
[Unit]
Description=TimeMaster FastAPI Backend
After=network.target postgresql.service redis.service
Requires=postgresql.service redis.service
[Service]
Type=exec
User=www-data
Group=www-data
WorkingDirectory=/opt/timemaster/backend
EnvironmentFile=/opt/timemaster/backend/.env
ExecStart=/opt/timemaster/backend/venv/bin/uvicorn app.main:app \
--host 127.0.0.1 \
--port 8000 \
--workers 4 \
--log-level info \
--access-log
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=5
StandardOutput=journal
StandardError=journal
SyslogIdentifier=timemaster
# Sicherheitsoptionen
NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=strict
ReadWritePaths=/opt/timemaster/backend
[Install]
WantedBy=multi-user.target
Uvicorn läuft mit 4 Workern auf Port 8000, nur auf localhost. nginx leitet eingehende Anfragen weiter.
Service-Befehle:
systemctl start timemaster
systemctl stop timemaster
systemctl restart timemaster
systemctl status timemaster
journalctl -u timemaster -f # Live-Logs
journalctl -u timemaster -n 100 # Letzte 100 Zeilen
nginx-Konfiguration
Datei: /etc/nginx/sites-available/timemaster (symlink nach sites-enabled)
# HTTP → HTTPS Redirect
server {
listen 80;
server_name yourdomain.com www.yourdomain.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name yourdomain.com www.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
# HSTS (nach agent-08 verpflichtend)
add_header Strict-Transport-Security "max-age=31536000" always;
client_max_body_size 20M;
# API Backend
location /api/ {
proxy_pass http://127.0.0.1:8000;
proxy_http_version 1.1;
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;
proxy_read_timeout 60s;
}
# FastAPI Swagger Docs (nur in dev-Deployments!)
location /docs {
proxy_pass http://127.0.0.1:8000/docs;
}
location /openapi.json {
proxy_pass http://127.0.0.1:8000/openapi.json;
}
# React Frontend (SPA – alle Routen über index.html)
location / {
root /opt/timemaster/frontend/dist;
index index.html;
try_files $uri $uri/ /index.html;
expires 1d;
add_header Cache-Control "public, must-revalidate";
}
# Statische Backend-Uploads
location /static/ {
alias /opt/timemaster/backend/static/;
expires 7d;
}
}
TLS-Zertifikat mit Let's Encrypt:
apt-get install certbot python3-certbot-nginx
certbot --nginx -d yourdomain.com -d www.yourdomain.com
Nach nginx-Konfigurationsänderungen:
nginx -t && systemctl reload nginx
Deployment-Workflow (reguläre Updates)
Der gesamte Deployment-Prozess ist in update.sh automatisiert.
Vollständiges Deployment
cd /home/sysops/Dokumente/Scripte/timemaster
./update.sh
Führt alle Schritte für beide Server durch:
- Tests auf Server 137 (pytest -x -q)
- Frontend lokal bauen (npm run build)
git pull --ff-only origin mainauf Server(n)- Alembic:
alembic upgrade head - Service:
systemctl restart timemaster - Health-Check:
curl http://localhost:8000/health(3 Versuche) - Frontend-Dist per rsync übertragen
Optionen
./update.sh --no-tests # Tests überspringen (schneller, nur im Notfall)
./update.sh --no-frontend # Frontend-Build überspringen (nur Backend-Änderungen)
./update.sh --server 137 # Nur Primary
./update.sh --server 164 # Nur Secondary
./update.sh --dry-run # Alle Befehle zeigen, nichts ausführen
Manueller Deploy (ohne update.sh)
# 1. Tests
ssh root@192.168.1.137 "cd /opt/timemaster/backend && source venv/bin/activate && pytest -x -q"
# 2. Frontend lokal bauen
cd /home/sysops/Dokumente/Scripte/timemaster/frontend
npm run build
# 3. Code auf Server synchronisieren
ssh root@192.168.1.137 "cd /opt/timemaster && git pull --ff-only origin main"
# 4. Migrationen
ssh root@192.168.1.137 "cd /opt/timemaster/backend && source venv/bin/activate && alembic upgrade head"
# 5. Service neustarten
ssh root@192.168.1.137 "systemctl restart timemaster"
# 6. Frontend-Dist synchronisieren
rsync -avz --delete \
/home/sysops/Dokumente/Scripte/timemaster/frontend/dist/ \
root@192.168.1.137:/opt/timemaster/frontend/dist/
# 7. Logs prüfen
ssh root@192.168.1.137 "journalctl -u timemaster -n 50"
Monitoring und Logs
Applikations-Logs
# Live-Stream
ssh root@192.168.1.137 "journalctl -u timemaster -f"
# Letzte 100 Zeilen
ssh root@192.168.1.137 "journalctl -u timemaster -n 100 --no-pager"
# Fehler der letzten Stunde
ssh root@192.168.1.137 "journalctl -u timemaster --since='1 hour ago' -p err"
Health-Endpoint
curl https://yourdomain.com/health
# → { "status": "ok", "database": "ok", "redis": "ok" }
Service-Status
ssh root@192.168.1.137 "systemctl status timemaster"
ssh root@192.168.1.137 "systemctl status nginx"
ssh root@192.168.1.137 "systemctl status postgresql"
ssh root@192.168.1.137 "systemctl status redis-server"
Backup-Strategie
PostgreSQL-Dump
# Vollständiger Dump (täglich per cron)
ssh root@192.168.1.137 "pg_dump -U timemaster timemaster_db | gzip > /opt/backups/timemaster_$(date +%Y%m%d).sql.gz"
# Wiederherstellung
ssh root@192.168.1.137 "gunzip -c /opt/backups/timemaster_20260524.sql.gz | psql -U timemaster timemaster_db"
Empfehlung: täglicher pg_dump-Cron + Upload auf externen S3-kompatiblen Speicher. Backup 30 Tage aufbewahren.
Frontend-Dist
Das dist/-Verzeichnis kann jederzeit aus dem lokalen Build reproduziert werden und muss nicht separat gesichert werden.
Rollback-Verfahren
Code-Rollback
# Auf Server: bestimmten Commit auschecken
ssh root@192.168.1.137 "cd /opt/timemaster && git log --oneline -10"
ssh root@192.168.1.137 "cd /opt/timemaster && git checkout <commit-hash>"
ssh root@192.168.1.137 "systemctl restart timemaster"
Alembic-Rollback
# Eine Version zurück
ssh root@192.168.1.137 "cd /opt/timemaster/backend && source venv/bin/activate && alembic downgrade -1"
# Auf bestimmte Version zurück
ssh root@192.168.1.137 "cd /opt/timemaster/backend && source venv/bin/activate && alembic downgrade 0023"
Achtung: Datenverlust möglich, wenn die downgrade()-Funktion Spalten löscht. Vor dem Downgrade immer einen pg_dump anlegen.
Alembic-Diagnosebefehle
# Aktuelle Migration-Version
ssh root@192.168.1.137 "cd /opt/timemaster/backend && source venv/bin/activate && alembic current"
# Migrationsverlauf
ssh root@192.168.1.137 "cd /opt/timemaster/backend && source venv/bin/activate && alembic history --verbose"
Zwei-Server-Setup
Server 137 ist der Primary. Server 164 ist das Fallback/Replikat.
Beide Server beziehen den Code über git pull aus demselben Gitea-Repository (gitea.perlbach24.de/scripte/timemaster.git). Jeder Server hat seine eigene PostgreSQL-Instanz. Es gibt keine automatische Replikation zwischen den Datenbanken – bei Failover muss manuell ein pg_dump vom Primary wiederhergestellt werden.
Update-Reihenfolge:
- Tests immer nur auf Server 137 ausführen (update.sh-Default)
- Migration zuerst auf 137, dann auf 164
- Service-Restart auf 137, dann auf 164
Bei divergentem Datenbankzustand zwischen den Servern: Server 164 aus dem Dump von Server 137 wiederherstellen.