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