The OTA self-update path only refreshed image-versions.sh, leaving reconcile-containers.sh and container-specs.sh frozen at whatever version was baked into the ISO that originally provisioned the node. Any fix to those scripts (notably the --create-missing flag and the DISK_GB detection fix shipped this round) never reached existing nodes, and on .228 both scripts were outright missing because the node predated their inclusion in the ISO recipe. Install all three helper scripts to /opt/archipelago/scripts/ on every self-update run. Also preserve the legacy copy of image-versions.sh at /opt/archipelago/image-versions.sh for any older backend binaries still looking there first.
244 lines
7.1 KiB
Bash
Executable File
244 lines
7.1 KiB
Bash
Executable File
#!/bin/bash
|
|
# Self-update: pull latest code from git.tx1138.com and apply
|
|
# Designed to run on installed Archipelago nodes (as archipelago user)
|
|
#
|
|
# Usage:
|
|
# ./self-update.sh # Check + apply if available
|
|
# ./self-update.sh --check # Check only, don't apply
|
|
# ./self-update.sh --force # Apply even if already up to date
|
|
#
|
|
# The script:
|
|
# 1. Pulls latest code from origin (git.tx1138.com)
|
|
# 2. Builds the Rust backend (release mode)
|
|
# 3. Builds the Vue frontend (production mode)
|
|
# 4. Installs the new binary and web UI
|
|
# 5. Restarts the archipelago service
|
|
# 6. Verifies health after restart
|
|
|
|
set -euo pipefail
|
|
|
|
REPO_DIR="$HOME/archy"
|
|
BACKEND_DIR="$REPO_DIR/core"
|
|
FRONTEND_DIR="$REPO_DIR/neode-ui"
|
|
INSTALL_BIN="/usr/local/bin/archipelago"
|
|
INSTALL_WEB="/opt/archipelago/web-ui"
|
|
STATE_FILE="/var/lib/archipelago/update_state.json"
|
|
LOG_FILE="/var/lib/archipelago/update.log"
|
|
LOCK_FILE="/tmp/archipelago-update.lock"
|
|
|
|
# Colors
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
BLUE='\033[0;34m'
|
|
NC='\033[0m'
|
|
|
|
log() { echo -e "${BLUE}[$(date '+%H:%M:%S')]${NC} $*" | tee -a "$LOG_FILE"; }
|
|
ok() { echo -e "${GREEN}[$(date '+%H:%M:%S')] OK${NC} $*" | tee -a "$LOG_FILE"; }
|
|
err() { echo -e "${RED}[$(date '+%H:%M:%S')] ERROR${NC} $*" | tee -a "$LOG_FILE"; }
|
|
warn(){ echo -e "${YELLOW}[$(date '+%H:%M:%S')] WARN${NC} $*" | tee -a "$LOG_FILE"; }
|
|
|
|
cleanup() {
|
|
rm -f "$LOCK_FILE"
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
# Prevent concurrent updates
|
|
if [ -f "$LOCK_FILE" ]; then
|
|
pid=$(cat "$LOCK_FILE" 2>/dev/null)
|
|
if kill -0 "$pid" 2>/dev/null; then
|
|
err "Update already in progress (PID $pid)"
|
|
exit 1
|
|
fi
|
|
warn "Stale lock file found, removing"
|
|
rm -f "$LOCK_FILE"
|
|
fi
|
|
echo $$ > "$LOCK_FILE"
|
|
|
|
# Parse args
|
|
CHECK_ONLY=false
|
|
FORCE=false
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--check) CHECK_ONLY=true; shift ;;
|
|
--force) FORCE=true; shift ;;
|
|
*) shift ;;
|
|
esac
|
|
done
|
|
|
|
# Ensure repo exists
|
|
if [ ! -d "$REPO_DIR/.git" ]; then
|
|
err "Repo not found at $REPO_DIR"
|
|
err "Clone it first: git clone https://git.tx1138.com/lfg2025/archy ~/archy"
|
|
exit 1
|
|
fi
|
|
|
|
cd "$REPO_DIR"
|
|
|
|
# Fetch latest
|
|
log "Fetching from origin..."
|
|
git fetch origin main --quiet 2>>"$LOG_FILE"
|
|
|
|
# Check if there are updates
|
|
LOCAL=$(git rev-parse HEAD)
|
|
REMOTE=$(git rev-parse origin/main)
|
|
|
|
if [ "$LOCAL" = "$REMOTE" ] && [ "$FORCE" = "false" ]; then
|
|
ok "Already up to date ($LOCAL)"
|
|
if [ "$CHECK_ONLY" = "true" ]; then
|
|
echo '{"update_available": false, "current": "'"$LOCAL"'"}'
|
|
fi
|
|
exit 0
|
|
fi
|
|
|
|
# Calculate what changed
|
|
COMMITS_BEHIND=$(git rev-list HEAD..origin/main --count)
|
|
log "Update available: $COMMITS_BEHIND commits behind"
|
|
log " Local: $LOCAL"
|
|
log " Remote: $REMOTE"
|
|
|
|
if [ "$CHECK_ONLY" = "true" ]; then
|
|
CHANGELOG=$(git log HEAD..origin/main --oneline --no-merges | head -20)
|
|
echo '{"update_available": true, "current": "'"$LOCAL"'", "latest": "'"$REMOTE"'", "commits_behind": '"$COMMITS_BEHIND"'}'
|
|
echo ""
|
|
echo "Changes:"
|
|
echo "$CHANGELOG"
|
|
exit 0
|
|
fi
|
|
|
|
# Backup current binary
|
|
BACKUP_DIR="/var/lib/archipelago/update-backup"
|
|
mkdir -p "$BACKUP_DIR"
|
|
if [ -f "$INSTALL_BIN" ]; then
|
|
cp "$INSTALL_BIN" "$BACKUP_DIR/archipelago.bak"
|
|
log "Backed up current binary"
|
|
fi
|
|
|
|
# Pull latest code
|
|
log "Pulling latest code..."
|
|
git pull origin main --ff-only 2>>"$LOG_FILE" || {
|
|
err "Git pull failed — local changes? Run: git reset --hard origin/main"
|
|
exit 1
|
|
}
|
|
|
|
NEW_VERSION=$(git rev-parse --short HEAD)
|
|
log "Now at: $NEW_VERSION"
|
|
|
|
# Build backend
|
|
log "Building Rust backend (release)..."
|
|
cd "$BACKEND_DIR"
|
|
if cargo build --release --workspace 2>>"$LOG_FILE"; then
|
|
ok "Backend built successfully"
|
|
else
|
|
err "Backend build failed — rolling back"
|
|
cd "$REPO_DIR"
|
|
git reset --hard "$LOCAL" 2>>"$LOG_FILE"
|
|
exit 1
|
|
fi
|
|
|
|
# Install binary
|
|
BUILT_BIN="$BACKEND_DIR/target/release/archipelago"
|
|
if [ ! -f "$BUILT_BIN" ]; then
|
|
err "Built binary not found at $BUILT_BIN"
|
|
exit 1
|
|
fi
|
|
sudo cp "$BUILT_BIN" "$INSTALL_BIN"
|
|
sudo chmod +x "$INSTALL_BIN"
|
|
ok "Backend installed"
|
|
|
|
# Build frontend
|
|
log "Building Vue frontend (production)..."
|
|
cd "$FRONTEND_DIR"
|
|
npm ci --silent 2>>"$LOG_FILE" || npm install --silent 2>>"$LOG_FILE"
|
|
if npm run build 2>>"$LOG_FILE"; then
|
|
ok "Frontend built successfully"
|
|
else
|
|
err "Frontend build failed — backend already updated, service may need manual fix"
|
|
exit 1
|
|
fi
|
|
|
|
# Install frontend (preserve aiui and claude-login.html)
|
|
BUILT_WEB="$REPO_DIR/web/dist/neode-ui"
|
|
if [ -d "$BUILT_WEB" ]; then
|
|
# Sync new files, preserving aiui/ and claude-login.html
|
|
sudo rsync -a --delete \
|
|
--exclude 'aiui' \
|
|
--exclude 'claude-login.html' \
|
|
"$BUILT_WEB/" "$INSTALL_WEB/"
|
|
ok "Frontend installed"
|
|
else
|
|
warn "Frontend build output not found at $BUILT_WEB — skipping"
|
|
fi
|
|
|
|
# Update helper scripts in /opt/archipelago/scripts/
|
|
# These are canonical home; keep a copy at /opt/archipelago/image-versions.sh
|
|
# for backward compatibility with older binaries that still look there.
|
|
SCRIPTS_DEST="/opt/archipelago/scripts"
|
|
sudo mkdir -p "$SCRIPTS_DEST"
|
|
for script in image-versions.sh reconcile-containers.sh container-specs.sh; do
|
|
src="$REPO_DIR/scripts/$script"
|
|
if [ -f "$src" ]; then
|
|
sudo install -m 755 "$src" "$SCRIPTS_DEST/$script"
|
|
ok "Updated $script"
|
|
else
|
|
warn "Missing $src — skipping"
|
|
fi
|
|
done
|
|
# Legacy path for image-versions.sh (older binaries looked here first)
|
|
if [ -f "$REPO_DIR/scripts/image-versions.sh" ]; then
|
|
sudo cp "$REPO_DIR/scripts/image-versions.sh" /opt/archipelago/image-versions.sh
|
|
fi
|
|
|
|
# Update systemd service if changed
|
|
if [ -f "$REPO_DIR/image-recipe/configs/archipelago.service" ]; then
|
|
if ! diff -q "$REPO_DIR/image-recipe/configs/archipelago.service" /etc/systemd/system/archipelago.service &>/dev/null; then
|
|
sudo cp "$REPO_DIR/image-recipe/configs/archipelago.service" /etc/systemd/system/archipelago.service
|
|
sudo systemctl daemon-reload
|
|
ok "Systemd service updated"
|
|
fi
|
|
fi
|
|
|
|
# Restart service
|
|
log "Restarting archipelago service..."
|
|
sudo systemctl restart archipelago
|
|
|
|
# Wait for health
|
|
log "Waiting for backend health..."
|
|
for i in $(seq 1 30); do
|
|
if curl -sf http://127.0.0.1:5678/health > /dev/null 2>&1; then
|
|
ok "Backend healthy after ${i}s"
|
|
break
|
|
fi
|
|
if [ "$i" = "30" ]; then
|
|
err "Backend failed to start within 30s"
|
|
warn "Rolling back binary..."
|
|
if [ -f "$BACKUP_DIR/archipelago.bak" ]; then
|
|
sudo cp "$BACKUP_DIR/archipelago.bak" "$INSTALL_BIN"
|
|
sudo systemctl restart archipelago
|
|
err "Rolled back to previous binary"
|
|
fi
|
|
exit 1
|
|
fi
|
|
sleep 1
|
|
done
|
|
|
|
# Update state file for the UI
|
|
python3 -c "
|
|
import json, datetime
|
|
state = {
|
|
'current_version': '$NEW_VERSION',
|
|
'last_check': datetime.datetime.utcnow().isoformat() + 'Z',
|
|
'available_update': None,
|
|
'update_in_progress': False,
|
|
'rollback_available': True,
|
|
'schedule': 'daily_check'
|
|
}
|
|
with open('$STATE_FILE', 'w') as f:
|
|
json.dump(state, f, indent=2)
|
|
" 2>/dev/null || true
|
|
|
|
echo ""
|
|
ok "Update complete: $LOCAL -> $NEW_VERSION"
|
|
log "Changelog:"
|
|
git log "$LOCAL".."$NEW_VERSION" --oneline --no-merges | head -10 | tee -a "$LOG_FILE"
|