> ⚠ **Veraltet.** Diese Datei stammt aus einer fruehen Version und
> nennt noch alte Server-Pfade (`/var/www/html/durchdacht/EEG-Assistent/Abrechnung/`).
> Aktuelle Deploy-Anleitung pflegt die Master-Session unter
> `Homepage/_deployment/DEPLOYMENT.md`. Diese Datei hier nicht mehr verwenden,
> sie wird beim naechsten Versionssprung entfernt.

# Deployment auf Hetzner (Debian/Ubuntu)

Zielpfad auf dem Server: `/var/www/html/durchdacht/EEG-Assistent/Abrechnung`

Nachfolgend die komplette Anleitung von null bis live. Ersetze `deine-domain.tld`
durch den echten Hostnamen. Alle Befehle als root oder mit `sudo`, außer wo
explizit anders angegeben.

---

## 1. Server vorbereiten

```bash
apt update && apt upgrade -y
apt install -y python3 python3-venv python3-pip \
               nginx certbot python3-certbot-nginx \
               rsync ufw git sqlite3 \
               build-essential libffi-dev libjpeg-dev zlib1g-dev
```

Firewall (falls noch nicht aktiv):

```bash
ufw allow OpenSSH
ufw allow 'Nginx Full'
ufw --force enable
```

Systemuser für die App (läuft nicht als root):

```bash
useradd -r -s /usr/sbin/nologin -d /var/www/html/durchdacht eeg
```

---

## 2. Projektdateien hochladen

**Von deinem Windows-Rechner** (in PowerShell oder Git-Bash), nach
`C:\Users\jakob\Documents\Claude\Abrechnung_EDA` wechseln und rsync-über-ssh
nutzen. Wenn du kein rsync hast, geht auch `scp -r`:

```bash
# Variante A: rsync (empfohlen, überträgt nur Änderungen)
rsync -avz --delete \
  --exclude '__pycache__' --exclude 'data/' --exclude 'venv/' \
  --exclude '.git' --exclude 'tests/' \
  ./v1_upload/  root@SERVER_IP:/var/www/html/durchdacht/EEG-Assistent/Abrechnung/v1_upload/

rsync -avz \
  ./main.py ./SEPA.py ./report.py \
  root@SERVER_IP:/var/www/html/durchdacht/EEG-Assistent/Abrechnung/
```

Die Struktur auf dem Server sieht dann so aus:

```
/var/www/html/durchdacht/EEG-Assistent/Abrechnung/
├── main.py              (aus dem Eltern-Ordner Abrechnung_EDA/)
├── SEPA.py
├── report.py
└── v1_upload/
    ├── app.py
    ├── licensing.py
    ├── licensing_schema.sql   ← WIRD ZUR LAUFZEIT GELESEN (init_db)
    ├── admin_cli.py
    ├── session_manager.py
    ├── requirements.txt
    ├── templates/
    ├── static/
    └── ...
```

Wichtig: `app.py` macht `sys.path.insert(0, parent_dir)`, damit werden `main.py`,
`SEPA.py`, `report.py` aus dem Eltern-Ordner gefunden. Darum liegen die direkt
eine Ebene über `v1_upload/`.

Ebenso wichtig: **`licensing_schema.sql` MUSS mitkopiert werden** — die Datei
wird bei jedem App-Start von `init_db()` eingelesen (idempotent). Fehlt sie,
wirft die App beim Start `RuntimeError: Schema-Datei fehlt: ...`. Der obige
rsync-Befehl überträgt sie automatisch mit (nicht in der `--exclude`-Liste).

Ownership setzen:

```bash
chown -R eeg:eeg /var/www/html/durchdacht/EEG-Assistent/Abrechnung
chmod -R u=rwX,g=rX,o= /var/www/html/durchdacht/EEG-Assistent/Abrechnung
```

---

## 3. Python-Venv + Abhängigkeiten

```bash
cd /var/www/html/durchdacht/EEG-Assistent/Abrechnung/v1_upload
sudo -u eeg python3 -m venv venv
sudo -u eeg ./venv/bin/pip install --upgrade pip wheel
sudo -u eeg ./venv/bin/pip install -r requirements.txt
```

Sanity-Check:

```bash
sudo -u eeg ./venv/bin/python -c "import flask, licensing, app; print('OK')"
```

---

## 4. Daten-Ordner + erster Admin + Testkonto

```bash
cd /var/www/html/durchdacht/EEG-Assistent/Abrechnung/v1_upload
sudo -u eeg mkdir -p data/backups
sudo -u eeg ./venv/bin/python admin_cli.py create-admin --username admin
# fragt interaktiv nach Passwort (mindestens 8 Zeichen, sicher wählen!)

sudo -u eeg ./venv/bin/python admin_cli.py create-account \
    --customer "Pilot-Kunde" --tokens 500
# notiert die ausgegebene UID (EEGA-XXXX-XXXX-XXXX)
```

Die DB liegt ab jetzt unter
`/var/www/html/durchdacht/EEG-Assistent/Abrechnung/v1_upload/data/licensing.db`.

---

## 5. Gunicorn-Test (optional, zum Debuggen)

Im Vordergrund starten, um zu sehen, ob alles läuft:

```bash
cd /var/www/html/durchdacht/EEG-Assistent/Abrechnung/v1_upload
sudo -u eeg ./venv/bin/gunicorn -w 1 -b 127.0.0.1:8081 \
    --forwarded-allow-ips 127.0.0.1 app:app
```

In anderem Terminal: `curl -I http://127.0.0.1:8081/EEG-Assistent/abrechnung`
→ sollte `200 OK` liefern. Dann mit Strg+C stoppen.

**Hinweis `-w 1`:** Die Rate-Limits (siehe `LICENSING.md` §5) leben im
Prozess-Speicher. Mehr als 1 Worker vervielfacht die effektiven Limits.
Das reicht für einen Pilotbetrieb locker aus; bei höherem Traffic später auf
Redis-backed Limiter umziehen.

---

## 6. Systemd-Service

Datei anlegen:

```bash
cat > /etc/systemd/system/eeg-abrechnung.service <<'EOF'
[Unit]
Description=EEG-Abrechnungs-Assistent (Gunicorn)
After=network.target

[Service]
User=eeg
Group=eeg
WorkingDirectory=/var/www/html/durchdacht/EEG-Assistent/Abrechnung/v1_upload
Environment="PATH=/var/www/html/durchdacht/EEG-Assistent/Abrechnung/v1_upload/venv/bin"
ExecStart=/var/www/html/durchdacht/EEG-Assistent/Abrechnung/v1_upload/venv/bin/gunicorn \
    -w 1 \
    -b 127.0.0.1:8081 \
    --timeout 120 \
    --forwarded-allow-ips 127.0.0.1 \
    --access-logfile - \
    --error-logfile - \
    app:app
Restart=on-failure
RestartSec=3

# Härtung
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=full
ProtectHome=true
ReadWritePaths=/var/www/html/durchdacht/EEG-Assistent/Abrechnung/v1_upload

[Install]
WantedBy=multi-user.target
EOF

systemctl daemon-reload
systemctl enable --now eeg-abrechnung
systemctl status eeg-abrechnung
```

Logs beobachten:

```bash
journalctl -u eeg-abrechnung -f
```

---

## 7. Nginx als Reverse-Proxy

Annahme: die Domain `deine-domain.tld` zeigt bereits auf den Server (A-Record).
Wenn du schon andere Sub-Projekte unter `durchdacht/*` hostest, erweitere den
bestehenden Server-Block; sonst:

```bash
cat > /etc/nginx/sites-available/durchdacht <<'EOF'
server {
    listen 80;
    listen [::]:80;
    server_name deine-domain.tld;

    # Upload-Limit passend zur App (app.py MAX_CONTENT_LENGTH = 100 MB)
    client_max_body_size 105M;

    # Kunden-UI + Admin + API alle unter /EEG-Assistent/
    location /EEG-Assistent/ {
        proxy_pass http://127.0.0.1:8081;
        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;

        # Für SSE (Server-Sent Events im /api/v1/preview-stream)
        proxy_buffering off;
        proxy_read_timeout 300s;
    }

    # Admin-Panel unter /EEG-Assistent/admin
    # Die App verdrahtet /admin intern – damit nach außen sauber unter
    # /EEG-Assistent/ bleibt, leiten wir /EEG-Assistent/admin dorthin um.
    # Alternative: separate Subdomain. Siehe unten.
    location /EEG-Assistent/admin {
        rewrite ^/EEG-Assistent/admin(/.*)?$ /admin$1 break;
        proxy_pass http://127.0.0.1:8081;
        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;
    }

    location = /datenschutz {
        proxy_pass http://127.0.0.1:8081;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}
EOF

ln -s /etc/nginx/sites-available/durchdacht /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginx
```

**Einfachere Alternative:** separate Subdomain `abrechnung.deine-domain.tld`
komplett für diese App. Dann braucht's keine Rewrites und keine Pfad-Akrobatik:

```nginx
server {
    listen 80;
    server_name abrechnung.deine-domain.tld;
    client_max_body_size 105M;

    location / {
        proxy_pass http://127.0.0.1:8081;
        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_buffering off;
        proxy_read_timeout 300s;
    }
}
```

---

## 8. HTTPS via Let's Encrypt

```bash
certbot --nginx -d deine-domain.tld
# bzw. -d abrechnung.deine-domain.tld
```

Certbot erneuert das Zertifikat via cron/timer automatisch alle 90 Tage.

---

## 9. Erreichbarkeit prüfen

| Wer | URL |
|---|---|
| Kunde | `https://deine-domain.tld/EEG-Assistent/abrechnung` (bzw. Subdomain `/`) |
| Admin | `https://deine-domain.tld/EEG-Assistent/admin` (bzw. Subdomain `/admin`) |
| DSGVO | `https://deine-domain.tld/datenschutz` |

---

## 10. Backups

`licensing.py` erzeugt alle 60 Minuten ein Backup nach `data/backups/`
(rollende 48 Stück). Zusätzlich Tages-Backup off-site per cron:

```bash
cat > /etc/cron.daily/eeg-abrechnung-backup <<'EOF'
#!/bin/bash
DST=/var/backups/eeg-abrechnung
mkdir -p "$DST"
sqlite3 /var/www/html/durchdacht/EEG-Assistent/Abrechnung/v1_upload/data/licensing.db \
    ".backup '$DST/licensing_$(date +%Y%m%d).db'"
find "$DST" -name 'licensing_*.db' -mtime +30 -delete
EOF
chmod +x /etc/cron.daily/eeg-abrechnung-backup
```

Restore-Verfahren siehe `LICENSING.md` §8.

---

## 11. Update-Flow (für Code-Änderungen)

Von deinem Rechner aus:

```bash
# 1. Code hochladen
rsync -avz --delete \
  --exclude '__pycache__' --exclude 'data/' --exclude 'venv/' --exclude 'tests/' \
  ./v1_upload/  root@SERVER_IP:/var/www/html/durchdacht/EEG-Assistent/Abrechnung/v1_upload/

# 2. Auf dem Server neu starten
ssh root@SERVER_IP "systemctl restart eeg-abrechnung && journalctl -u eeg-abrechnung -n 20"
```

Wenn sich requirements geändert haben:

```bash
ssh root@SERVER_IP "cd /var/www/html/durchdacht/EEG-Assistent/Abrechnung/v1_upload && \
    sudo -u eeg ./venv/bin/pip install -r requirements.txt && \
    systemctl restart eeg-abrechnung"
```

---

## 12. Troubleshooting

| Symptom | Check |
|---|---|
| `502 Bad Gateway` | `systemctl status eeg-abrechnung` + `journalctl -u eeg-abrechnung -n 100` |
| `413 Request Entity Too Large` | Nginx `client_max_body_size` hochsetzen |
| `/admin` redirectet falsch | Rewrite-Regel prüfen oder auf separate Subdomain umziehen |
| Rate-Limit zu aggressiv | In `app.py` Konstanten anpassen, Service neu starten |
| SSE-Progress bleibt stehen | `proxy_buffering off;` und `proxy_read_timeout 300s;` im Nginx-Block? |
| DB-Lock-Fehler | `lsof` auf die db-Datei; nur der Service-User darf schreiben |
| Alle Admin-Sessions weg nach Neustart | Erwartet — Admin-Sessions sind In-Memory, Kunden-Sessions in DB |
| Login/Admin-Login sofort 429 | IP im DB-Burst-Limit? `sqlite3 data/licensing.db "DELETE FROM failed_logins WHERE ip='X.X.X.X';"` |

---

## 13. Monitoring-Minimum

```bash
# Logs live
journalctl -u eeg-abrechnung -f

# HTTP-Health von außen
curl -I https://deine-domain.tld/EEG-Assistent/abrechnung

# Accounts anzeigen
sudo -u eeg /var/www/html/durchdacht/EEG-Assistent/Abrechnung/v1_upload/venv/bin/python \
    /var/www/html/durchdacht/EEG-Assistent/Abrechnung/v1_upload/admin_cli.py list

# Audit-Log
sudo -u eeg /var/www/html/durchdacht/EEG-Assistent/Abrechnung/v1_upload/venv/bin/python \
    /var/www/html/durchdacht/EEG-Assistent/Abrechnung/v1_upload/admin_cli.py log --limit 30
```

Für eine Mail-Benachrichtigung bei Service-Crash lässt sich folgender
Override-Drop-in ergänzen:

```bash
mkdir -p /etc/systemd/system/eeg-abrechnung.service.d
cat > /etc/systemd/system/eeg-abrechnung.service.d/onfailure.conf <<'EOF'
[Unit]
OnFailure=status-email@%n.service
EOF
```
(entsprechender `status-email@.service` siehe jede gängige Systemd-Mail-Anleitung)

---

## Checkliste fürs Go-Live

- [ ] DNS zeigt auf Hetzner-IP
- [ ] `systemctl status eeg-abrechnung` = `active (running)`
- [ ] Nginx-Test mit `nginx -t` sauber
- [ ] HTTPS-Zertifikat aktiv (Certbot)
- [ ] Erster Admin angelegt + Passwort sicher verwahrt
- [ ] `client_max_body_size` hoch genug (≥ 105M)
- [ ] `proxy_buffering off` für SSE gesetzt
- [ ] Off-site Backup-Cron eingerichtet
- [ ] UFW aktiviert, nur SSH + 80/443 offen
- [ ] Test-UID von außen durchgespielt: Login → Upload → Process → Download → Cookie weg

Viel Erfolg beim Livegang.
