feat: deploy-to-target supports .253 + mesh/federation/VPN updates
Some checks failed
Build Archipelago ISO (dev) / build-iso (push) Failing after 3m27s

- Add deploy_secondary() function for deploying to multiple LAN nodes
- --both now deploys to .198 and .253 (previously .198 only)
- Fleet deploy updated for 3 LAN nodes
- Mesh DM fixes: protocol frame format, DM-via-channel routing
- Federation pending requests, discover modal
- VPN status UI improvements
- Image versions and container specs updates

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-04-18 11:07:08 -04:00
parent e210376e05
commit 9dd802998c
38 changed files with 3773 additions and 697 deletions

View File

@@ -6,7 +6,7 @@
# ./scripts/deploy-to-target.sh # Sync and rebuild
# ./scripts/deploy-to-target.sh --quick # Sync only, no rebuild
# ./scripts/deploy-to-target.sh --live # Deploy to live system (default: 192.168.1.228)
# ./scripts/deploy-to-target.sh --both # Deploy to 228, then copy to 198
# ./scripts/deploy-to-target.sh --both # Deploy to 228, then copy to 198 + 253
# ./scripts/deploy-to-target.sh --frontend-only # Frontend-only deploy (skip Rust build + container rebuilds)
# ./scripts/deploy-to-target.sh --demo # Demo mode: Bitcoin pruning enabled (smaller disk)
# ./scripts/deploy-to-target.sh --dry-run --live # Show what would be deployed without executing
@@ -55,6 +55,7 @@ CANARY=false
TAILSCALE=false
TAILSCALE_NODE=""
FLEET=false
RESET_MESH=false
for arg in "$@"; do
case $arg in
--quick) QUICK=true ;;
@@ -68,6 +69,7 @@ for arg in "$@"; do
--tailscale-node=*) TAILSCALE_NODE="${arg#*=}" ;;
--fleet) FLEET=true ;;
--all) FLEET=true ;;
--reset-mesh) RESET_MESH=true ;;
esac
done
@@ -93,8 +95,8 @@ if [ "$FLEET" = true ]; then
echo "FAILED: .228 unreachable"; exit 1
fi
echo ""
echo "Phase 2: Copy to .198 (LAN secondary — skip if unreachable)"
"$0" --both 2>/dev/null || echo " .198 unreachable, skipping"
echo "Phase 2: Copy to .198 + .253 (LAN secondaries — skip if unreachable)"
"$0" --both 2>/dev/null || echo " LAN secondaries unreachable, skipping"
echo ""
echo "Phase 3: Deploy to all Tailscale nodes (Arch 1/2/3)"
"$SCRIPT_DIR/deploy-tailscale.sh" --all || { echo "WARNING: Some Tailscale nodes failed"; }
@@ -333,22 +335,19 @@ if [ "$CANARY" = true ]; then
exit 0
fi
# When --both: deploy to 228 first, then copy to 198
if [ "$BOTH" = true ]; then
echo "Deploying to both servers (228, then 198)..."
# Release lock so the recursive --live call can acquire it
rm -rf "$LOCK_DIR" 2>/dev/null; trap - EXIT
"$0" --live
# ── deploy_secondary: copy built binary+frontend from .228 to a secondary node ──
# Usage: deploy_secondary <user@ip> <short_label> (e.g. deploy_secondary archipelago@192.168.1.198 198)
deploy_secondary() {
local SEC_TARGET="$1"
local SEC_LABEL="$2"
local SEC_IP="${SEC_TARGET#*@}"
echo ""
echo "📤 Copying to 192.168.1.198 (no rsync/cargo on that node)..."
TARGET_198="archipelago@192.168.1.198"
if ! scp $SSH_OPTS "archipelago@192.168.1.228:$TARGET_DIR/core/target/release/archipelago" /tmp/archipelago-both 2>/dev/null; then
echo " ERROR: Failed to copy binary from .228 — is the build available?"
exit 1
fi
scp $SSH_OPTS /tmp/archipelago-both "$TARGET_198:/tmp/archipelago-new"
ssh $SSH_OPTS "archipelago@192.168.1.228" "cd '$TARGET_DIR' && tar cf - web/dist/neode-ui 2>/dev/null" | ssh $SSH_OPTS "$TARGET_198" "mkdir -p /tmp/web-deploy && cd /tmp/web-deploy && tar xf -"
ssh $SSH_OPTS "$TARGET_198" '
echo "📤 Copying to $SEC_IP (no rsync/cargo on that node)..."
scp $SSH_OPTS /tmp/archipelago-both "$SEC_TARGET:/tmp/archipelago-new"
ssh $SSH_OPTS "archipelago@192.168.1.228" "cd '$TARGET_DIR' && tar cf - web/dist/neode-ui 2>/dev/null" | ssh $SSH_OPTS "$SEC_TARGET" "mkdir -p /tmp/web-deploy && cd /tmp/web-deploy && tar xf -"
ssh $SSH_OPTS "$SEC_TARGET" '
sudo systemctl stop archipelago
sudo cp /tmp/archipelago-new /usr/local/bin/archipelago
sudo chmod +x /usr/local/bin/archipelago
@@ -358,49 +357,49 @@ if [ "$BOTH" = true ]; then
sudo chown -R 1000:1000 /opt/archipelago/web-ui
'
# Deploy AIUI to 198 if available
# Deploy AIUI if available
AIUI_DIST="$PROJECT_DIR/../AIUI/packages/app/dist"
if [ -d "$AIUI_DIST" ] && [ -f "$AIUI_DIST/index.html" ]; then
echo " Deploying AIUI to 198..."
ssh $SSH_OPTS "$TARGET_198" "sudo mkdir -p /opt/archipelago/web-ui/aiui && sudo rm -rf /opt/archipelago/web-ui/aiui/*"
cd "$AIUI_DIST" && tar --no-xattrs -cf - . | ssh $SSH_OPTS "$TARGET_198" "sudo tar xf - -C /opt/archipelago/web-ui/aiui/"
echo " Deploying AIUI to .$SEC_LABEL..."
ssh $SSH_OPTS "$SEC_TARGET" "sudo mkdir -p /opt/archipelago/web-ui/aiui && sudo rm -rf /opt/archipelago/web-ui/aiui/*"
cd "$AIUI_DIST" && tar --no-xattrs -cf - . | ssh $SSH_OPTS "$SEC_TARGET" "sudo tar xf - -C /opt/archipelago/web-ui/aiui/"
cd "$PROJECT_DIR"
ssh $SSH_OPTS "$TARGET_198" "sudo chown -R 1000:1000 /opt/archipelago/web-ui/aiui"
ssh $SSH_OPTS "$SEC_TARGET" "sudo chown -R 1000:1000 /opt/archipelago/web-ui/aiui"
fi
# Sync nginx config + snippets + fixes to 198
# Sync nginx config + snippets
NGINX_CFG="$PROJECT_DIR/image-recipe/configs/nginx-archipelago.conf"
SNIPPETS_DIR="$PROJECT_DIR/image-recipe/configs/snippets"
if [ -f "$NGINX_CFG" ]; then
echo " Syncing nginx config to 198..."
scp $SSH_OPTS "$NGINX_CFG" "$TARGET_198:/tmp/nginx-archipelago.conf" 2>/dev/null || true
ssh $SSH_OPTS "$TARGET_198" '
echo " Syncing nginx config to .$SEC_LABEL..."
scp $SSH_OPTS "$NGINX_CFG" "$SEC_TARGET:/tmp/nginx-archipelago.conf" 2>/dev/null || true
ssh $SSH_OPTS "$SEC_TARGET" '
sudo cp /tmp/nginx-archipelago.conf /etc/nginx/sites-available/archipelago
sudo rm -f /etc/nginx/conf.d/external-app-proxies.conf
sudo sed -i "s|proxy_pass http://127.0.0.1:3141/;|proxy_pass http://127.0.0.1:3142/;|g" /etc/nginx/sites-available/archipelago
rm -f /tmp/nginx-archipelago.conf
' 2>/dev/null || true
fi
# Sync nginx snippets to 198
if [ -d "$SNIPPETS_DIR" ]; then
echo " Syncing nginx snippets to 198..."
ssh $SSH_OPTS "$TARGET_198" "sudo mkdir -p /etc/nginx/snippets" 2>/dev/null || true
echo " Syncing nginx snippets to .$SEC_LABEL..."
ssh $SSH_OPTS "$SEC_TARGET" "sudo mkdir -p /etc/nginx/snippets" 2>/dev/null || true
for f in "$SNIPPETS_DIR"/*.conf; do
[ -f "$f" ] && scp $SSH_OPTS "$f" "$TARGET_198:/tmp/nginx-snippet-$(basename "$f")" 2>/dev/null || true
[ -f "$f" ] && scp $SSH_OPTS "$f" "$SEC_TARGET:/tmp/nginx-snippet-$(basename "$f")" 2>/dev/null || true
done
ssh $SSH_OPTS "$TARGET_198" '
ssh $SSH_OPTS "$SEC_TARGET" '
for f in /tmp/nginx-snippet-*.conf; do
[ -f "$f" ] && sudo mv "$f" "/etc/nginx/snippets/$(basename "$f" | sed "s/^nginx-snippet-//")"
done
' 2>/dev/null || true
fi
ssh $SSH_OPTS "$TARGET_198" 'sudo nginx -t 2>&1 && echo " nginx config OK" || echo " nginx config test failed"' 2>/dev/null || true
# Sync systemd service file to 198
ssh $SSH_OPTS "$SEC_TARGET" 'sudo nginx -t 2>&1 && echo " nginx config OK" || echo " nginx config test failed"' 2>/dev/null || true
# Sync systemd service file
SERVICE_FILE="$PROJECT_DIR/image-recipe/configs/archipelago.service"
if [ -f "$SERVICE_FILE" ]; then
echo " Syncing systemd service to 198..."
scp $SSH_OPTS "$SERVICE_FILE" "$TARGET_198:/tmp/archipelago.service" 2>/dev/null || true
ssh $SSH_OPTS "$TARGET_198" '
echo " Syncing systemd service to .$SEC_LABEL..."
scp $SSH_OPTS "$SERVICE_FILE" "$SEC_TARGET:/tmp/archipelago.service" 2>/dev/null || true
ssh $SSH_OPTS "$SEC_TARGET" '
if ! diff -q /tmp/archipelago.service /etc/systemd/system/archipelago.service >/dev/null 2>&1; then
sudo cp /tmp/archipelago.service /etc/systemd/system/archipelago.service
sudo systemctl daemon-reload
@@ -412,12 +411,12 @@ if [ "$BOTH" = true ]; then
' 2>/dev/null || true
fi
# Deploy udev rule for mesh radio to 198
# Deploy udev rule for mesh radio
UDEV_RULE="$PROJECT_DIR/image-recipe/configs/99-mesh-radio.rules"
if [ -f "$UDEV_RULE" ]; then
echo " Syncing udev rule to 198..."
scp $SSH_OPTS "$UDEV_RULE" "$TARGET_198:/tmp/99-mesh-radio.rules" 2>/dev/null || true
ssh $SSH_OPTS "$TARGET_198" '
echo " Syncing udev rule to .$SEC_LABEL..."
scp $SSH_OPTS "$UDEV_RULE" "$SEC_TARGET:/tmp/99-mesh-radio.rules" 2>/dev/null || true
ssh $SSH_OPTS "$SEC_TARGET" '
if ! diff -q /tmp/99-mesh-radio.rules /etc/udev/rules.d/99-mesh-radio.rules >/dev/null 2>&1; then
sudo cp /tmp/99-mesh-radio.rules /etc/udev/rules.d/99-mesh-radio.rules
sudo udevadm control --reload-rules
@@ -430,8 +429,8 @@ if [ "$BOTH" = true ]; then
' 2>/dev/null || true
fi
# Dev mode + FileBrowser on 198
ssh $SSH_OPTS "$TARGET_198" '
# Dev mode + FileBrowser
ssh $SSH_OPTS "$SEC_TARGET" '
# Dev mode
if ! grep -q "ARCHIPELAGO_DEV_MODE=true" /etc/systemd/system/archipelago.service.d/override.conf 2>/dev/null; then
sudo mkdir -p /etc/systemd/system/archipelago.service.d
@@ -454,9 +453,10 @@ if [ "$BOTH" = true ]; then
fi
' 2>/dev/null || true
# Write deploy manifest to .198
# Write deploy manifest
local DEPLOY_TS
DEPLOY_TS=$(date -u +%Y-%m-%dT%H:%M:%SZ)
ssh $SSH_OPTS "$TARGET_198" "sudo tee /opt/archipelago/deploy-manifest.json > /dev/null" << MANIFEST_198_EOF
ssh $SSH_OPTS "$SEC_TARGET" "sudo tee /opt/archipelago/deploy-manifest.json > /dev/null" <<-MANIFEST_SEC_EOF
{
"commit": "$DEPLOY_COMMIT_FULL",
"commit_short": "$DEPLOY_COMMIT",
@@ -464,30 +464,50 @@ if [ "$BOTH" = true ]; then
"dirty": $DEPLOY_DIRTY,
"deployed_at": "$DEPLOY_TS",
"deployed_from": "$(hostname)",
"target": "$TARGET_198"
"target": "$SEC_TARGET"
}
MANIFEST_198_EOF
MANIFEST_SEC_EOF
ssh $SSH_OPTS "$TARGET_198" "sudo systemctl start archipelago && sudo systemctl restart nginx"
ssh $SSH_OPTS "$SEC_TARGET" "sudo systemctl start archipelago && sudo systemctl restart nginx"
# Run container doctor on .198
echo " Running container doctor on .198..."
"$SCRIPT_DIR/container-doctor.sh" "$TARGET_198" 2>&1 | sed 's/^/ /' || true
# Run container doctor
echo " Running container doctor on .$SEC_LABEL..."
"$SCRIPT_DIR/container-doctor.sh" "$SEC_TARGET" 2>&1 | sed 's/^/ /' || true
# Post-deploy health check on .198
echo " Checking .198 health..."
HEALTH_198="fail"
# Post-deploy health check
echo " Checking .$SEC_LABEL health..."
local HEALTH="fail"
for i in $(seq 1 12); do
sleep 5
HEALTH_198=$(curl -s --max-time 5 "http://192.168.1.198/health" 2>/dev/null || { echo "WARNING: Health check failed for 192.168.1.198" >&2; echo ""; })
if [ "$HEALTH_198" = "OK" ]; then
echo "192.168.1.198 deployed (health OK after $((i * 5))s)"
HEALTH=$(curl -s --max-time 5 "http://$SEC_IP/health" 2>/dev/null || { echo "WARNING: Health check failed for $SEC_IP" >&2; echo ""; })
if [ "$HEALTH" = "OK" ]; then
echo "$SEC_IP deployed (health OK after $((i * 5))s)"
break
fi
done
if [ "$HEALTH_198" != "OK" ]; then
echo " ⚠️ 192.168.1.198 deployed but health check failed after 60s"
if [ "$HEALTH" != "OK" ]; then
echo " ⚠️ $SEC_IP deployed but health check failed after 60s"
fi
}
# When --both: deploy to 228 first, then copy to 198 + 253
if [ "$BOTH" = true ]; then
echo "Deploying to all LAN servers (228, then 198 + 253)..."
# Release lock so the recursive --live call can acquire it
rm -rf "$LOCK_DIR" 2>/dev/null; trap - EXIT
"$0" --live
echo ""
# Fetch built binary from .228 (shared by all secondary nodes)
if ! scp $SSH_OPTS "archipelago@192.168.1.228:$TARGET_DIR/core/target/release/archipelago" /tmp/archipelago-both 2>/dev/null; then
echo " ERROR: Failed to copy binary from .228 — is the build available?"
exit 1
fi
# Deploy to each secondary node
deploy_secondary "archipelago@192.168.1.198" "198"
deploy_secondary "archipelago@192.168.1.253" "253"
rm -f /tmp/archipelago-both
exit 0
fi
@@ -547,6 +567,10 @@ if [ "$LIVE" = true ]; then
elif ssh $SSH_OPTS "$TARGET_HOST" "[ -f $TARGET_DIR/core/target/release/archipelago ]" 2>/dev/null; then
progress "Deploying backend binary"
ssh $SSH_OPTS "$TARGET_HOST" 'sudo systemctl stop archipelago --no-block 2>/dev/null; sleep 2; sudo kill -9 $(pgrep -x archipelago) 2>/dev/null; sleep 1; true'
if [ "$RESET_MESH" = true ]; then
echo " Wiping mesh cache (peers/messages/sessions) per --reset-mesh"
ssh $SSH_OPTS "$TARGET_HOST" 'sudo rm -f /var/lib/archipelago/messages.json /var/lib/archipelago/sessions.json /var/lib/archipelago/mesh-outbox.json 2>/dev/null; true'
fi
ssh $SSH_OPTS "$TARGET_HOST" "sudo cp $TARGET_DIR/core/target/release/archipelago /usr/local/bin/"
fi