Files
timemaster/docs/deployment.md
T
patrick ada1b51f33 docs: vollständige Projektdokumentation hinzugefügt
- 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>
2026-05-24 11:29:44 +02:00

430 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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:
```bash
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:
```bash
# 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:**
```sql
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:**
```bash
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:**
```bash
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:
```bash
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
```bash
cp /opt/timemaster/timemaster.service /etc/systemd/system/
systemctl daemon-reload
systemctl enable timemaster
systemctl start timemaster
```
---
## Umgebungsvariablen (.env)
Datei: `/opt/timemaster/backend/.env`
```bash
# === 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_KEY` muss einmalig und zufällig sein (min. 32 Zeichen). Die Applikation verweigert den Start mit dem Default-Wert `change-me-in-production`.
- `DATABASE_URL` mit dem echten Passwort des `timemaster`-Datenbankusers.
- `RESEND_API_KEY` für ausgehende E-Mails (Einladungen, Passwort-Reset, Willkommensmails).
---
## Systemd-Service
Datei: `/etc/systemd/system/timemaster.service`
```ini
[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:
```bash
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`)
```nginx
# 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:
```bash
apt-get install certbot python3-certbot-nginx
certbot --nginx -d yourdomain.com -d www.yourdomain.com
```
Nach nginx-Konfigurationsänderungen:
```bash
nginx -t && systemctl reload nginx
```
---
## Deployment-Workflow (reguläre Updates)
Der gesamte Deployment-Prozess ist in `update.sh` automatisiert.
### Vollständiges Deployment
```bash
cd /home/sysops/Dokumente/Scripte/timemaster
./update.sh
```
Führt alle Schritte für beide Server durch:
1. Tests auf Server 137 (pytest -x -q)
2. Frontend lokal bauen (npm run build)
3. `git pull --ff-only origin main` auf Server(n)
4. Alembic: `alembic upgrade head`
5. Service: `systemctl restart timemaster`
6. Health-Check: `curl http://localhost:8000/health` (3 Versuche)
7. Frontend-Dist per rsync übertragen
### Optionen
```bash
./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)
```bash
# 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
```bash
# 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
```bash
curl https://yourdomain.com/health
# → { "status": "ok", "database": "ok", "redis": "ok" }
```
### Service-Status
```bash
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
```bash
# 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
```bash
# 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
```bash
# 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
```bash
# 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:
1. Tests immer nur auf Server 137 ausführen (update.sh-Default)
2. Migration zuerst auf 137, dann auf 164
3. Service-Restart auf 137, dann auf 164
Bei divergentem Datenbankzustand zwischen den Servern: Server 164 aus dem Dump von Server 137 wiederherstellen.