Four fixes building on the prior upgrade-path work. All observed on marcelle across today's v2.9.2 → v2.9.5 cycles and addressed here. - Fix 1 (breaking-release gate). upgrade-check.sh now parses the first line of each Gitea release body for `BREAKING: <reason>` and threads `breaking`/`breakingReason` through status.json into the API status response. Admin UI renders a red Alert with a typed-tag confirmation input and gates the Start Upgrade button. auto-upgrade.service.ts refuses to apply breaking releases, logging a skip and holding off until the operator confirms manually. - Fix 2 (release-mode rollback). print_rollback_help and the --rollback flow both used `git checkout`, which silently fails in release installs (no .git). Added INSTALL_MODE branches: release mode downloads the prior tarball from Gitea using a new VERSION.rollback marker seeded at Phase 3 start. Source mode retains the existing git-based flow. - Fix 3 (Phase 7 health budgets). admin verify_service_health budget 30s → 90s (matches the admin container's start_period from commit 47704667). Gancio + MkDocs switched from one-shot to the existing verify_service_health retry wrapper. Cuts the cry-wolf "services may still be starting" warning from every upgrade result. - Fix 4 (symmetric success archival). Bash archive_failure_to_history already logs failures on exit; added a matching archive_success_to_ history called after write_result on the success path. API-side archiveResult now dedupes on completedAt so double-recording (bash + post-restart handler) can't land twice in history.json. Release the bundle as v2.9.6. Bunker Admin
189 lines
6.4 KiB
Bash
Executable File
189 lines
6.4 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# =============================================================================
|
|
# Changemaker Lite V2 — Upgrade Check Script
|
|
# Checks for available updates and writes status to data/upgrade/status.json.
|
|
# Safe to run via cron or on-demand via file trigger.
|
|
# Usage: ./scripts/upgrade-check.sh [--branch BRANCH]
|
|
# =============================================================================
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
|
|
UPGRADE_DIR="${PROJECT_DIR}/data/upgrade"
|
|
STATUS_FILE="${UPGRADE_DIR}/status.json"
|
|
BRANCH=""
|
|
|
|
# --- Parse Arguments ---
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--branch) BRANCH="$2"; shift 2 ;;
|
|
*) shift ;;
|
|
esac
|
|
done
|
|
|
|
cd "$PROJECT_DIR"
|
|
mkdir -p "$UPGRADE_DIR"
|
|
|
|
# --- Detect install mode ---
|
|
if [[ -f "$PROJECT_DIR/VERSION" ]] && [[ ! -d "$PROJECT_DIR/.git" ]]; then
|
|
INSTALL_MODE="release"
|
|
else
|
|
INSTALL_MODE="source"
|
|
fi
|
|
|
|
# --- Release mode: check Gitea Releases API ---
|
|
if [[ "$INSTALL_MODE" == "release" ]]; then
|
|
GITEA_API="https://gitea.bnkops.com/api/v1"
|
|
CURRENT_VERSION=$(head -1 "$PROJECT_DIR/VERSION" 2>/dev/null || echo "unknown")
|
|
CURRENT_SHA=$(sed -n '2p' "$PROJECT_DIR/VERSION" 2>/dev/null || echo "unknown")
|
|
CURRENT_DATE=$(sed -n '3p' "$PROJECT_DIR/VERSION" 2>/dev/null || echo "")
|
|
|
|
RELEASE_JSON=$(curl -sf "${GITEA_API}/repos/admin/changemaker.lite/releases/latest" 2>/dev/null || true)
|
|
if [[ -z "$RELEASE_JSON" ]]; then
|
|
cat > "$STATUS_FILE" <<EOF
|
|
{
|
|
"branch": "release",
|
|
"currentCommit": "${CURRENT_SHA}",
|
|
"currentCommitFull": "${CURRENT_SHA}",
|
|
"currentMessage": "Release ${CURRENT_VERSION}",
|
|
"currentDate": "${CURRENT_DATE}",
|
|
"remoteCommit": null,
|
|
"commitsBehind": 0,
|
|
"changelog": [],
|
|
"checkedAt": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
|
|
"error": "Failed to reach Gitea API"
|
|
}
|
|
EOF
|
|
exit 1
|
|
fi
|
|
|
|
LATEST_TAG=$(echo "$RELEASE_JSON" | python3 -c "import sys,json; print(json.load(sys.stdin).get('tag_name',''))" 2>/dev/null)
|
|
LATEST_DATE=$(echo "$RELEASE_JSON" | python3 -c "import sys,json; print(json.load(sys.stdin).get('created_at',''))" 2>/dev/null)
|
|
LATEST_BODY=$(echo "$RELEASE_JSON" | python3 -c "import sys,json; print(json.load(sys.stdin).get('body','').replace('\"','\\\\\"')[:200])" 2>/dev/null)
|
|
|
|
# Breaking-release marker: first line of the release body matching
|
|
# `^BREAKING:[[:space:]]*(.+)` (case-insensitive) flags this release as
|
|
# requiring manual confirmation. Admin UI gates Start Upgrade and
|
|
# auto-upgrade refuses to apply until the operator confirms.
|
|
IS_BREAKING=$(echo "$RELEASE_JSON" | python3 -c "
|
|
import sys, json, re
|
|
body = json.load(sys.stdin).get('body', '') or ''
|
|
m = re.match(r'^BREAKING:\s*(.+?)(?:\n|$)', body, re.IGNORECASE)
|
|
print('true' if m else 'false')
|
|
" 2>/dev/null || echo "false")
|
|
BREAKING_REASON=$(echo "$RELEASE_JSON" | python3 -c "
|
|
import sys, json, re
|
|
body = json.load(sys.stdin).get('body', '') or ''
|
|
m = re.match(r'^BREAKING:\s*(.+?)(?:\n|$)', body, re.IGNORECASE)
|
|
print((m.group(1).strip() if m else '').replace('\"','\\\\\"')[:300])
|
|
" 2>/dev/null || echo "")
|
|
|
|
if [[ "$CURRENT_VERSION" == "$LATEST_TAG" ]]; then
|
|
COMMITS_BEHIND=0
|
|
else
|
|
COMMITS_BEHIND=1
|
|
fi
|
|
|
|
cat > "$STATUS_FILE" <<EOF
|
|
{
|
|
"branch": "release",
|
|
"currentCommit": "${CURRENT_SHA}",
|
|
"currentCommitFull": "${CURRENT_SHA}",
|
|
"currentMessage": "Release ${CURRENT_VERSION}",
|
|
"currentDate": "${CURRENT_DATE}",
|
|
"remoteCommit": "${LATEST_TAG}",
|
|
"remoteCommitFull": "${LATEST_TAG}",
|
|
"commitsBehind": ${COMMITS_BEHIND},
|
|
"changelog": [{"hash":"${LATEST_TAG}","message":"${LATEST_BODY}","date":"${LATEST_DATE}","author":"release"}],
|
|
"breaking": ${IS_BREAKING},
|
|
"breakingReason": "${BREAKING_REASON}",
|
|
"checkedAt": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
|
|
"error": null
|
|
}
|
|
EOF
|
|
echo "Update check complete (release mode): ${CURRENT_VERSION} → ${LATEST_TAG} (${COMMITS_BEHIND} update(s) available)"
|
|
exit 0
|
|
fi
|
|
|
|
# --- Source mode: git-based check ---
|
|
|
|
# Determine branch
|
|
if [[ -z "$BRANCH" ]]; then
|
|
BRANCH="$(git rev-parse --abbrev-ref HEAD)"
|
|
fi
|
|
|
|
# Write an error status and exit
|
|
write_error() {
|
|
local msg="$1"
|
|
cat > "$STATUS_FILE" <<EOF
|
|
{
|
|
"branch": "${BRANCH}",
|
|
"currentCommit": "$(git rev-parse --short HEAD 2>/dev/null || echo "unknown")",
|
|
"currentCommitFull": "$(git rev-parse HEAD 2>/dev/null || echo "unknown")",
|
|
"currentMessage": "$(git log -1 --format='%s' HEAD 2>/dev/null | sed 's/"/\\"/g' || echo "")",
|
|
"currentDate": "$(git log -1 --format='%aI' HEAD 2>/dev/null || echo "")",
|
|
"remoteCommit": null,
|
|
"commitsBehind": 0,
|
|
"changelog": [],
|
|
"checkedAt": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
|
|
"error": "${msg}"
|
|
}
|
|
EOF
|
|
exit 1
|
|
}
|
|
|
|
# Fetch latest from remote
|
|
if ! timeout 30 git fetch origin "$BRANCH" 2>/dev/null; then
|
|
write_error "Failed to reach git remote"
|
|
fi
|
|
|
|
# Gather info
|
|
CURRENT_COMMIT="$(git rev-parse HEAD)"
|
|
CURRENT_SHORT="$(git rev-parse --short HEAD)"
|
|
CURRENT_MSG="$(git log -1 --format='%s' HEAD | sed 's/"/\\"/g')"
|
|
CURRENT_DATE="$(git log -1 --format='%aI' HEAD)"
|
|
REMOTE_COMMIT="$(git rev-parse "origin/${BRANCH}" 2>/dev/null || echo "")"
|
|
REMOTE_SHORT="$(git rev-parse --short "origin/${BRANCH}" 2>/dev/null || echo "")"
|
|
|
|
if [[ -z "$REMOTE_COMMIT" ]]; then
|
|
write_error "Remote branch origin/${BRANCH} not found"
|
|
fi
|
|
|
|
# Count commits behind
|
|
COMMITS_BEHIND=0
|
|
if [[ "$CURRENT_COMMIT" != "$REMOTE_COMMIT" ]]; then
|
|
COMMITS_BEHIND="$(git rev-list --count HEAD..origin/"${BRANCH}" 2>/dev/null || echo "0")"
|
|
fi
|
|
|
|
# Build changelog (last 30 commits we're behind)
|
|
CHANGELOG="[]"
|
|
if [[ "$COMMITS_BEHIND" -gt 0 ]]; then
|
|
CHANGELOG="$(git log --oneline --format='{"hash":"%h","message":"%s","date":"%aI","author":"%an"}' HEAD..origin/"${BRANCH}" 2>/dev/null | head -30 | while IFS= read -r line; do
|
|
# Escape any double quotes in the message that aren't already escaped
|
|
echo "$line"
|
|
done | paste -sd ',' | sed 's/^/[/' | sed 's/$/]/')"
|
|
# Fallback if jq-less approach fails
|
|
if [[ -z "$CHANGELOG" ]] || [[ "$CHANGELOG" == "[]" ]]; then
|
|
CHANGELOG="[]"
|
|
fi
|
|
fi
|
|
|
|
# Write status
|
|
cat > "$STATUS_FILE" <<EOF
|
|
{
|
|
"branch": "${BRANCH}",
|
|
"currentCommit": "${CURRENT_SHORT}",
|
|
"currentCommitFull": "${CURRENT_COMMIT}",
|
|
"currentMessage": "${CURRENT_MSG}",
|
|
"currentDate": "${CURRENT_DATE}",
|
|
"remoteCommit": "${REMOTE_SHORT}",
|
|
"remoteCommitFull": "${REMOTE_COMMIT}",
|
|
"commitsBehind": ${COMMITS_BEHIND},
|
|
"changelog": ${CHANGELOG},
|
|
"checkedAt": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
|
|
"error": null
|
|
}
|
|
EOF
|
|
|
|
echo "Update check complete: ${COMMITS_BEHIND} commit(s) behind on ${BRANCH}"
|