diff --git a/scripts/deploy-db.sh b/scripts/deploy-db.sh index a7db086..a4ea147 100755 --- a/scripts/deploy-db.sh +++ b/scripts/deploy-db.sh @@ -1,78 +1,148 @@ #!/bin/bash # Deploy DB zum VServer -# Usage: ./scripts/deploy-db.sh [--dry-run] -set -e +# Usage: ./scripts/deploy-db.sh [--dry-run] [--migrate-only] [--skip-migrate] +# +# Modes: +# (default) Upload DB + run migrations + restart container +# --dry-run Nur anzeigen, nichts ändern +# --migrate-only Nur Migrationen auf Remote-DB ausführen (kein Upload) +# --skip-migrate DB hochladen, aber keine Migrationen +# +# Migrationen: FTS5, Strang, Fristen -LOCAL_DB="data/tracker_remote.db" +set -euo pipefail + +LOCAL_DB="data/tracker.db" REMOTE_DB="/opt/antragstracker/data/tracker.db" REMOTE_HOST="vserver" +CONTAINER="antragstracker-hagen" DRY_RUN=false +MIGRATE_ONLY=false +SKIP_MIGRATE=false -if [ "$1" = "--dry-run" ]; then - DRY_RUN=true - echo "🔍 DRY RUN — keine Änderungen" - echo "" -fi +for arg in "$@"; do + case $arg in + --dry-run) DRY_RUN=true ;; + --migrate-only) MIGRATE_ONLY=true ;; + --skip-migrate) SKIP_MIGRATE=true ;; + -h|--help) + echo "Usage: $0 [--dry-run] [--migrate-only] [--skip-migrate]" + exit 0 ;; + *) echo "Unknown arg: $arg"; exit 1 ;; + esac +done cd "$(dirname "$0")/.." echo "=== DB Deploy ===" -echo "Lokale DB: $(du -sh $LOCAL_DB | cut -f1)" -# 0. Schema-Validierung -echo "0. Schema prüfen..." -EXPECTED_TABLES="vorlagen beratungen ketten ketten_glieder ki_bewertungen parteien antragsteller gremien orte referenzen" -MISSING="" -for tbl in $EXPECTED_TABLES; do - EXISTS=$(sqlite3 "$LOCAL_DB" "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='$tbl'" 2>/dev/null) - if [ "$EXISTS" != "1" ]; then - MISSING="$MISSING $tbl" +# --- Schema-Validierung (nur bei Upload) --- +if [ "$MIGRATE_ONLY" = false ]; then + # Fallback zu tracker_remote.db wenn tracker.db nicht existiert + if [ ! -f "$LOCAL_DB" ]; then + if [ -f "data/tracker_remote.db" ]; then + LOCAL_DB="data/tracker_remote.db" + echo " → Nutze data/tracker_remote.db" + else + echo "❌ Keine lokale DB gefunden (data/tracker.db oder data/tracker_remote.db)" + exit 1 + fi fi -done -if [ -n "$MISSING" ]; then - echo "❌ FEHLER: Fehlende Tabellen:$MISSING" - exit 1 -fi -echo " ✓ Alle ${#EXPECTED_TABLES[@]} Tabellen vorhanden" + echo "Lokale DB: $(du -sh $LOCAL_DB | cut -f1)" -# Stats -echo "" -echo " Vorlagen: $(sqlite3 "$LOCAL_DB" 'SELECT COUNT(*) FROM vorlagen')" -echo " Ketten: $(sqlite3 "$LOCAL_DB" 'SELECT COUNT(*) FROM ketten')" -echo " KI-Bew.: $(sqlite3 "$LOCAL_DB" 'SELECT COUNT(*) FROM ki_bewertungen')" -echo " Matches: $(sqlite3 "$LOCAL_DB" "SELECT COUNT(*) FROM ki_bewertungen WHERE typ='umsetzung_match'")" -echo " Orte: $(sqlite3 "$LOCAL_DB" 'SELECT COUNT(*) FROM orte WHERE lat IS NOT NULL')" -echo "" + echo "Schema prüfen..." + EXPECTED_TABLES="vorlagen beratungen ketten ketten_glieder ki_bewertungen parteien antragsteller gremien orte referenzen" + MISSING="" + for tbl in $EXPECTED_TABLES; do + EXISTS=$(sqlite3 "$LOCAL_DB" "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='$tbl'" 2>/dev/null) + if [ "$EXISTS" != "1" ]; then + MISSING="$MISSING $tbl" + fi + done -# Flush WAL journal -echo " Flushing WAL..." -sqlite3 "$LOCAL_DB" "PRAGMA wal_checkpoint(TRUNCATE)" 2>/dev/null -echo "" + if [ -n "$MISSING" ]; then + echo "❌ Fehlende Tabellen:$MISSING" + exit 1 + fi + echo " ✓ Alle Tabellen vorhanden" -if $DRY_RUN; then - echo "🔍 Würde hochladen: $(du -sh $LOCAL_DB | cut -f1) nach $REMOTE_HOST:$REMOTE_DB" - echo "🔍 Prod-DB Größe: $(ssh $REMOTE_HOST "du -sh $REMOTE_DB 2>/dev/null | cut -f1" || echo "N/A")" + # Stats echo "" - echo "DRY RUN fertig. Ohne --dry-run ausführen zum Deployen." + echo " Vorlagen: $(sqlite3 "$LOCAL_DB" 'SELECT COUNT(*) FROM vorlagen')" + echo " Ketten: $(sqlite3 "$LOCAL_DB" 'SELECT COUNT(*) FROM ketten')" + echo " KI-Bew.: $(sqlite3 "$LOCAL_DB" 'SELECT COUNT(*) FROM ki_bewertungen')" + echo " Orte: $(sqlite3 "$LOCAL_DB" 'SELECT COUNT(*) FROM orte WHERE lat IS NOT NULL')" + echo "" + + # Flush WAL journal + echo " WAL flushen..." + sqlite3 "$LOCAL_DB" "PRAGMA wal_checkpoint(TRUNCATE)" 2>/dev/null +fi + +if [ "$DRY_RUN" = true ]; then + echo "" + echo "🔍 DRY RUN" + if [ "$MIGRATE_ONLY" = false ]; then + echo " Würde hochladen: $(du -sh $LOCAL_DB | cut -f1) nach $REMOTE_HOST:$REMOTE_DB" + echo " Prod-DB: $(ssh $REMOTE_HOST "du -sh $REMOTE_DB 2>/dev/null | cut -f1" || echo "N/A")" + fi + if [ "$SKIP_MIGRATE" = false ]; then + echo " Migrationen: FTS5, Strang, Fristen" + fi + echo "" + echo "DRY RUN fertig." exit 0 fi -# 1. Backup auf VServer -BACKUP_NAME="${REMOTE_DB}.bak-$(date +%Y%m%d-%H%M)" -echo "1. Backup Prod-DB..." -ssh $REMOTE_HOST "cp $REMOTE_DB $BACKUP_NAME" -echo " → $BACKUP_NAME" +# --- Upload --- +if [ "$MIGRATE_ONLY" = false ]; then + # Backup auf VServer + BACKUP_NAME="${REMOTE_DB}.bak-$(date +%Y%m%d-%H%M)" + echo "1. Backup Prod-DB..." + ssh $REMOTE_HOST "cp $REMOTE_DB $BACKUP_NAME 2>/dev/null || echo ' (keine bestehende DB)'" + echo " → $BACKUP_NAME" -# 2. Upload -echo "2. Upload..." -scp "$LOCAL_DB" "$REMOTE_HOST:$REMOTE_DB" + # Container stoppen für sicheren DB-Write + echo "2. Container stoppen..." + ssh $REMOTE_HOST "cd /opt/antragstracker && docker compose stop antragstracker" 2>&1 | grep -v "level=warning" || true -# 3. Restart -echo "3. Restart Backend..." -ssh $REMOTE_HOST 'cd /opt/antragstracker && docker compose restart antragstracker' 2>&1 | grep -v "level=warning" + echo "3. Upload..." + scp "$LOCAL_DB" "$REMOTE_HOST:$REMOTE_DB" + echo " ✓ Upload fertig" +fi + +# --- Migrationen --- +if [ "$SKIP_MIGRATE" = false ]; then + echo "4. Migrationen ausführen..." + + # Migrationen im Docker-Container auf dem VServer + ssh $REMOTE_HOST "cd /opt/antragstracker && docker compose run --rm --no-deps antragstracker python scripts/migrate_fts5.py /app/data/tracker.db" 2>&1 | grep -v "level=warning" || true + echo " ✓ FTS5" + + ssh $REMOTE_HOST "cd /opt/antragstracker && docker compose run --rm --no-deps antragstracker python scripts/migrate_strang.py" 2>&1 | grep -v "level=warning" || true + echo " ✓ Strang" + + ssh $REMOTE_HOST "cd /opt/antragstracker && docker compose run --rm --no-deps antragstracker python scripts/migrate_fristen.py" 2>&1 | grep -v "level=warning" || true + echo " ✓ Fristen" +fi + +# --- Container starten --- +echo "5. Container starten..." +ssh $REMOTE_HOST "cd /opt/antragstracker && docker compose up -d" 2>&1 | grep -v "level=warning" || true + +# --- Health-Check --- +sleep 5 +HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" "https://antraege.toppyr.de/api/health" 2>/dev/null || echo "000") +if [ "$HTTP_STATUS" = "200" ]; then + echo " ✓ Health-Check OK" +else + echo " ⚠️ Health-Check: HTTP $HTTP_STATUS" +fi echo "" -echo "✅ Deploy fertig" -echo "Backup: $BACKUP_NAME" -echo "Rollback: ssh $REMOTE_HOST 'cp $BACKUP_NAME $REMOTE_DB && cd /opt/antragstracker && docker compose restart antragstracker'" +echo "✅ DB-Deploy fertig" +if [ "$MIGRATE_ONLY" = false ]; then + echo "Backup: $BACKUP_NAME" + echo "Rollback: ssh $REMOTE_HOST 'cp $BACKUP_NAME $REMOTE_DB && cd /opt/antragstracker && docker compose restart antragstracker'" +fi diff --git a/scripts/deploy.sh b/scripts/deploy.sh new file mode 100755 index 0000000..e889b64 --- /dev/null +++ b/scripts/deploy.sh @@ -0,0 +1,113 @@ +#!/bin/bash +# Deploy Antragstracker auf VServer +# Usage: ./scripts/deploy.sh [--build-only] [--full] [--skip-frontend] +# +# Modes: +# (default) Build + deploy code, restart container +# --build-only Build Docker image on server, don't restart +# --full Full deploy: frontend build + code sync + docker rebuild + restart +# --skip-frontend Skip local frontend build (use existing build/) +# +# Voraussetzung: ssh vserver funktioniert + +set -euo pipefail + +VSERVER="vserver" +REMOTE_DIR="/opt/antragstracker" +CONTAINER="antragstracker-hagen" +PROJECT_DIR="$(cd "$(dirname "$0")/.." && pwd)" + +# Parse args +BUILD_ONLY=false +SKIP_FRONTEND=false + +for arg in "$@"; do + case $arg in + --build-only) BUILD_ONLY=true ;; + --full) ;; # default behavior + --skip-frontend) SKIP_FRONTEND=true ;; + -h|--help) + echo "Usage: $0 [--build-only] [--full] [--skip-frontend]" + exit 0 ;; + *) echo "Unknown arg: $arg"; exit 1 ;; + esac +done + +cd "$PROJECT_DIR" + +# 1. Frontend bauen (lokal) +if [ "$SKIP_FRONTEND" = false ]; then + echo "🔨 Frontend bauen..." + cd frontend && npm run build && cd .. + echo " ✓ Frontend build fertig ($(du -sh frontend/build | cut -f1))" +else + echo "⏭️ Frontend-Build übersprungen" +fi + +# 2. Code zum VServer synchronisieren +echo "📦 Code synchronisieren..." +tar czf /tmp/antragstracker-deploy.tar.gz \ + --exclude='.git' \ + --exclude='.venv' \ + --exclude='venv' \ + --exclude='node_modules' \ + --exclude='frontend/.svelte-kit' \ + --exclude='data' \ + --exclude='*.log' \ + --exclude='*.pyc' \ + --exclude='__pycache__' \ + --exclude='.DS_Store' \ + --exclude='.pytest_cache' \ + --exclude='tracker.db*' \ + --exclude='antraege.db' \ + --exclude='.github' \ + --exclude='logs' \ + -C "$(dirname "$PROJECT_DIR")" "$(basename "$PROJECT_DIR")" + +scp /tmp/antragstracker-deploy.tar.gz "$VSERVER:/tmp/" +ssh "$VSERVER" "cd /opt && tar xzf /tmp/antragstracker-deploy.tar.gz && rm /tmp/antragstracker-deploy.tar.gz" +rm -f /tmp/antragstracker-deploy.tar.gz +echo " ✓ Code synchronisiert" + +# 3. Docker Image bauen (auf VServer) +echo "🐳 Docker Image bauen auf VServer..." +ssh "$VSERVER" "cd $REMOTE_DIR && docker compose build --no-cache" +echo " ✓ Image gebaut" + +if [ "$BUILD_ONLY" = true ]; then + echo "⏹️ Build-only Mode — Container wird nicht neu gestartet" + exit 0 +fi + +# 4. Container neu starten +echo "🔄 Container neu starten..." +ssh "$VSERVER" "cd $REMOTE_DIR && docker compose up -d" +echo " ✓ Container gestartet" + +# 5. Warten + Health-Check +echo "⏳ Warte auf Startup (5s)..." +sleep 5 + +echo "🏥 Health-Check..." +HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" "https://antraege.toppyr.de/api/health" 2>/dev/null || echo "000") + +if [ "$HTTP_STATUS" = "200" ]; then + echo " ✓ Health-Check OK (HTTP $HTTP_STATUS)" +elif [ "$HTTP_STATUS" = "000" ]; then + sleep 5 + HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" "https://antraege.toppyr.de/api/health" 2>/dev/null || echo "000") + if [ "$HTTP_STATUS" = "200" ]; then + echo " ✓ Health-Check OK (HTTP $HTTP_STATUS, 2. Versuch)" + else + echo " ⚠️ Health-Check fehlgeschlagen (HTTP $HTTP_STATUS)" + echo " Logs prüfen: ssh $VSERVER 'docker logs --tail 20 $CONTAINER'" + fi +else + echo " ⚠️ Health-Check: HTTP $HTTP_STATUS" + echo " Logs prüfen: ssh $VSERVER 'docker logs --tail 20 $CONTAINER'" +fi + +echo "" +echo "✅ Deploy fertig!" +echo " URL: https://antraege.toppyr.de" +echo " Logs: ssh $VSERVER 'docker logs --tail 50 $CONTAINER'"