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