Artikel

25. Deployment

Allgemein

Das Deployment bringt das System von der lokalen Entwicklung auf einen Linux-Server.

Ziel:

Projekt liegt stabil auf dem Server.
.env ist produktiv gesetzt.
Playwright-Browser sind installiert.
Sessions, Screenshots und Logs haben passende Rechte.
Cron startet den Scheduler.
Systemd kann Smoke-Tests oder Worker ausführen.
Rollback ist vorbereitet.

Für dieses Handbuch wird Debian/Ubuntu angenommen.

25.1 Verzeichnis auf Server

Empfohlenes Zielverzeichnis:

/opt/social-publisher

Anlegen:

sudo mkdir -p /opt/social-publisher
sudo chown -R "$USER":"$USER" /opt/social-publisher

Projekt kopieren:

rsync -av --exclude ".venv" --exclude ".env" ./ /opt/social-publisher/

Auf dem Server:

cd /opt/social-publisher
python3 -m venv .venv
.venv/bin/python -m pip install --upgrade pip
.venv/bin/pip install -r requirements.txt

Erwartete Struktur:

/opt/social-publisher/
├── config.yaml
├── requirements.txt
├── data/
├── prompts/
├── src/
├── storage/
│   ├── sessions/
│   ├── screenshots/
│   ├── images/
│   └── logs/
└── tests/

25.2 .env produktiv setzen

Datei erstellen:

nano /opt/social-publisher/.env

Beispiel:

OPENAI_API_KEY=sk-...
APP_ENV=prod
APP_TIMEZONE=Europe/Berlin
DRY_RUN=true

Rechte setzen:

chmod 600 /opt/social-publisher/.env

Die .env wird nicht versioniert.

.gitignore:

.env
.venv/
storage/sessions/
storage/screenshots/
storage/logs/
__pycache__/
.pytest_cache/

Produktiv zunächst mit DRY_RUN=true starten. Live-Betrieb erst nach erfolgreichem Smoke-Test und geprüfter Session.

25.3 Playwright-Browser installieren

Playwright-Browser installieren:

cd /opt/social-publisher
sudo .venv/bin/python -m playwright install --with-deps chromium

Prüfen:

.venv/bin/python -m playwright install --list

Minimaler Test:

.venv/bin/python -m src.browser_session check linkedin --headed

Auf einem Headless-Server ohne Desktop:

.venv/bin/python -m src.browser_session check linkedin

Falls noch keine Session vorhanden ist, den manuellen Login auf einer Umgebung mit sichtbarem Browser durchführen und storage/sessions/linkedin.json anschließend sicher auf den Server kopieren.

25.4 Rechte setzen

Verzeichnisse anlegen:

sudo mkdir -p /opt/social-publisher/storage/sessions
sudo mkdir -p /opt/social-publisher/storage/screenshots
sudo mkdir -p /opt/social-publisher/storage/images
sudo mkdir -p /opt/social-publisher/storage/logs
sudo mkdir -p /opt/social-publisher/data

Eigentümer setzen:

sudo chown -R "$USER":"$USER" /opt/social-publisher

Rechte für sensible Sessiondaten:

chmod 700 /opt/social-publisher/storage/sessions
chmod 600 /opt/social-publisher/storage/sessions/*.json 2>/dev/null || true

Rechte für Logs und Screenshots:

chmod 755 /opt/social-publisher/storage
chmod 755 /opt/social-publisher/storage/screenshots
chmod 755 /opt/social-publisher/storage/logs

Wenn ein eigener Systemnutzer verwendet wird:

sudo useradd --system --home /opt/social-publisher --shell /usr/sbin/nologin socialpublisher
sudo chown -R socialpublisher:socialpublisher /opt/social-publisher

Dann müssen Cron und Systemd ebenfalls unter diesem Nutzer laufen.

25.5 Systemd-Unit

Für das MVP reicht Cron. Eine Systemd-Unit ist dennoch sinnvoll für manuelle Läufe, Smoke-Tests oder spätere Worker.

Service-Datei:

sudo nano /etc/systemd/system/social-publisher-smoke.service

Inhalt:

[Unit]
Description=Social Publisher Smoke Test
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot
WorkingDirectory=/opt/social-publisher
EnvironmentFile=/opt/social-publisher/.env
ExecStart=/opt/social-publisher/.venv/bin/python -m src.main logs events --limit 1
User=socialpublisher
Group=socialpublisher

[Install]
WantedBy=multi-user.target

Falls kein eigener Nutzer verwendet wird, User und Group entsprechend anpassen oder entfernen.

Systemd neu laden:

sudo systemctl daemon-reload

Test:

sudo systemctl start social-publisher-smoke.service
sudo systemctl status social-publisher-smoke.service

Logs:

journalctl -u social-publisher-smoke.service -n 100 --no-pager

Optionale Scheduler-Unit:

sudo nano /etc/systemd/system/social-publisher-scheduler.service

[Unit]
Description=Social Publisher Scheduler
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot
WorkingDirectory=/opt/social-publisher
EnvironmentFile=/opt/social-publisher/.env
ExecStart=/opt/social-publisher/.venv/bin/python -m src.scheduler run
User=socialpublisher
Group=socialpublisher

Live-Variante erst später:

ExecStart=/opt/social-publisher/.venv/bin/python -m src.scheduler run --live

25.6 Cron-Konfiguration

Crontab für den Systemnutzer öffnen:

sudo crontab -u socialpublisher -e

Dry-Run-Betrieb:

SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

0 8 * * 1-5 cd /opt/social-publisher && .venv/bin/python -m src.generate_posts >> storage/logs/cron.log 2>&1
10 8 * * 1-5 cd /opt/social-publisher && .venv/bin/python -m src.review_queue auto-approve >> storage/logs/cron.log 2>&1
*/15 8-18 * * 1-5 flock -n /tmp/social-publisher.lock bash -lc 'cd /opt/social-publisher && .venv/bin/python -m src.scheduler run' >> /opt/social-publisher/storage/logs/cron.log 2>&1
0 18 * * 1-5 cd /opt/social-publisher && .venv/bin/python -m src.report daily --date $(date +\%F) >> storage/logs/reports.log 2>&1

Live-Betrieb:

SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

0 8 * * 1-5 cd /opt/social-publisher && .venv/bin/python -m src.generate_posts >> storage/logs/cron.log 2>&1
10 8 * * 1-5 cd /opt/social-publisher && .venv/bin/python -m src.review_queue auto-approve >> storage/logs/cron.log 2>&1
*/15 8-18 * * 1-5 flock -n /tmp/social-publisher.lock bash -lc 'cd /opt/social-publisher && .venv/bin/python -m src.scheduler run --live' >> /opt/social-publisher/storage/logs/cron.log 2>&1
0 18 * * 1-5 cd /opt/social-publisher && .venv/bin/python -m src.report daily --date $(date +\%F) >> storage/logs/reports.log 2>&1

Cron prüfen:

sudo crontab -u socialpublisher -l

Log prüfen:

tail -n 100 /opt/social-publisher/storage/logs/cron.log

25.7 Smoke-Test produktiv

Reihenfolge:

cd /opt/social-publisher

Python-Import prüfen:

.venv/bin/python -m src.main logs events --limit 1

Konfiguration prüfen:

.venv/bin/python -m src.scheduler run

OpenAI-Test:

.venv/bin/python -m src.main generate --platform linkedin --type workshop

Queue prüfen:

.venv/bin/python -m src.main queue list --status draft

Freigabe im Dry-Run:

.venv/bin/python -m src.main queue auto-approve
.venv/bin/python -m src.main publish-next

Screenshot prüfen:

ls -lah storage/screenshots/linkedin

Logs prüfen:

tail -n 50 storage/logs/events.jsonl
tail -n 50 storage/logs/errors.jsonl

Live-Test mit einem einzelnen freigegebenen Beitrag:

.venv/bin/python -m src.main publish-next --live

Direkt danach prüfen:

.venv/bin/python -m src.main queue status
tail -n 50 storage/logs/events.jsonl
tail -n 50 storage/logs/errors.jsonl

25.8 Rollback

Vor jeder Änderung Backup anlegen:

cd /opt
sudo tar -czf social-publisher-backup-$(date +%Y%m%d-%H%M%S).tar.gz social-publisher

Nur Code zurückrollen, Daten behalten:

cd /opt/social-publisher
git status
git log --oneline -n 5

Dann gezielt auf eine bekannte Version wechseln. Keine destruktiven Git-Befehle ohne vorherige Sicherung.

Sicherer Rollback über neues Verzeichnis:

sudo mkdir -p /opt/social-publisher-rollback
sudo chown -R "$USER":"$USER" /opt/social-publisher-rollback

Backup entpacken:

cd /opt
sudo tar -xzf social-publisher-backup-YYYYMMDD-HHMMSS.tar.gz -C /opt/social-publisher-rollback --strip-components=1

Cron temporär deaktivieren:

sudo crontab -u socialpublisher -e

Alle aktiven Social-Publisher-Zeilen auskommentieren.

Smoke-Test im Rollback-Verzeichnis:

cd /opt/social-publisher-rollback
.venv/bin/python -m src.main queue status
.venv/bin/python -m src.scheduler run

Umschalten erst nach Prüfung:

sudo mv /opt/social-publisher /opt/social-publisher-broken-$(date +%Y%m%d-%H%M%S)
sudo mv /opt/social-publisher-rollback /opt/social-publisher
sudo chown -R socialpublisher:socialpublisher /opt/social-publisher

Cron wieder aktivieren:

sudo crontab -u socialpublisher -e

Ergebnis dieses Kapitels

Das Deployment umfasst jetzt:

Serververzeichnis unter /opt
produktive .env
installierte Playwright-Browser
gesetzte Rechte für Sessions, Logs und Screenshots
optionale Systemd-Units
Cron-Automatisierung mit Lockfile
produktiver Smoke-Test
Rollback über Backup-Verzeichnis

Damit ist das System serverfähig, bleibt aber kontrolliert: Dry-Run vor Live-Betrieb, Logs vor Vertrauen, Rollback vor Änderungen.

0 Kommentare