#!/usr/bin/env bash # ===================================================================== # TeamVis — Update einer Self-Host-Instanz # ===================================================================== # Aktualisiert eine bestehende Instanz in einem Rutsch: # 1. Pre-Update-Backup der Datenbank (Modus 2) # 2. `git pull` im Bundle → neue Migrationen + Skripte # 3. NUR die NEU hinzugekommenen Migrationen einspielen (git als Ledger: # welche Dateien seit dem letzten Stand dazukamen) # 4. neues App-Image ziehen + Container neu starten # # Warum git als Ledger? Die Migrationen sind append-only. `git diff` zwischen # altem und neuem Stand liefert exakt die neuen Migrationsdateien — kein # separater DB-Tracker nötig, kein erneutes Ausführen alter Migrationen. # # Aufruf: bash deploy/selfhost/update.sh [instanz-verzeichnis] # (ohne Argument: das Instanz-Verzeichnis wird automatisch erkannt, wenn # es genau eines gibt.) set -euo pipefail c_bold=$'\033[1m'; c_grn=$'\033[32m'; c_red=$'\033[31m'; c_yel=$'\033[33m'; c_rst=$'\033[0m' say() { printf '%s\n' "$*"; } head() { printf '\n%s%s%s\n' "$c_bold" "$*" "$c_rst"; } ok() { printf '%s✓%s %s\n' "$c_grn" "$c_rst" "$*"; } warn() { printf '%s!%s %s\n' "$c_yel" "$c_rst" "$*"; } die() { printf '%s✗ %s%s\n' "$c_red" "$*" "$c_rst" >&2; exit 1; } BUNDLE="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" cd "$BUNDLE" [ -d .git ] || die "Kein git-Repo in $BUNDLE — Updates laufen über 'git pull'. Bitte das Bundle per 'git clone' beziehen." command -v docker >/dev/null || die "docker fehlt." # ── Instanz-Verzeichnis bestimmen (enthält docker-compose.yml + .env) ── INSTANCE="${1:-}" if [ -z "$INSTANCE" ]; then mapfile -t cands < <( for d in "$BUNDLE"/*/; do [ -f "${d}docker-compose.yml" ] && [ -f "${d}.env" ] && printf '%s\n' "${d%/}" done ) if [ "${#cands[@]}" -eq 1 ]; then INSTANCE="${cands[0]}" elif [ "${#cands[@]}" -eq 0 ]; then die "Kein Instanz-Verzeichnis gefunden. Bitte als Argument angeben: bash deploy/selfhost/update.sh " else say "Mehrere Instanzen gefunden:"; printf ' %s\n' "${cands[@]}" die "Bitte das gewünschte Verzeichnis als Argument angeben." fi fi [ -f "$INSTANCE/docker-compose.yml" ] || die "In $INSTANCE liegt keine docker-compose.yml." ok "Instanz: $INSTANCE" # Modus erkennen: eigener db-Service (Modus 2) oder externe Supabase (Modus 1). MODE2=0 grep -qE '^[[:space:]]+db:' "$INSTANCE/docker-compose.yml" && MODE2=1 POSTGRES_DB="$(grep -E '^POSTGRES_DB=' "$INSTANCE/.env" 2>/dev/null | cut -d= -f2- || true)" POSTGRES_DB="${POSTGRES_DB:-postgres}" PGPW="$(grep -E '^POSTGRES_PASSWORD=' "$INSTANCE/.env" 2>/dev/null | cut -d= -f2- || true)" dbsql() { ( cd "$INSTANCE" && docker compose exec -T -e PGPASSWORD="$PGPW" db psql -v ON_ERROR_STOP=1 -U supabase_admin -d "$POSTGRES_DB" "$@" ); } # ── 1. Pre-Update-Backup (nur Modus 2 — lokale DB) ──────────────────── if [ "$MODE2" = "1" ]; then head "1) Datenbank-Backup" ts="$(date +%Y%m%d-%H%M%S)" dump="$INSTANCE/backup-db-$ts.sql" if ( cd "$INSTANCE" && docker compose exec -T -e PGPASSWORD="$PGPW" db pg_dump -U supabase_admin "$POSTGRES_DB" ) > "$dump" 2>/dev/null; then ok "Backup: $dump ($(wc -l < "$dump") Zeilen)" else warn "Backup fehlgeschlagen — trotzdem fortfahren? (Strg-C zum Abbrechen)"; read -r _ || true fi else warn "Externe Supabase (Modus 1): Backup macht dein Supabase-Anbieter. Kein lokaler Dump." fi # ── 2. git pull → neue Migrationen/Skripte ──────────────────────────── head "2) Bundle aktualisieren (git pull)" OLD="$(git rev-parse HEAD)" git pull --ff-only origin main || die "git pull fehlgeschlagen (abweichende Branches?). Notfalls: git fetch && git reset --hard origin/main" NEW="$(git rev-parse HEAD)" if [ "$OLD" = "$NEW" ]; then ok "Schon aktuell (keine neuen Commits)." else ok "Aktualisiert: ${OLD:0:8} → ${NEW:0:8}" fi # ── 3. NUR neue Migrationen einspielen ──────────────────────────────── head "3) Neue Migrationen" mapfile -t NEWMIG < <(git diff --diff-filter=A --name-only "$OLD" "$NEW" -- 'supabase/migrations/*.sql' 2>/dev/null | sort) if [ "${#NEWMIG[@]}" -eq 0 ]; then ok "Keine neuen Migrationen." elif [ "$MODE2" = "1" ]; then for m in "${NEWMIG[@]}"; do say "→ $m" dbsql < "$BUNDLE/$m" done ok "${#NEWMIG[@]} Migration(en) eingespielt." else warn "Externe Supabase: bitte diese neuen Migrationen EINMAL in Supabase Studio (SQL-Editor) ausführen:" for m in "${NEWMIG[@]}"; do say " $BUNDLE/$m"; done read -r -p " Enter drücken, sobald sie eingespielt sind … " _ || true fi # ── 4. Neues Image ziehen + Container neu starten ───────────────────── head "4) App aktualisieren" ( cd "$INSTANCE" && docker compose pull && docker compose up -d ) ok "Container neu gestartet." head "Fertig 🎉" SITE_URL="$(grep -E '^SITE_URL=|^NEXT_PUBLIC_SITE_URL=' "$INSTANCE/.env" 2>/dev/null | head -1 | cut -d= -f2-)" [ -n "$SITE_URL" ] && say " $SITE_URL" say " Bei Problemen Logs prüfen: ( cd $INSTANCE && docker compose logs -f app )"