freezmo/scripts/rollback.sh
Martin Dvorak 3f52ab47c2 Infra: split dev and production (releases, services, deploy script)
- Production now runs from ~/freezmo-prod/current (release symlink) as a
  standalone server on :3207; releases are exported by scripts/deploy.sh,
  built and e2e-verified on a temp port, switched atomically, health-
  checked via the new /api/healthz (commit + build time) and rolled back
  automatically on failure. Last 5 releases kept; scripts/rollback.sh for
  manual rollback. Shared env + data live in ~/freezmo-prod/shared/ and
  are symlinked into every release (existing sync/push data migrated and
  verified identical).
- Dev runs from ~/freezmo via freezmo-dev.service: next dev on :3217
  (LAN only), its own build folder (.next-dev via NEXT_DIST_DIR), its own
  data (~/freezmo/.freezmo-dev), DEV badge, dev favicon/theme colour,
  "Freezmo Dev" PWA identity and never the production service worker.
- /api/healthz + the Settings Details version row (short commit + build
  time). output: standalone in next.config (next start still works).
- AGENTS.md documents the workflow rules: only dev updates during a task,
  temporary dirs/ports for test builds, and deploy-at-the-end as the only
  production path. README gains an Environments and deploy section.
- Fix found by the rehearsal: lib/dev-mode.ts must not be a client module
  (a server component importing a constant from a "use client" module
  gets a truthy proxy, which baked dev metadata into production builds).
- SW -> freezmo-v34.
2026-10-11 19:25:29 +00:00

59 lines
2 KiB
Bash
Executable file

#!/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"