From 0b49f28a803866858851db78df2b169ee6e4e804 Mon Sep 17 00:00:00 2001 From: ZGaetano Date: Wed, 20 May 2026 13:49:05 -0400 Subject: [PATCH] =?UTF-8?q?feat(deploy):=20onboard-node.sh=20=E2=80=94=20o?= =?UTF-8?q?ne-command=20cluster=20node=20provisioning?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- deploy/onboard-node.sh | 161 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 161 insertions(+) create mode 100644 deploy/onboard-node.sh diff --git a/deploy/onboard-node.sh b/deploy/onboard-node.sh new file mode 100644 index 0000000..0d82ffe --- /dev/null +++ b/deploy/onboard-node.sh @@ -0,0 +1,161 @@ +#!/usr/bin/env bash +# ============================================================================= +# Wild Dragon MAM — Cluster Node Onboarding +# ============================================================================= +# +# Provisions a Linux machine as a cluster worker node in one command. +# +# Quick-start (pipe to bash): +# export MAM_API_URL=http://10.0.0.25:47432 +# export NODE_TOKEN=wd_xxxx # create via Z-AMPP → Admin → Tokens +# curl -sL https://forge.wilddragon.net/zgaetano/wild-dragon/raw/branch/main/deploy/onboard-node.sh | bash +# +# Or run from a cloned repo: +# MAM_API_URL=http://10.0.0.25:47432 NODE_TOKEN=wd_xxxx ./deploy/onboard-node.sh +# +# Environment variables: +# MAM_API_URL REQUIRED Primary MAM API base URL +# NODE_TOKEN API bearer token (required if AUTH_ENABLED=true) +# NODE_ROLE Role tag reported to the cluster (default: worker) +# AGENT_PORT Host port for the node agent (default: 7436) +# INSTALL_DIR Where to clone/find the repo (default: /opt/wild-dragon) +# PROFILES Extra compose profiles, space-separated e.g. "worker" +# REPO_URL Override the Forgejo clone URL +# ============================================================================= + +set -euo pipefail + +# ── Config ─────────────────────────────────────────────────────────────────── +REPO_URL="${REPO_URL:-https://forge.wilddragon.net/zgaetano/wild-dragon.git}" +INSTALL_DIR="${INSTALL_DIR:-/opt/wild-dragon}" +MAM_API_URL="${MAM_API_URL:-}" +NODE_TOKEN="${NODE_TOKEN:-}" +NODE_ROLE="${NODE_ROLE:-worker}" +AGENT_PORT="${AGENT_PORT:-7436}" +PROFILES="${PROFILES:-}" +PROJECT_NAME="wild-dragon-worker" + +# ── Colours ────────────────────────────────────────────────────────────────── +RED='\033[0;31m'; YEL='\033[1;33m'; GRN='\033[0;32m'; CYN='\033[0;36m' +BLD='\033[1m'; NC='\033[0m' +log() { echo -e "${GRN} ✓${NC} $*"; } +info() { echo -e "${CYN} ▶${NC} $*"; } +warn() { echo -e "${YEL} ⚠${NC} $*"; } +header() { echo -e "\n${BLD}${CYN}── $* ──────────────────────────────────────${NC}"; } +die() { echo -e "${RED} ✗ ERROR:${NC} $*" >&2; exit 1; } + +# ── Preflight ──────────────────────────────────────────────────────────────── +echo -e "\n${BLD}${CYN}Wild Dragon MAM — Cluster Node Onboarding${NC}\n" + +[[ -z "$MAM_API_URL" ]] && die "MAM_API_URL is required.\n\n Example:\n export MAM_API_URL=http://10.0.0.25:47432\n export NODE_TOKEN=wd_xxxx\n ./deploy/onboard-node.sh" + +info "Primary API : $MAM_API_URL" +info "Role : $NODE_ROLE" +info "Agent port : $AGENT_PORT" +info "Install dir : $INSTALL_DIR" +[[ -n "$PROFILES" ]] && info "Profiles : $PROFILES" + +if [[ -z "$NODE_TOKEN" ]]; then + warn "NODE_TOKEN is not set." + warn "If AUTH_ENABLED=true on the primary, heartbeats will be rejected." + warn "Create a token: Z-AMPP web UI → Admin → Tokens → New Token" +fi + +# ── Step 1: Docker ─────────────────────────────────────────────────────────── +header "1/4 Docker" + +if ! command -v docker &>/dev/null; then + warn "Docker not found — installing via get.docker.com" + curl -fsSL https://get.docker.com | bash + systemctl enable --now docker 2>/dev/null || true + # Add current user to docker group (effective on next login) + usermod -aG docker "${SUDO_USER:-$USER}" 2>/dev/null || true + log "Docker installed" +else + log "Docker $(docker --version | grep -oP '\d+\.\d+\.\d+' | head -1) already installed" +fi + +if ! docker info &>/dev/null; then + die "Docker daemon not accessible.\n Try: sudo systemctl start docker\n Or add your user to the docker group and re-login." +fi + +# ── Step 2: Repository ─────────────────────────────────────────────────────── +header "2/4 Repository" + +if [[ -d "$INSTALL_DIR/.git" ]]; then + info "Updating $INSTALL_DIR" + git -C "$INSTALL_DIR" pull --ff-only + log "Repository up to date ($(git -C "$INSTALL_DIR" rev-parse --short HEAD))" +else + info "Cloning $REPO_URL → $INSTALL_DIR" + mkdir -p "$(dirname "$INSTALL_DIR")" + git clone "$REPO_URL" "$INSTALL_DIR" + log "Repository cloned" +fi + +# ── Step 3: Environment ────────────────────────────────────────────────────── +header "3/4 Configuration" + +ENV_FILE="$INSTALL_DIR/.env.worker" +info "Writing $ENV_FILE" + +{ + echo "# Wild Dragon worker node — generated $(date -u +%Y-%m-%dT%H:%M:%SZ) by onboard-node.sh" + echo "MAM_API_URL=$MAM_API_URL" + echo "NODE_TOKEN=$NODE_TOKEN" + echo "NODE_ROLE=$NODE_ROLE" + echo "AGENT_PORT=$AGENT_PORT" + echo "HEARTBEAT_MS=30000" + for v in REDIS_URL DATABASE_URL S3_ENDPOINT S3_BUCKET S3_ACCESS_KEY S3_SECRET_KEY S3_REGION; do + val="${!v:-}" + [[ -n "$val" ]] && echo "$v=$val" + done +} > "$ENV_FILE" + +log "Env file written" + +# ── Step 4: Start services ─────────────────────────────────────────────────── +header "4/4 Starting services" + +COMPOSE="docker compose -f $INSTALL_DIR/docker-compose.worker.yml --env-file $ENV_FILE --project-name $PROJECT_NAME" + +PROFILE_FLAGS="" +for p in $PROFILES; do + PROFILE_FLAGS="$PROFILE_FLAGS --profile $p" +done + +info "Building images (this may take a minute on first run)…" +$COMPOSE build + +info "Starting containers…" +# shellcheck disable=SC2086 +$COMPOSE $PROFILE_FLAGS up -d + +# ── Verify ─────────────────────────────────────────────────────────────────── +echo "" +info "Waiting 6 seconds for agent to initialise…" +sleep 6 + +HEALTH_URL="http://localhost:$AGENT_PORT/health" +if curl -sf "$HEALTH_URL" > /dev/null 2>&1; then + log "Node agent healthy at $HEALTH_URL" +else + warn "Could not reach $HEALTH_URL — check logs:" + warn " $COMPOSE logs node-agent" +fi + +# ── Done ───────────────────────────────────────────────────────────────────── +echo "" +echo -e "${BLD}${GRN}Onboarding complete!${NC}" +echo "" +echo -e " Node agent ${BLD}:$AGENT_PORT${NC} (heartbeating every 30s)" +echo -e " Primary API ${BLD}$MAM_API_URL${NC}" +echo -e " Role ${BLD}$NODE_ROLE${NC}" +echo "" +echo -e " ${CYN}Useful commands:${NC}" +echo -e " Status : $COMPOSE ps" +echo -e " Logs : $COMPOSE logs -f" +echo -e " Stop : $COMPOSE down" +echo -e " Update : git -C $INSTALL_DIR pull && $COMPOSE build && $COMPOSE up -d" +echo "" +echo -e " Open the Z-AMPP web UI → ${BLD}Admin → Cluster${NC} to see this node."