feat: architecture review fixes, self-update system, CI pipeline, supply chain hardening
Architecture review (all P0+P1 issues now fixed): - Add 10s timeout to 6 bare Nostr client.connect() calls - Pin all 12 crypto deps to exact versions from Cargo.lock - Pin all 15 floating container image tags to exact patch versions - Add CI pipeline (cargo fmt + clippy + tests, frontend type-check + build) Self-update system (git.tx1138.com): - scripts/self-update.sh: pull, build, install, restart with rollback - systemd timer checks daily at 3 AM - update.check RPC does git-based checks when repo is present - update.git-apply RPC triggers self-update from UI - Default update URL changed from GitHub to git.tx1138.com - Git added to ISO package list for fresh installs Documentation: - CHANGELOG v1.3.1 with all changes - README updated (version, update system section) - BETA-PROGRESS session #6 logged - architecture-review.html: 4 issues marked FIXED, 8/12 refactoring done Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -8,32 +8,32 @@
|
||||
# Bitcoin stack
|
||||
BITCOIN_KNOTS_IMAGE="docker.io/bitcoinknots/bitcoin:28.1"
|
||||
LND_IMAGE="docker.io/lightninglabs/lnd:v0.18.5-beta"
|
||||
ELECTRUMX_IMAGE="docker.io/lukechilds/electrumx:v1.16.0"
|
||||
ELECTRUMX_IMAGE="docker.io/lukechilds/electrumx:v1.18.0"
|
||||
|
||||
# Mempool stack
|
||||
MEMPOOL_BACKEND_IMAGE="docker.io/mempool/backend:v3.0.0"
|
||||
MEMPOOL_WEB_IMAGE="docker.io/mempool/frontend:v3.0.0"
|
||||
MARIADB_IMAGE="docker.io/library/mariadb:11.4"
|
||||
MARIADB_IMAGE="docker.io/library/mariadb:11.4.10"
|
||||
|
||||
# BTCPay
|
||||
BTCPAY_IMAGE="docker.io/btcpayserver/btcpayserver:1.13.7"
|
||||
NBXPLORER_IMAGE="docker.io/nicolasdorier/nbxplorer:2.5.13"
|
||||
POSTGRES_IMAGE="docker.io/library/postgres:15"
|
||||
BTCPAY_POSTGRES_IMAGE="docker.io/library/postgres:15"
|
||||
NBXPLORER_IMAGE="docker.io/nicolasdorier/nbxplorer:2.6.0"
|
||||
POSTGRES_IMAGE="docker.io/library/postgres:15.17"
|
||||
BTCPAY_POSTGRES_IMAGE="docker.io/library/postgres:15.17"
|
||||
|
||||
# Apps
|
||||
HOMEASSISTANT_IMAGE="ghcr.io/home-assistant/home-assistant:2024.12"
|
||||
HOMEASSISTANT_IMAGE="ghcr.io/home-assistant/home-assistant:2024.12.5"
|
||||
GRAFANA_IMAGE="docker.io/grafana/grafana:11.4.0"
|
||||
UPTIME_KUMA_IMAGE="docker.io/louislam/uptime-kuma:1"
|
||||
UPTIME_KUMA_IMAGE="docker.io/louislam/uptime-kuma:1.23.17"
|
||||
JELLYFIN_IMAGE="docker.io/jellyfin/jellyfin:10.10.3"
|
||||
PHOTOPRISM_IMAGE="docker.io/photoprism/photoprism:240915"
|
||||
OLLAMA_IMAGE="docker.io/ollama/ollama:0.5.4"
|
||||
VAULTWARDEN_IMAGE="docker.io/vaultwarden/server:1.32.5"
|
||||
NEXTCLOUD_IMAGE="docker.io/library/nextcloud:29"
|
||||
NEXTCLOUD_IMAGE="docker.io/library/nextcloud:29.0.16"
|
||||
SEARXNG_IMAGE="docker.io/searxng/searxng:2026.3.20-6c7e9c197"
|
||||
ONLYOFFICE_IMAGE="docker.io/onlyoffice/documentserver:8.2"
|
||||
FILEBROWSER_IMAGE="docker.io/filebrowser/filebrowser:v2"
|
||||
NPM_IMAGE="docker.io/jc21/nginx-proxy-manager:2"
|
||||
ONLYOFFICE_IMAGE="docker.io/onlyoffice/documentserver:8.2.3.1"
|
||||
FILEBROWSER_IMAGE="docker.io/filebrowser/filebrowser:v2.27.0"
|
||||
NPM_IMAGE="docker.io/jc21/nginx-proxy-manager:2.14.0"
|
||||
PORTAINER_IMAGE="docker.io/portainer/portainer-ce:2.21.5"
|
||||
|
||||
# Networking
|
||||
@@ -42,14 +42,14 @@ ALPINE_TOR_IMAGE="docker.io/andrius/alpine-tor:0.4.8.13"
|
||||
ADGUARDHOME_IMAGE="docker.io/adguard/adguardhome:v0.107.55"
|
||||
|
||||
# Fedimint
|
||||
FEDIMINT_IMAGE="docker.io/fedimint/fedimintd:v0.5.1"
|
||||
FEDIMINT_GATEWAY_IMAGE="docker.io/fedimint/gatewayd:v0.5.1"
|
||||
FEDIMINT_IMAGE="docker.io/fedimint/fedimintd:v0.10.0"
|
||||
FEDIMINT_GATEWAY_IMAGE="docker.io/fedimint/gatewayd:v0.10.0"
|
||||
|
||||
# Media
|
||||
REDIS_IMAGE="docker.io/library/redis:7"
|
||||
REDIS_IMAGE="docker.io/library/redis:7.4.8"
|
||||
|
||||
# Valkey (general purpose)
|
||||
VALKEY_IMAGE="docker.io/valkey/valkey:8"
|
||||
VALKEY_IMAGE="docker.io/valkey/valkey:8.1.6"
|
||||
|
||||
# Nostr
|
||||
NOSTR_RS_RELAY_IMAGE="docker.io/scsibug/nostr-rs-relay:0.9.0"
|
||||
@@ -57,12 +57,12 @@ STRFRY_IMAGE="docker.io/pluja/strfry:1.0.4"
|
||||
|
||||
# IndeedHub stack (local builds use :local tag, not :latest)
|
||||
MINIO_IMAGE="docker.io/minio/minio:RELEASE.2024-11-07T00-52-20Z"
|
||||
INDEEDHUB_POSTGRES_IMAGE="docker.io/library/postgres:16-alpine"
|
||||
INDEEDHUB_REDIS_IMAGE="docker.io/library/redis:7-alpine"
|
||||
INDEEDHUB_POSTGRES_IMAGE="docker.io/library/postgres:16.13-alpine"
|
||||
INDEEDHUB_REDIS_IMAGE="docker.io/library/redis:7.4.8-alpine"
|
||||
|
||||
# DWN (Decentralized Web Node)
|
||||
DWN_SERVER_IMAGE="ghcr.io/tbd54566975/dwn-server:main"
|
||||
DWN_SERVER_IMAGE="ghcr.io/tbd54566975/dwn-server:main@sha256:665cb00f45ffbf0d6324915b593503927654ebf13b7b71440a5ffe26edb3c48e"
|
||||
|
||||
|
||||
# Base images
|
||||
NGINX_ALPINE_IMAGE="docker.io/library/nginx:alpine"
|
||||
NGINX_ALPINE_IMAGE="docker.io/library/nginx:1.29.6-alpine"
|
||||
|
||||
230
scripts/self-update.sh
Executable file
230
scripts/self-update.sh
Executable file
@@ -0,0 +1,230 @@
|
||||
#!/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 image-versions.sh on the server
|
||||
if [ -f "$REPO_DIR/scripts/image-versions.sh" ]; then
|
||||
sudo cp "$REPO_DIR/scripts/image-versions.sh" /opt/archipelago/image-versions.sh
|
||||
ok "Image versions updated"
|
||||
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"
|
||||
Reference in New Issue
Block a user