#!/bin/bash set -euo pipefail COMPONENT="meshpress-bridge" TARGET_DIR="/opt/gracepress-meshpress-bridge" ENV_FILE="/etc/gracepress/meshpress-bridge.env" SERVICE_NAME="gp-meshpress-bridge" LOG_PREFIX="[meshpress-bridge-update]" LOCK_FILE="/tmp/gracepress-meshpress-bridge-update.lock" TMP_DIR="$(mktemp -d)" STAGE_DIR="" BACKUP_DIR="" cleanup() { rm -rf "$TMP_DIR" if [ -n "$STAGE_DIR" ] && [ -d "$STAGE_DIR" ]; then rm -rf "$STAGE_DIR"; fi } trap cleanup EXIT log() { echo "$LOG_PREFIX $(date -Is) $*"; } if [ "$(id -u)" -ne 0 ]; then log "Please run as root." exit 1 fi exec 9>"$LOCK_FILE" if command -v flock >/dev/null 2>&1; then flock -n 9 || { log "Another bridge update is already running."; exit 1; } fi [ -f "$ENV_FILE" ] && . "$ENV_FILE" CATALOG_URL="${GRACEPRESS_CATALOG_URL:-https://christit.com/gracepress/api.php?endpoint=manifest&slug=meshpress-bridge}" apt-get update -y >/dev/null apt-get install -y curl unzip python3 ca-certificates nodejs npm >/dev/null if ! command -v pm2 >/dev/null 2>&1; then npm install -g pm2 >/dev/null fi log "Checking catalog: $CATALOG_URL" curl -fsSL "$CATALOG_URL&nocache=$(date +%s)" -o "$TMP_DIR/manifest.json" DOWNLOAD_URL="$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1])).get("download_url",""))' "$TMP_DIR/manifest.json")" VERSION="$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1])).get("version","0.0.0"))' "$TMP_DIR/manifest.json")" if [ -z "$DOWNLOAD_URL" ]; then log "No download_url found in catalog manifest." exit 1 fi CURRENT="$(cat "$TARGET_DIR/VERSION" 2>/dev/null || echo 0.0.0)" if [ "$CURRENT" = "$VERSION" ]; then log "$COMPONENT is already current: $CURRENT" exit 0 fi log "Updating $COMPONENT from $CURRENT to $VERSION" curl -fL "$DOWNLOAD_URL" -o "$TMP_DIR/package.zip" unzip -q "$TMP_DIR/package.zip" -d "$TMP_DIR/pkg" PKG_DIR="$(find "$TMP_DIR/pkg" -maxdepth 3 -type f -name server.js -printf '%h\n' | head -n 1)" if [ -z "$PKG_DIR" ] || [ ! -f "$PKG_DIR/package.json" ]; then log "Package does not contain a valid bridge payload." exit 1 fi STAGE_DIR="${TARGET_DIR}.next.${VERSION}.$$" rm -rf "$STAGE_DIR" mkdir -p "$STAGE_DIR" cp -a "$PKG_DIR"/. "$STAGE_DIR"/ echo "$VERSION" > "$STAGE_DIR/VERSION" chmod +x "$STAGE_DIR/update.sh" "$STAGE_DIR/meshpress-bridge" 2>/dev/null || true log "Installing node dependencies in staging directory." (cd "$STAGE_DIR" && npm install --omit=dev >/dev/null && node --check server.js >/dev/null) BACKUP_DIR="${TARGET_DIR}.old.$(date +%Y%m%d%H%M%S)" if [ -d "$TARGET_DIR" ]; then mv "$TARGET_DIR" "$BACKUP_DIR" log "Previous install moved to $BACKUP_DIR" fi mv "$STAGE_DIR" "$TARGET_DIR" STAGE_DIR="" rollback() { log "Update failed after swap; attempting rollback." local failed="${TARGET_DIR}.failed.$(date +%Y%m%d%H%M%S)" if [ -d "$TARGET_DIR" ]; then mv "$TARGET_DIR" "$failed"; fi if [ -n "$BACKUP_DIR" ] && [ -d "$BACKUP_DIR" ]; then mv "$BACKUP_DIR" "$TARGET_DIR"; fi if [ -f "$TARGET_DIR/server.js" ]; then pm2 start "$TARGET_DIR/server.js" --name "$SERVICE_NAME" --time --max-memory-restart 512M >/dev/null 2>&1 || pm2 restart "$SERVICE_NAME" --update-env >/dev/null 2>&1 || true pm2 save >/dev/null 2>&1 || true fi } if pm2 describe "$SERVICE_NAME" >/dev/null 2>&1; then if ! (cd "$TARGET_DIR" && pm2 restart "$SERVICE_NAME" --update-env >/dev/null); then rollback exit 1 fi else if ! (cd "$TARGET_DIR" && pm2 start server.js --name "$SERVICE_NAME" --time --max-memory-restart 512M >/dev/null); then rollback exit 1 fi fi pm2 save >/dev/null log "$COMPONENT updated to $VERSION"