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

9.9 KiB
Raw Blame History

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

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):

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):

cd ~
git clone <DEIN_REPO_URL> projekt-hub
cd projekt-hub

Variante B — Dateien direkt hochladen (z.B. von deinem Rechner aus):

scp -r projekt-hub/ projekthub@DEINE_SERVER_IP:~/projekt-hub

3. Python-Umgebung einrichten

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:

sudo apt install -y redis-server
sudo systemctl enable --now redis-server

4. Konfigurationsdatei .env anlegen

cp .env.example .env
nano .env

Wichtige Werte für die Produktion:

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:

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

mkdir -p instance
flask db upgrade

Prüfen, dass instance/hub.sqlite existiert:

ls -la instance/

6. Testlauf mit dem Flask-Dev-Server (nur zur Kontrolle)

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:

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:

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

flask shell
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):

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:

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:

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:

RATELIMIT_STORAGE_URI=redis://localhost:6379

Damit Redis vor der App startet, in der systemd-Unit (auskommentierte Zeilen aktivieren):

After=network.target redis-server.service
Wants=redis-server.service

Schnelltest, dass Redis läuft: redis-cli pingPONG. Wer bewusst nur einen Worker fährt, kann auch bei memory:// bleiben.

Logs ansehen:

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:
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:

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:
    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:
    sudo systemctl restart projekt-hub
    

11. Updates einspielen

Wenn neue Features (z.B. weitere Module) hinzukommen:

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):

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:
    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)