426e9eeabd
Benchmark Bridge Tests / benchmark (bunx @biomejs/biome check packages/lifeops-bench/src, benchmark-lint) (push) Waiting to run
Benchmark Bridge Tests / benchmark (bunx vitest run --config packages/lifeops-bench/vitest.config.ts --root packages/lifeops-bench --passWithNoTests, benchmark-tests) (push) Waiting to run
Build Agent Image / build-and-push (push) Waiting to run
Chat shell gestures / Chat shell gesture + parity e2e (push) Waiting to run
ci / test (push) Waiting to run
ci / lint-and-format (push) Waiting to run
ci / build (push) Waiting to run
ci / dev-startup (push) Waiting to run
Cloud Gateway Discord / Test (push) Waiting to run
Cloud Gateway Webhook / Test (push) Waiting to run
Cloud Tests / lint-and-types (push) Waiting to run
Cloud Tests / unit-tests (push) Waiting to run
Cloud Tests / integration-tests (push) Waiting to run
Cloud Tests / e2e-tests (push) Blocked by required conditions
CodeQL Advanced / Analyze (javascript-typescript) (push) Waiting to run
Deploy Apps Worker (Product 2) / Determine environment (push) Waiting to run
Deploy Apps Worker (Product 2) / Deploy apps worker to apps-control host (${{ needs.determine-env.outputs.environment }}) (push) Blocked by required conditions
Deploy Eliza Provisioning Worker / Determine environment (push) Waiting to run
Deploy Eliza Provisioning Worker / Deploy worker to Hetzner host (${{ needs.determine-env.outputs.environment }} @ ${{ needs.determine-env.outputs.deployment_sha }}) (push) Blocked by required conditions
Dev Smoke / Classify changed paths (push) Waiting to run
Dev Smoke / bun run dev onboarding chat (push) Blocked by required conditions
Dev Smoke / Vite HMR dependency-level smoke (push) Blocked by required conditions
Electrobun Submodule Guard / electrobun gitlink is fetchable (push) Waiting to run
gitleaks / gitleaks (push) Waiting to run
Markdown Links / Relative Markdown Links (push) Waiting to run
Publish @elizaos/example-code / check_npm (push) Waiting to run
Publish @elizaos/example-code / publish_npm (push) Blocked by required conditions
Publish @elizaos/plugin-elizacloud / verify_version (push) Waiting to run
Publish @elizaos/plugin-elizacloud / publish_npm (push) Blocked by required conditions
Quality (Extended) / Homepage Build (PR smoke) (push) Waiting to run
Quality (Extended) / Comment-only diff guard (push) Waiting to run
Quality (Extended) / Format + Type Safety Ratchet (push) Waiting to run
Quality (Extended) / Develop Gate (secret scan + UI determinism) (push) Waiting to run
Quality (Extended) / Develop Gate (lint) (push) Waiting to run
Sandbox Live Smoke / Sandbox live smoke (push) Waiting to run
Snap Build & Test / Build Snap (amd64) (push) Waiting to run
Snap Build & Test / Build Snap (arm64) (push) Waiting to run
supply-chain / sbom (push) Waiting to run
supply-chain / vulnerability-scan (push) Waiting to run
Build, Push & Deploy to Phala Cloud / build-and-push (push) Waiting to run
Test Packaging / Validate Packaging Configs (push) Waiting to run
Test Packaging / PyPI on Python ${{ matrix.python }} (push) Waiting to run
Test Packaging / Pack & Test JS Tarballs (push) Waiting to run
Test Packaging / elizaos CLI global-install smoke (node + bun) (push) Waiting to run
UI Fixture E2E / ui-fixture-e2e (push) Waiting to run
UI Fixture E2E / fixture-e2e (push) Waiting to run
UI Story Gate / story-gate (push) Waiting to run
vault-ci / test (macos-latest) (push) Waiting to run
vault-ci / test (ubuntu-latest) (push) Waiting to run
vault-ci / test (windows-latest) (push) Waiting to run
vault-ci / app-core wiring tests (push) Waiting to run
verify-patches / verify patches/CHECKSUMS.sha256 (push) Waiting to run
Voice Benchmark Smoke / voice-emotion fixture smoke (push) Waiting to run
Voice Benchmark Smoke / voiceagentbench fixture smoke (push) Waiting to run
Voice Benchmark Smoke / voicebench-quality unit smoke (push) Waiting to run
Voice Benchmark Smoke / voicebench TypeScript unit (no audio) (push) Waiting to run
Voice Benchmark Smoke / voice bench smoke summary (push) Blocked by required conditions
Windows CI / windows ([bun run --cwd packages/app-core test bun run --cwd packages/elizaos test bun run --cwd packages/cloud/shared test], app-and-cli) (push) Waiting to run
Windows CI / windows ([bun run --cwd packages/scenario-runner test bun run --cwd packages/vault test bun run --cwd packages/security test bun run --cwd plugins/plugin-coding-tools test], framework-packages) (push) Waiting to run
Windows CI / windows ([bun run --cwd plugins/plugin-elizacloud test bun run --cwd plugins/plugin-discord test bun run --cwd plugins/plugin-anthropic test bun run --cwd plugins/plugin-openai test bun run --cwd plugins/plugin-app-control test bun run --cwd plugins/pl… (push) Waiting to run
Windows CI / windows ([node packages/scripts/run-turbo.mjs run build --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/agent --concurrency=4 node packages/scripts/run-bash-linux-only.mjs scripts/verify-riscv64-buildpaths.sh node packages/scripts/run… (push) Waiting to run
Windows CI / windows ([node packages/scripts/run-turbo.mjs run typecheck --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/cloud-shared --concurrency=4 bun run --cwd packages/core test bun run --cwd packages/shared test], core-runtime, 75) (push) Waiting to run
Test Packaging / Build & Test PyPI Package (push) Waiting to run
Voice Workbench / headless workbench (mocked backends) (push) Has been cancelled
Voice Workbench / real acoustic lane (nightly, provisioned only) (push) Has been cancelled
280 lines
10 KiB
Bash
280 lines
10 KiB
Bash
#!/usr/bin/env bash
|
|
# deploy-to-nodes.sh — Deploy eliza/agent image to Docker nodes
|
|
#
|
|
# Usage:
|
|
# ./deploy/deploy-to-nodes.sh [OPTIONS]
|
|
#
|
|
# Options:
|
|
# --image TAG Image to deploy (default: eliza/agent:latest)
|
|
# --nodes LIST Comma-separated node list: name:ip (overrides defaults)
|
|
# --node NAME Deploy to a single node by name (agent-node-1 or nyx-node)
|
|
# --restart Restart all eliza containers after loading image
|
|
# --rolling Rolling restart (one container at a time, wait for healthy)
|
|
# --snapshot Create container snapshots before restarting (default when --restart)
|
|
# --no-snapshot Skip snapshot before restart
|
|
# --list Just list running containers, don't deploy
|
|
# --status Show image and container status on all nodes
|
|
# --dry-run Show what would be done
|
|
# -h, --help Show this help
|
|
#
|
|
# Examples:
|
|
# ./deploy/deploy-to-nodes.sh --status # Check current state
|
|
# ./deploy/deploy-to-nodes.sh --list # List running containers
|
|
# ./deploy/deploy-to-nodes.sh # Load image to all nodes
|
|
# ./deploy/deploy-to-nodes.sh --restart # Load + restart all containers
|
|
# ./deploy/deploy-to-nodes.sh --restart --rolling # Rolling restart
|
|
# ./deploy/deploy-to-nodes.sh --node agent-node-1 --list # List on one node
|
|
|
|
set -euo pipefail
|
|
|
|
# ── Config ────────────────────────────────────────────────────────────────────
|
|
DEFAULT_IMAGE="eliza/agent:latest"
|
|
SSH_KEY="${SSH_KEY:-$HOME/.ssh/clawdnet_nodes}"
|
|
SSH_USER="${SSH_USER:-root}"
|
|
SSH_OPTS="-o StrictHostKeyChecking=no -o ConnectTimeout=15"
|
|
|
|
declare -A NODE_MAP=(
|
|
["agent-node-1"]="37.27.190.196"
|
|
["nyx-node"]="89.167.49.4"
|
|
)
|
|
ALL_NODES=("agent-node-1" "nyx-node")
|
|
|
|
# ── Defaults ──────────────────────────────────────────────────────────────────
|
|
IMAGE="$DEFAULT_IMAGE"
|
|
DO_RESTART=false
|
|
DO_ROLLING=false
|
|
DO_SNAPSHOT=true # default: snapshot before restart
|
|
DO_LIST=false
|
|
DO_STATUS=false
|
|
DRY_RUN=false
|
|
SELECTED_NODES=()
|
|
|
|
# ── Colors ────────────────────────────────────────────────────────────────────
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
BLUE='\033[0;34m'
|
|
CYAN='\033[0;36m'
|
|
NC='\033[0m'
|
|
|
|
log() { echo -e "${BLUE}[deploy]${NC} $*"; }
|
|
ok() { echo -e "${GREEN}[✓]${NC} $*"; }
|
|
warn() { echo -e "${YELLOW}[!]${NC} $*"; }
|
|
err() { echo -e "${RED}[✗]${NC} $*" >&2; }
|
|
hdr() { echo -e "\n${CYAN}━━━ $* ━━━${NC}"; }
|
|
|
|
ssh_cmd() {
|
|
local ip="$1"; shift
|
|
ssh $SSH_OPTS -i "$SSH_KEY" "${SSH_USER}@${ip}" "$@"
|
|
}
|
|
|
|
list_remote_images_cmd() {
|
|
cat <<'EOF'
|
|
docker images --format 'table {{.Repository}}:{{.Tag}}\t{{.Size}}\t{{.ID}}\t{{.CreatedSince}}' | grep -E '^(eliza/agent|ghcr\.io/eliza-ai/agent|ghcr\.io/eliza-ai/eliza/agent):' || true
|
|
EOF
|
|
}
|
|
|
|
list_remote_containers_cmd() {
|
|
cat <<'EOF'
|
|
{
|
|
printf 'NAMES|IMAGE|STATUS|PORTS\n'
|
|
docker ps --format '{{.Names}}|{{.Image}}|{{.Status}}|{{.Ports}}'
|
|
} | awk -F'|' 'NR == 1 || $2 ~ /^(eliza\/agent|ghcr\.io\/eliza-ai\/agent|ghcr\.io\/eliza-ai\/eliza\/agent)(:|@)/'
|
|
EOF
|
|
}
|
|
|
|
list_remote_container_names_cmd() {
|
|
cat <<'EOF'
|
|
docker ps --format '{{.Names}}|{{.Image}}' | awk -F'|' '$2 ~ /^(eliza\/agent|ghcr\.io\/eliza-ai\/agent|ghcr\.io\/eliza-ai\/eliza\/agent)(:|@)/ { print $1 }'
|
|
EOF
|
|
}
|
|
|
|
# ── Parse Args ────────────────────────────────────────────────────────────────
|
|
while [[ $# -gt 0 ]]; do
|
|
case $1 in
|
|
--image) IMAGE="$2"; shift 2 ;;
|
|
--node)
|
|
if [[ -z "${NODE_MAP[$2]+x}" ]]; then
|
|
err "Unknown node: $2 (known: ${ALL_NODES[*]})"
|
|
exit 1
|
|
fi
|
|
SELECTED_NODES+=("$2"); shift 2 ;;
|
|
--restart) DO_RESTART=true; shift ;;
|
|
--rolling) DO_ROLLING=true; DO_RESTART=true; shift ;;
|
|
--snapshot) DO_SNAPSHOT=true; shift ;;
|
|
--no-snapshot) DO_SNAPSHOT=false; shift ;;
|
|
--list) DO_LIST=true; shift ;;
|
|
--status) DO_STATUS=true; shift ;;
|
|
--dry-run) DRY_RUN=true; shift ;;
|
|
-h|--help)
|
|
head -28 "$0" | grep '^#' | sed 's/^# \?//'
|
|
exit 0
|
|
;;
|
|
*)
|
|
err "Unknown option: $1"
|
|
exit 1
|
|
;;
|
|
esac
|
|
done
|
|
|
|
# Default to all nodes if none selected
|
|
if [[ ${#SELECTED_NODES[@]} -eq 0 ]]; then
|
|
SELECTED_NODES=("${ALL_NODES[@]}")
|
|
fi
|
|
|
|
# ── Status mode ───────────────────────────────────────────────────────────────
|
|
if $DO_STATUS; then
|
|
for node in "${SELECTED_NODES[@]}"; do
|
|
ip="${NODE_MAP[$node]}"
|
|
hdr "$node ($ip)"
|
|
|
|
echo -e "${YELLOW}Images:${NC}"
|
|
ssh_cmd "$ip" "$(list_remote_images_cmd)" 2>/dev/null || warn "Failed to connect"
|
|
|
|
echo -e "\n${YELLOW}Running containers:${NC}"
|
|
ssh_cmd "$ip" "$(list_remote_containers_cmd)" 2>/dev/null || true
|
|
done
|
|
exit 0
|
|
fi
|
|
|
|
# ── List mode ─────────────────────────────────────────────────────────────────
|
|
if $DO_LIST; then
|
|
for node in "${SELECTED_NODES[@]}"; do
|
|
ip="${NODE_MAP[$node]}"
|
|
hdr "$node ($ip)"
|
|
ssh_cmd "$ip" "$(list_remote_containers_cmd)" 2>/dev/null || warn "Failed to connect"
|
|
done
|
|
exit 0
|
|
fi
|
|
|
|
# ── Deploy image ──────────────────────────────────────────────────────────────
|
|
log "Image: ${YELLOW}${IMAGE}${NC}"
|
|
log "Nodes: ${YELLOW}${SELECTED_NODES[*]}${NC}"
|
|
|
|
if $DRY_RUN; then
|
|
warn "DRY RUN mode"
|
|
fi
|
|
|
|
# Check image exists locally
|
|
if ! docker image inspect "$IMAGE" &>/dev/null; then
|
|
err "Image $IMAGE not found locally. Build it first:"
|
|
echo " ./scripts/build-image.sh --tag latest"
|
|
exit 1
|
|
fi
|
|
|
|
# Save image to temp file
|
|
TMPFILE=$(mktemp /tmp/eliza-deploy-XXXXXX.tar)
|
|
trap "rm -f $TMPFILE" EXIT
|
|
|
|
if ! $DRY_RUN; then
|
|
log "Saving image to tarball..."
|
|
docker save "$IMAGE" > "$TMPFILE"
|
|
TAR_SIZE=$(du -h "$TMPFILE" | cut -f1)
|
|
ok "Saved ($TAR_SIZE)"
|
|
fi
|
|
|
|
# Push to each node
|
|
for node in "${SELECTED_NODES[@]}"; do
|
|
ip="${NODE_MAP[$node]}"
|
|
hdr "$node ($ip)"
|
|
|
|
if $DRY_RUN; then
|
|
echo " Would load $IMAGE"
|
|
if $DO_RESTART; then echo " Would restart eliza containers"; fi
|
|
continue
|
|
fi
|
|
|
|
# Load image
|
|
log "Loading image..."
|
|
LOAD_START=$(date +%s)
|
|
ssh_cmd "$ip" "docker load" < "$TMPFILE"
|
|
LOAD_END=$(date +%s)
|
|
ok "Loaded in $((LOAD_END - LOAD_START))s"
|
|
|
|
# Get running eliza containers
|
|
CONTAINERS=$(ssh_cmd "$ip" "$(list_remote_container_names_cmd)" 2>/dev/null || true)
|
|
|
|
if [[ -z "$CONTAINERS" ]]; then
|
|
warn "No running eliza containers on $node"
|
|
continue
|
|
fi
|
|
|
|
CONTAINER_COUNT=$(echo "$CONTAINERS" | wc -l)
|
|
log "Found ${YELLOW}${CONTAINER_COUNT}${NC} running container(s)"
|
|
echo "$CONTAINERS" | while read -r c; do echo " - $c"; done
|
|
|
|
# Restart containers if requested
|
|
if $DO_RESTART; then
|
|
echo "$CONTAINERS" | while read -r container; do
|
|
[[ -z "$container" ]] && continue
|
|
|
|
log "Restarting ${YELLOW}${container}${NC}..."
|
|
|
|
# Snapshot before restart
|
|
if $DO_SNAPSHOT; then
|
|
SNAP_NAME="${container}-pre-deploy-$(date +%Y%m%d-%H%M%S)"
|
|
log "Creating snapshot: $SNAP_NAME"
|
|
ssh_cmd "$ip" "docker commit $container $SNAP_NAME" 2>/dev/null && \
|
|
ok "Snapshot created" || \
|
|
warn "Snapshot failed (continuing anyway)"
|
|
fi
|
|
|
|
# Get container config for recreation
|
|
CONTAINER_ENV=$(ssh_cmd "$ip" "docker inspect --format '{{range .Config.Env}}--env {{.}} {{end}}' $container" 2>/dev/null || true)
|
|
CONTAINER_PORTS=$(ssh_cmd "$ip" "docker inspect --format '{{range \$p, \$conf := .NetworkSettings.Ports}}{{range \$conf}}-p {{.HostIp}}:{{.HostPort}}:{{index (split \$p \"/\") 0}} {{end}}{{end}}' $container" 2>/dev/null || true)
|
|
CONTAINER_VOLS=$(ssh_cmd "$ip" "docker inspect --format '{{range .Mounts}}-v {{.Source}}:{{.Destination}} {{end}}' $container" 2>/dev/null || true)
|
|
CONTAINER_NET=$(ssh_cmd "$ip" "docker inspect --format '{{range \$net, \$conf := .NetworkSettings.Networks}}--network {{\$net}} {{end}}' $container" 2>/dev/null || true)
|
|
CONTAINER_RESTART=$(ssh_cmd "$ip" "docker inspect --format '{{.HostConfig.RestartPolicy.Name}}' $container" 2>/dev/null || echo "unless-stopped")
|
|
|
|
# Stop old container
|
|
log "Stopping $container..."
|
|
ssh_cmd "$ip" "docker stop -t 30 $container" 2>/dev/null
|
|
ssh_cmd "$ip" "docker rm $container" 2>/dev/null
|
|
|
|
# Start new container
|
|
log "Starting $container with new image..."
|
|
ssh_cmd "$ip" "docker run -d \
|
|
--name $container \
|
|
--restart=$CONTAINER_RESTART \
|
|
$CONTAINER_ENV \
|
|
$CONTAINER_PORTS \
|
|
$CONTAINER_VOLS \
|
|
$CONTAINER_NET \
|
|
$IMAGE" 2>/dev/null
|
|
|
|
# Wait for healthy if rolling
|
|
if $DO_ROLLING; then
|
|
log "Waiting for $container to become healthy..."
|
|
for i in $(seq 1 60); do
|
|
STATUS=$(ssh_cmd "$ip" "docker inspect --format '{{.State.Health.Status}}' $container" 2>/dev/null || echo "unknown")
|
|
if [[ "$STATUS" == "healthy" ]]; then
|
|
ok "$container is healthy"
|
|
break
|
|
fi
|
|
if [[ "$STATUS" == "unhealthy" ]]; then
|
|
err "$container is unhealthy! Check logs:"
|
|
echo " ssh -i $SSH_KEY ${SSH_USER}@${ip} 'docker logs --tail 50 $container'"
|
|
break
|
|
fi
|
|
sleep 5
|
|
done
|
|
if [[ "$STATUS" != "healthy" && "$STATUS" != "unhealthy" ]]; then
|
|
warn "Timed out waiting for $container health check (status: $STATUS)"
|
|
fi
|
|
fi
|
|
|
|
ok "$container restarted"
|
|
done
|
|
fi
|
|
done
|
|
|
|
echo ""
|
|
ok "Deploy complete!"
|
|
if ! $DO_RESTART; then
|
|
echo ""
|
|
echo " Image loaded but containers not restarted."
|
|
echo " To restart containers:"
|
|
echo " $0 --restart"
|
|
echo " $0 --restart --rolling # one at a time"
|
|
fi
|