#!/usr/bin/env bash # # Roll production back to the previous release (or to a named one). # # scripts/rollback.sh # previous release # scripts/rollback.sh 20261011-193000-5ca0de9 # # It flips the `current` symlink, restarts the service and health-checks the # result, so a manual rollback is the same atomic operation the deploy script # performs on failure. set -euo pipefail PROD="${FREEZMO_PROD:-$HOME/freezmo-prod}" SERVICE="${FREEZMO_SERVICE:-freezmo}" PORT="${FREEZMO_PORT:-3207}" HEALTH_TRIES="${FREEZMO_HEALTH_TRIES:-45}" say() { printf '\033[1m[rollback]\033[0m %s\n' "$*"; } fail() { printf '\033[1;31m[rollback]\033[0m %s\n' "$*" >&2; exit 1; } LIVE="$(readlink -f "$PROD/current" 2>/dev/null || true)" [[ -n "$LIVE" ]] || fail "no current release at $PROD/current" if [[ -n "${1:-}" ]]; then TARGET="$(readlink -f "$PROD/releases/$1" 2>/dev/null || true)" [[ -n "$TARGET" && -d "$TARGET" ]] || fail "release not found: $1" else TARGET="" for dir in "$PROD"/releases/*/; do resolved="$(readlink -f "$dir")" if [[ "$resolved" != "$LIVE" ]]; then TARGET="$resolved" break fi done [[ -n "$TARGET" ]] || fail "no previous release to roll back to" fi say "current: $(basename "$LIVE")" say "switching to: $(basename "$TARGET")" ln -sfn "$TARGET" "$PROD/current.tmp" mv -T "$PROD/current.tmp" "$PROD/current" systemctl --user restart "$SERVICE" EXPECTED="$(grep -o '"commit":"[^"]*"' "$TARGET/server/release.json" 2>/dev/null | cut -d'"' -f4 || true)" [[ -n "$EXPECTED" ]] || EXPECTED="$(basename "$TARGET" | sed 's/.*-//')" for ((i = 0; i < HEALTH_TRIES; i++)); do body="$(curl -fsS --max-time 2 "http://127.0.0.1:$PORT/api/healthz" 2>/dev/null || true)" commit="$(printf '%s' "$body" | grep -o '"commit":"[^"]*"' | cut -d'"' -f4 || true)" if [[ -n "$commit" && "${commit:0:7}" == "${EXPECTED:0:7}" ]]; then say "healthy — production is on $(basename "$TARGET")" exit 0 fi sleep 1 done fail "health check did not confirm $(basename "$TARGET") — check journalctl --user -u $SERVICE"