Add full Docker deployment: setup.sh, update.sh, healthcheck, TURN support
- setup.sh: interactive/non-interactive one-shot installer (build, DB healthcheck, migrate, seed, start), idempotent secret generation, NPM reverse-proxy network auto-detection and optional join, optional AUTO_UPDATE cron install. - update.sh: release-tag-gated updates only (never bare main), DB backup with retention before every update, lock file against concurrent runs, automatic code rollback on failed post-update healthcheck. - Dockerfile: multi-stage build, non-root user, built-in HEALTHCHECK against the new /api/health route, wholesale COPY so new source dirs (e.g. scripts/) never silently go missing at runtime. - docker-compose.yml: internal anouma-network (configurable), named volume for Postgres, app depends_on postgres healthy, no unnecessary published ports; docker-compose.override.yml.example documents joining an existing NPM network without ever touching NPM itself. - Fix host-detection: isHost was Boolean(user), wrongly granting host privileges to logged-in customers; now checks user.collection === "users". - Wire configurable STUN/TURN servers through to the WebRTC client (lib/meeting/iceServers.ts) so a TURN server can be added later via env vars only, no code changes. - DEPLOYMENT.md, updated README.md and .env.example documenting the whole flow: NPM integration, env vars, WebRTC, updates, backups, rollback. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
#!/usr/bin/env bash
|
||||
# ANOUMA — safe, release-based update.
|
||||
#
|
||||
# ./update.sh interactive update to the latest release tag
|
||||
# ./update.sh --auto used by the AUTO_UPDATE cron job — silently
|
||||
# does nothing if already on the latest release
|
||||
# ./update.sh vX.Y.Z update to a specific tag
|
||||
#
|
||||
# Never deletes volumes. Never force-deploys an untagged commit from main —
|
||||
# only real release tags (vX.Y.Z) are deployed. Backs up the database before
|
||||
# touching anything, and rolls the *code* back (not the database schema —
|
||||
# migrations should be forward-compatible, see DEPLOYMENT.md) if the
|
||||
# post-update healthcheck fails.
|
||||
set -euo pipefail
|
||||
cd "$(dirname "${BASH_SOURCE[0]}")"
|
||||
|
||||
COLOR_INFO="\033[36m"; COLOR_WARN="\033[33m"; COLOR_ERROR="\033[31m"; COLOR_OK="\033[32m"; COLOR_RESET="\033[0m"
|
||||
log_info() { printf "${COLOR_INFO}[INFO]${COLOR_RESET} %s\n" "$1"; }
|
||||
log_warn() { printf "${COLOR_WARN}[WARN]${COLOR_RESET} %s\n" "$1"; }
|
||||
log_error() { printf "${COLOR_ERROR}[ERROR]${COLOR_RESET} %s\n" "$1" >&2; }
|
||||
log_success() { printf "${COLOR_OK}[SUCCESS]${COLOR_RESET} %s\n" "$1"; }
|
||||
fail() { log_error "$1"; exit 1; }
|
||||
|
||||
command -v git >/dev/null 2>&1 || fail "Git wird für release-basierte Updates benötigt."
|
||||
command -v docker >/dev/null 2>&1 || fail "Docker wurde nicht gefunden."
|
||||
COMPOSE="docker compose"; docker compose version >/dev/null 2>&1 || COMPOSE="docker-compose"
|
||||
|
||||
AUTO_MODE=false
|
||||
TARGET_VERSION=""
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--auto) AUTO_MODE=true ;;
|
||||
v*) TARGET_VERSION="$arg" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Update lock — prevents two updates (e.g. a manual one and the cron job)
|
||||
# from running at the same time.
|
||||
# ---------------------------------------------------------------------------
|
||||
LOCK_FILE="$(pwd)/.update.lock"
|
||||
if [ -f "$LOCK_FILE" ] && kill -0 "$(cat "$LOCK_FILE")" 2>/dev/null; then
|
||||
log_warn "Update already running."
|
||||
exit 0
|
||||
fi
|
||||
echo $$ > "$LOCK_FILE"
|
||||
trap 'rm -f "$LOCK_FILE"' EXIT
|
||||
|
||||
[ -f .env ] || fail ".env nicht gefunden — bitte zuerst ./setup.sh ausführen."
|
||||
[ -d .git ] || fail "Kein Git-Repository — Updates funktionieren nur in einem Checkout von https://git.maro.run/maro/anouma.git."
|
||||
|
||||
if [ -n "$(git status --porcelain)" ]; then
|
||||
fail "Das Arbeitsverzeichnis hat uncommittete Änderungen — Update abgebrochen, um nichts zu überschreiben."
|
||||
fi
|
||||
|
||||
CURRENT_REF="$(git describe --tags --exact-match 2>/dev/null || git rev-parse --short HEAD)"
|
||||
log_info "Aktuelle Version: $CURRENT_REF"
|
||||
|
||||
log_info "Prüfe auf neue Releases …"
|
||||
git fetch --tags --quiet origin
|
||||
|
||||
if [ -n "$TARGET_VERSION" ]; then
|
||||
NEW_VERSION="$TARGET_VERSION"
|
||||
else
|
||||
# Highest vX.Y.Z tag, sorted as real version numbers (not alphabetically).
|
||||
NEW_VERSION="$(git tag -l 'v*' | sort -t. -k1.2,1n -k2,2n -k3,3n | tail -n1)"
|
||||
fi
|
||||
|
||||
if [ -z "$NEW_VERSION" ]; then
|
||||
log_info "Keine Release-Tags im Repository gefunden — nichts zu deployen (main wird bewusst nicht automatisch deployt)."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "$NEW_VERSION" = "$CURRENT_REF" ]; then
|
||||
log_info "Bereits auf dem neuesten Release ($CURRENT_REF)."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "$AUTO_MODE" = true ]; then
|
||||
log_info "Neues Release gefunden: $NEW_VERSION (aktuell: $CURRENT_REF) — automatisches Update wird gestartet."
|
||||
fi
|
||||
log_info "Update: $CURRENT_REF → $NEW_VERSION"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Backup
|
||||
# ---------------------------------------------------------------------------
|
||||
mkdir -p backups
|
||||
BACKUP_FILE="backups/database-$(date +%Y-%m-%d-%H%M).sql.gz"
|
||||
log_info "Erstelle Datenbank-Backup: $BACKUP_FILE"
|
||||
set -a; source .env; set +a
|
||||
if $COMPOSE ps -q postgres >/dev/null 2>&1 && [ -n "$($COMPOSE ps -q postgres)" ]; then
|
||||
$COMPOSE exec -T postgres pg_dump -U "${POSTGRES_USER:-postgres}" "${POSTGRES_DB:-anouma}" | gzip > "$BACKUP_FILE"
|
||||
log_success "Backup erstellt ($(du -h "$BACKUP_FILE" | cut -f1))."
|
||||
else
|
||||
log_warn "Datenbank-Container läuft nicht — Backup übersprungen."
|
||||
fi
|
||||
|
||||
# Retention: delete backups older than BACKUP_RETENTION_DAYS, but never the
|
||||
# one we just created.
|
||||
RETENTION_DAYS="${BACKUP_RETENTION_DAYS:-14}"
|
||||
find backups -name 'database-*.sql.gz' -mtime "+${RETENTION_DAYS}" -not -name "$(basename "$BACKUP_FILE")" -delete 2>/dev/null || true
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Deploy
|
||||
# ---------------------------------------------------------------------------
|
||||
wait_healthy() {
|
||||
local service="$1" timeout_iterations="$2" id status
|
||||
for _ in $(seq 1 "$timeout_iterations"); do
|
||||
id="$($COMPOSE ps -q "$service" 2>/dev/null || true)"
|
||||
if [ -n "$id" ]; then
|
||||
status="$(docker inspect --format '{{.State.Health.Status}}' "$id" 2>/dev/null || true)"
|
||||
[ "$status" = "healthy" ] && return 0
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
deploy_ref() {
|
||||
local ref="$1"
|
||||
git checkout --quiet "$ref"
|
||||
$COMPOSE build
|
||||
$COMPOSE run --rm app npm run migrate
|
||||
$COMPOSE up -d
|
||||
}
|
||||
|
||||
log_info "Checke $NEW_VERSION aus und baue neu …"
|
||||
if deploy_ref "$NEW_VERSION" && wait_healthy app 60; then
|
||||
log_success "Update auf $NEW_VERSION erfolgreich."
|
||||
echo "$NEW_VERSION" > .installed-version
|
||||
exit 0
|
||||
fi
|
||||
|
||||
log_error "Healthcheck nach Update auf $NEW_VERSION fehlgeschlagen — rolle Code auf $CURRENT_REF zurück."
|
||||
log_warn "Datenbank-Migrationen werden dabei NICHT rückgängig gemacht (siehe DEPLOYMENT.md) — falls $NEW_VERSION eine nicht abwärtskompatible Migration enthielt, stelle das Backup manuell wieder her: $BACKUP_FILE"
|
||||
|
||||
if deploy_ref "$CURRENT_REF" && wait_healthy app 60; then
|
||||
log_warn "Rollback auf $CURRENT_REF erfolgreich — die Anwendung läuft wieder auf der vorherigen Version."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
fail "Rollback ebenfalls fehlgeschlagen — bitte manuell prüfen: $COMPOSE logs app"
|
||||
Reference in New Issue
Block a user