gwoe-antragspruefer/scripts/major-release-cut.sh
Dotty Dotter d7e3c8a944 scripts: Standard-Deploy ueber git-pull (release/2.0) + major-release-cut.sh
Nach dem 1.x → 2.0-Cut auf prod (siehe v2.0.0-Tag) laeuft prod als sauberer
git-checkout. Tar-Upload-Pfad ist obsolet.

- scripts/deploy.sh: Branch-Guard release/2.0, Pre-flight-Checks (clean +
  pushed), Pre-Deploy-DB-Backup, Uptime-Kuma-Wartungsmodus, /health-Check
  mit Version-Anzeige nach Deploy
- scripts/major-release-cut.sh: dokumentierter Workflow fuer den naechsten
  Major-Cut (z.B. 2.0 → 3.0). Inklusive Bundle-Fallback bei
  Gitea-Korruption (war beim 2.0-Cut gebraucht), DB-Wipe-Liste mit
  Erhalt der Vote-Daten, Pfad-Switchover und Smoke-Tests
2026-05-10 11:43:33 +02:00

161 lines
5.9 KiB
Bash
Executable File
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/bin/bash
# Major-Release-Cut auf Prod (z.B. 1.x → 2.0).
#
# Sonderfall — laeuft EINMAL pro Major. Fuer normale Updates auf demselben
# Major-Branch: scripts/deploy.sh.
#
# Was hier passiert:
# 1. Pre-flight: Tag + release/X.0-Branch existieren? Lokales Repo clean?
# 2. DB-Dumps prod + dev frisch ziehen
# 3. Manuelles Restic-Backup
# 4. Frischer git-clone auf vserver in Parallel-Pfad
# 5. Praeparierte data/-Dir vorbereiten:
# - dev's data als Basis (enthaelt aktuelles Schema, embeddings, votes)
# - assessments/jobs/news/PM/monitoring leeren (Frischstart)
# - Behalten: plenum_vote_results, abgeordnetenwatch_votes/polls
# 6. backups/, reports/, .env aus altem Pfad kopieren
# 7. Container down → Pfad-Switch → Container up
# 8. Smoke-Tests
#
# Usage:
# ./scripts/major-release-cut.sh v2.0.0 release/2.0
#
# Voraussetzungen:
# - Tag + Branch sind bereits gepusht
# - Lokal $RELEASE_BRANCH ist ausgecheckt und aktuell
# - SSH-Zugang zu vserver
#
# Falls Gitea-seitig "early EOF / repository corruption":
# git bundle create /tmp/release.bundle <release-branch>
# scp /tmp/release.bundle vserver:/tmp/
# ssh vserver 'sudo git clone -b <branch> /tmp/release.bundle <neuer-pfad>'
set -euo pipefail
if [ $# -lt 2 ]; then
cat <<EOF
Usage: $0 <tag> <branch>
Beispiel: $0 v2.0.0 release/2.0
EOF
exit 1
fi
TAG="$1"
BRANCH="$2"
SERVER="vserver"
PROD_DIR="/opt/gwoe-antragspruefer"
DEV_DIR="/opt/gwoe-antragspruefer-dev"
NEW_DIR="/opt/gwoe-antragspruefer-${TAG#v}" # z.B. /opt/gwoe-antragspruefer-2.0.0
ARCHIVE_DIR="/opt/gwoe-antragspruefer-$(date +%Y%m%d-%H%M%S)-archive"
PUBLIC_URL="https://gwoe.toppyr.de"
REPO_URL="https://repo.toppyr.de/tobias/gwoe-antragspruefer.git"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
cd "$PROJECT_DIR"
echo "=== Major-Release-Cut auf $PUBLIC_URL ==="
echo "Tag: $TAG"
echo "Branch: $BRANCH"
echo "Neu: $NEW_DIR"
echo "Alt: $ARCHIVE_DIR (Archiv)"
echo
# Pre-flight
if [ -n "$(git status --porcelain)" ]; then
echo "✗ Lokales Repo nicht clean. Erst commiten/stashen."
exit 1
fi
if ! git rev-parse "$TAG" >/dev/null 2>&1; then
echo "✗ Tag $TAG existiert lokal nicht."
exit 1
fi
read -p "⚠ Major-Cut auf prod jetzt durchfuehren? [yes/N] " ans
[ "$ans" = "yes" ] || exit 1
# 1. Frische DB-Dumps prod + dev
echo
echo "1⃣ Frische DB-Dumps..."
ssh "$SERVER" "$PROD_DIR/scripts/backup-db.sh && $DEV_DIR/scripts/backup-db.sh" 2>&1 | tail -2
# 2. Manuelles Restic-Backup
echo
echo "2⃣ Restic-Backup..."
ssh "$SERVER" "sudo /home/dotty/backup-scripts/vserver-backup.sh" 2>&1 | tail -3
# 3. Frischer Clone auf vserver (mit Bundle-Fallback bei Gitea-Korruption)
echo
echo "3⃣ Klone $BRANCH nach $NEW_DIR..."
if ! ssh "$SERVER" "sudo git clone -b '$BRANCH' --single-branch '$REPO_URL' '$NEW_DIR' 2>&1" | tail -3 | grep -q "done"; then
echo " Gitea-Clone fehlgeschlagen — versuche Bundle-Fallback..."
git bundle create "/tmp/${TAG}.bundle" "$BRANCH" 2>&1 | tail -1
scp "/tmp/${TAG}.bundle" "$SERVER:/tmp/"
ssh "$SERVER" "sudo rm -rf '$NEW_DIR' 2>/dev/null; sudo git clone -b '$BRANCH' '/tmp/${TAG}.bundle' '$NEW_DIR' 2>&1 | tail -3"
ssh "$SERVER" "cd '$NEW_DIR' && sudo git remote set-url origin '$REPO_URL'"
fi
ssh "$SERVER" "sudo chown -R dotty:dotty '$NEW_DIR' && cd '$NEW_DIR' && git log --oneline -1"
# 4. Praeparierte data/-Dir: dev's Stand als Basis, ausgewaehlte Tabellen leeren
echo
echo "4⃣ Bereite leere data/-Dir vor (basiert auf dev, assessments leer, votes behalten)..."
ssh "$SERVER" "cp -a '$DEV_DIR/data/.' '$NEW_DIR/data/'"
ssh "$SERVER" "python3 - <<'PY'
import sqlite3
db = sqlite3.connect('$NEW_DIR/data/gwoe-antraege.db')
TO_CLEAR = [
'assessments', 'assessment_versions',
'presse_drafts', 'news_articles',
'auto_rate_runs', 'jobs',
'monitoring_scans', 'monitoring_daily_summary',
'auth_bypass_uses', 'comments', 'merkliste',
'bookmarks', 'email_subscriptions', 'votes',
]
# Behalten: plenum_vote_results, abgeordnetenwatch_votes, abgeordnetenwatch_polls
for t in TO_CLEAR:
try: db.execute(f'DELETE FROM {t}')
except Exception: pass
try: db.execute('DELETE FROM sqlite_sequence')
except Exception: pass
db.commit()
db.execute('VACUUM')
print(' votes:', db.execute('SELECT COUNT(*) FROM plenum_vote_results').fetchone()[0])
print(' assessments:', db.execute('SELECT COUNT(*) FROM assessments').fetchone()[0])
db.close()
PY"
# 5. .env, backups/, reports/ aus altem Pfad
echo
echo "5⃣ Kopiere .env, backups/, reports/ aus altem Pfad..."
ssh "$SERVER" "cp '$PROD_DIR/.env' '$NEW_DIR/.env'"
ssh "$SERVER" "cp -a '$PROD_DIR/backups' '$NEW_DIR/backups'"
ssh "$SERVER" "cp -a '$PROD_DIR/reports' '$NEW_DIR/reports'"
# 6. Switchover
echo
echo "6⃣ Container down + Pfad-Switch..."
ssh "$SERVER" "cd '$PROD_DIR' && docker compose down" 2>&1 | tail -3
ssh "$SERVER" "sudo mv '$PROD_DIR' '$ARCHIVE_DIR' && sudo mv '$NEW_DIR' '$PROD_DIR' && sudo chown -R dotty:dotty '$PROD_DIR'"
ssh "$SERVER" "cd '$PROD_DIR' && docker compose up -d --build" 2>&1 | tail -5
# 7. Smoke-Tests
echo
echo "7⃣ Smoke-Tests..."
for i in $(seq 1 60); do
response=$(curl -sS --max-time 3 "$PUBLIC_URL/health" 2>/dev/null || echo "")
if echo "$response" | grep -q '"status":"ok"'; then
version=$(echo "$response" | python3 -c "import json,sys; print(json.load(sys.stdin).get('version','?'))" 2>/dev/null)
echo "✅ Health OK nach ${i}s — version $version"
break
fi
sleep 1
done
echo " Score-Histogram: $(curl -sk "$PUBLIC_URL/api/auswertungen/score-histogram" | python3 -c 'import json,sys; print(json.load(sys.stdin)["total"])')"
echo " Bundeslaender: $(curl -sk "$PUBLIC_URL/api/bundeslaender" | python3 -c 'import json,sys; print(len(json.load(sys.stdin)))')"
echo
echo "=== Cut abgeschlossen ==="
echo "Alt-Stand: $ARCHIVE_DIR (kann nach paar Tagen geloescht werden)"
echo "Neuer Workflow: scripts/deploy.sh (auf Branch $BRANCH)"