Project_Manager/DEPLOYMENT.md
2026-06-26 18:21:22 +02:00

364 lines
9.9 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.

# DEPLOYMENT.md — Projekt-Hub auf dem VPS installieren
Diese Anleitung beschreibt, wie die fertige Flask-App (siehe `CLAUDE.md` und
`TASKLISTE.md`) auf einem Ubuntu-VPS produktiv läuft. Zielarchitektur:
```
Internet → nginx (Port 80/443, TLS) → gunicorn (Unix-Socket) → Flask-App → SQLite
```
systemd sorgt dafür, dass die App automatisch beim Booten startet und bei einem Absturz neu startet.
Vorausgesetzt: Ubuntu-VPS mit SSH-Zugang und sudo-Rechten. Domain ist optional —
diese Anleitung funktioniert zunächst auch nur mit der Server-IP (HTTPS dann erst,
sobald eine Domain feststeht).
---
## 1. System vorbereiten
```bash
sudo apt update && sudo apt upgrade -y
sudo apt install -y python3 python3-venv python3-pip nginx git
```
Eigenen, nicht-root Nutzer für die App anlegen (falls noch nicht vorhanden):
```bash
sudo adduser projekthub
sudo usermod -aG sudo projekthub # optional, falls dieser Nutzer auch administrieren soll
```
Ab hier alles als `projekthub`-Nutzer ausführen (`su - projekthub`), außer wo
explizit `sudo` für Systemdienste nötig ist.
---
## 2. Code auf den Server bringen
Variante A — über Git (empfohlen, falls Repo z.B. auf einem privaten Git-Server
oder GitHub liegt):
```bash
cd ~
git clone <DEIN_REPO_URL> projekt-hub
cd projekt-hub
```
Variante B — Dateien direkt hochladen (z.B. von deinem Rechner aus):
```bash
scp -r projekt-hub/ projekthub@DEINE_SERVER_IP:~/projekt-hub
```
---
## 3. Python-Umgebung einrichten
```bash
cd ~/projekt-hub
python3 -m venv .venv
source .venv/bin/activate
pip install --upgrade pip
pip install -r requirements.txt
```
`gunicorn` und der Redis-Client sind bereits in `requirements.txt` enthalten —
kein separates Installieren nötig. Der **Redis-Server** (für Rate-Limiting bei
mehreren Workern, siehe Abschnitt 8a) ist ein Systemdienst und wird separat
installiert:
```bash
sudo apt install -y redis-server
sudo systemctl enable --now redis-server
```
---
## 4. Konfigurationsdatei `.env` anlegen
```bash
cp .env.example .env
nano .env
```
Wichtige Werte für die Produktion:
```ini
FLASK_ENV=production
SECRET_KEY=<langer-zufaelliger-string>
# Relativer sqlite-Name wird automatisch in den instance/-Ordner aufgelöst
# (-> instance/hub.sqlite). Keinen instance/-Pfad voranstellen.
DATABASE_URL=sqlite:///hub.sqlite
# Login
ALLOWED_EMAILS=deine@adresse.com
# Rate-Limiting über gemeinsames Backend (mehrere Worker), siehe Abschnitt 8a
RATELIMIT_STORAGE_URI=redis://localhost:6379
# Mail (siehe Abschnitt 7)
MAIL_SERVER=127.0.0.1
MAIL_PORT=1025
MAIL_USERNAME=deine@adresse.com
MAIL_PASSWORD=<bridge-oder-relay-passwort>
MAIL_USE_TLS=true
# Domain (sobald bekannt, siehe Abschnitt 9)
SERVER_NAME=
SESSION_COOKIE_SECURE=true
```
Einen zufälligen `SECRET_KEY` generieren:
```bash
python3 -c "import secrets; print(secrets.token_hex(32))"
```
`.env` darf **niemals** ins Git-Repo committen — sicherstellen, dass `.gitignore`
`.env` enthält.
---
## 5. Datenbank initialisieren
```bash
mkdir -p instance
flask db upgrade
```
Prüfen, dass `instance/hub.sqlite` existiert:
```bash
ls -la instance/
```
---
## 6. Testlauf mit dem Flask-Dev-Server (nur zur Kontrolle)
```bash
flask run --host=0.0.0.0 --port=5000
```
Im Browser `http://DEINE_SERVER_IP:5000` aufrufen — die Login-Seite sollte
erscheinen. Danach mit `Strg+C` wieder stoppen; der Dev-Server ist **nicht** für
Dauerbetrieb gedacht.
---
## 7. E-Mail-Versand über Proton einrichten
### Option A: Proton Mail Bridge (Docker)
Die Bridge initialisiert beim Start lokale IMAP/SMTP-Server, über die die App
Mails verschicken kann. Auf dem VPS läuft sie containerisiert:
```bash
sudo apt install -y docker.io
sudo systemctl enable --now docker
docker volume create proton-bridge
docker run -d --name proton-bridge \
-v proton-bridge:/root \
-p 127.0.0.1:1025:25 \
-p 127.0.0.1:1143:143 \
spameier/proton-bridge
```
Danach einmalig in den Container einloggen und die Bridge mit den Proton-Zugangsdaten
verbinden:
```bash
docker exec -it proton-bridge bridge --cli
# im Bridge-CLI: "login" eingeben und den Anweisungen folgen
```
Die Bridge zeigt danach lokale SMTP-Zugangsdaten (Benutzername + generiertes
Passwort) an — diese in die `.env` unter `MAIL_USERNAME` / `MAIL_PASSWORD`
eintragen, `MAIL_SERVER=127.0.0.1`, `MAIL_PORT=1025`.
**Bekannte Stolpersteine:** Proton Bridge nutzt ein selbstsigniertes Zertifikat und initiiert den TLS-Handshake selbst, was bei direkter Relay-Einbindung zu Problemen führen kann. Falls der Mailversand aus Flask nicht klappt, hilft es,
Postfix als lokalen Zwischen-Relay zu betreiben, der an die Bridge weiterleitet —
oder direkt auf Option B auszuweichen.
### Option B: EU-SMTP-Relay als Fallback
Falls Option A zu instabil ist: bei einem europäischen Transactional-Mail-Anbieter
(z.B. Infomaniak, Mailjet EU) ein kostenloses/günstiges Konto anlegen, SMTP-Zugangsdaten
erhalten und in `.env` eintragen — `MAIL_SERVER`, `MAIL_PORT`, `MAIL_USERNAME`,
`MAIL_PASSWORD`, `MAIL_USE_TLS` entsprechend anpassen. **Am Flask-Code ändert sich
nichts**, da die App generisch über `Flask-Mail`/SMTP konfiguriert ist.
### Testmail verschicken
```bash
flask shell
```
```python
from app.extensions import mail
from flask_mail import Message
msg = Message("Testmail Projekt-Hub", recipients=["deine@adresse.com"])
msg.body = "Funktioniert!"
mail.send(msg)
```
---
## 8. gunicorn + systemd einrichten
Die Gunicorn-Konfiguration liegt versioniert im Repo: **`gunicorn.conf.py`**
(Worker-Anzahl, Socket, Logging). Startbefehl testen (im `.venv`):
```bash
gunicorn -c gunicorn.conf.py wsgi:app
```
Nginx an Gunicorn über einen Unix-Socket statt TCP-Port anzubinden reduziert den TCP-Overhead und ist die bevorzugte Variante, wenn beide auf demselben Host laufen. Mit `Strg+C` stoppen, danach den systemd-Service einrichten.
Die systemd-Unit liegt ebenfalls im Repo (**`deploy/projekt-hub.service`**) und
lädt die Gunicorn-Config per `-c`. Kopieren und ggf. Pfade/Nutzer anpassen:
```bash
sudo cp deploy/projekt-hub.service /etc/systemd/system/projekt-hub.service
sudo nano /etc/systemd/system/projekt-hub.service # Pfade/Nutzer prüfen
```
Die Worker-Anzahl steuert `gunicorn.conf.py` (Default: (2 × CPU-Kerne) + 1,
per env `GUNICORN_WORKERS` überschreibbar).
Service aktivieren und starten:
```bash
sudo systemctl daemon-reload
sudo systemctl enable projekt-hub
sudo systemctl start projekt-hub
sudo systemctl status projekt-hub
```
---
## 8a. Redis für Rate-Limiting
Flask-Limiter (Login-Code-Anfragen) braucht bei **mehreren** gunicorn-Workern ein
gemeinsames Speicher-Backend — sonst zählt jeder Worker eigene Limits und das
Limit greift faktisch nicht. Redis wurde in Abschnitt 3 installiert; in der `.env`
verweist `RATELIMIT_STORAGE_URI` darauf:
```ini
RATELIMIT_STORAGE_URI=redis://localhost:6379
```
Damit Redis vor der App startet, in der systemd-Unit (auskommentierte Zeilen
aktivieren):
```ini
After=network.target redis-server.service
Wants=redis-server.service
```
Schnelltest, dass Redis läuft: `redis-cli ping``PONG`. Wer bewusst nur **einen**
Worker fährt, kann auch bei `memory://` bleiben.
Logs ansehen:
```bash
sudo journalctl -u projekt-hub -f
```
---
## 9. nginx als Reverse Proxy
Die nginx-Vorlage liegt im Repo: **`deploy/nginx/projekt-hub.conf`** (Socket-Proxy
+ Static-Auslieferung). Kopieren und `server_name` (zunächst Server-IP) anpassen:
```bash
sudo cp deploy/nginx/projekt-hub.conf /etc/nginx/sites-available/projekt-hub
sudo nano /etc/nginx/sites-available/projekt-hub # server_name / Pfade prüfen
```
Aktivieren:
```bash
sudo ln -s /etc/nginx/sites-available/projekt-hub /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
```
Im Browser `http://DEINE_SERVER_IP` aufrufen — die Login-Seite sollte jetzt über
nginx ausgeliefert werden.
---
## 10. Domain & HTTPS (sobald die Domain feststeht)
1. DNS A-Record der gewählten Domain/Subdomain auf die Server-IP zeigen lassen
2. In der nginx-Config `server_name` auf die Domain ändern, `nginx -t` +
`systemctl reload nginx`
3. Certbot installieren und Zertifikat holen:
```bash
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d deine-domain.de
```
Certbot passt die nginx-Config automatisch für HTTPS + Redirect von HTTP an.
4. In `.env`: `SERVER_NAME=deine-domain.de`, `SESSION_COOKIE_SECURE=true`,
`PREFERRED_URL_SCHEME=https` setzen, danach App neu starten:
```bash
sudo systemctl restart projekt-hub
```
---
## 11. Updates einspielen
Wenn neue Features (z.B. weitere Module) hinzukommen:
```bash
cd ~/projekt-hub
git pull # oder neue Dateien per scp hochladen
source .venv/bin/activate
pip install -r requirements.txt # falls neue Abhängigkeiten
flask db upgrade # falls neue Migrationen
sudo systemctl restart projekt-hub
```
---
## 12. Backup
Die gesamte App-Datenbank ist eine einzelne SQLite-Datei. Einfaches Backup per
Cronjob (täglich, Beispiel):
```bash
crontab -e
```
Zeile hinzufügen:
```
0 3 * * * cp /home/projekthub/projekt-hub/instance/hub.sqlite /home/projekthub/backups/hub-$(date +\%F).sqlite
```
Vorher den Ordner anlegen: `mkdir -p ~/backups`. Da SQLite auch während Schreib-
zugriffen kopiert werden kann, ist für ein konsistentes Backup `sqlite3 hub.sqlite
".backup '/pfad/hub-$(date +%F).sqlite'"` die sauberere Variante.
> Hinweis: Der frühere JSON-Export wurde durch den **PDF-Export** ersetzt (eine
> druckbare, projektbasierte To-Do-Liste, kein Datensicherungs-Format). Die
> Datensicherung erfolgt also ausschließlich über die SQLite-Datei wie oben.
---
## 13. Sicherheits-Checkliste (kurz)
- [ ] `.env` hat restriktive Dateirechte: `chmod 600 .env`
- [ ] `FLASK_ENV=production` gesetzt (kein Debug-Modus)
- [ ] Firewall (`ufw`) aktiv, nur Ports 22, 80, 443 offen:
```bash
sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw enable
```
- [ ] SSH-Login per Passwort deaktiviert, nur Key-Auth (falls noch nicht der Fall)
- [ ] Regelmäßige `apt upgrade` einplanen (z.B. `unattended-upgrades`)