#!/usr/bin/env bash # Publish one immutable Hub release (tsc + admin-web SPA) to the managed host, # then repoint every Silo in the chosen fleet to that release and restart it. # # Fleet is selected by the middle DNS label of each Silo's HUB_PUBLIC_BASE_URL: # # prod → https://.educraft.paradigm-edu.net # dev → https://.educraft-dev.paradigm-edu.net # # Example tenant (prod): # https://para-26071100.educraft.paradigm-edu.net/auth/feishu/para-26071100 # # Configure (CI secrets / env): # CPH_FLEET required: dev | prod # PLATFORM_DEPLOY_HOST default 39.107.254.4 # PLATFORM_DEPLOY_USER default root # PLATFORM_DEPLOY_SSH_KEY required (path to private key) # PLATFORM_DEPLOY_PORT default 22 # PLATFORM_DEPLOY_BASE default /srv/curriculum-project-hub # PLATFORM_DEPLOY_RELEASE default git HEAD (must be safe for paths) # ALLOW_EMPTY_FLEET optional: if 1, succeed when no silo matches # # This is a code-roll for existing silos. It does not create databases, domains, # or Feishu apps — use new_silo.sh / apply_new_silo.sh for that. set -euo pipefail FLEET="${CPH_FLEET:?CPH_FLEET required (dev|prod)}" case "$FLEET" in dev) FLEET_DOMAIN_SUFFIX="educraft-dev.paradigm-edu.net" ;; prod) FLEET_DOMAIN_SUFFIX="educraft.paradigm-edu.net" ;; *) echo "CPH_FLEET must be dev or prod, got: $FLEET" >&2 exit 1 ;; esac HOST="${PLATFORM_DEPLOY_HOST:-39.107.254.4}" DEPLOY_USER="${PLATFORM_DEPLOY_USER:-root}" PORT="${PLATFORM_DEPLOY_PORT:-22}" SSH_KEY="${PLATFORM_DEPLOY_SSH_KEY:?PLATFORM_DEPLOY_SSH_KEY required}" BASE="${PLATFORM_DEPLOY_BASE:-/srv/curriculum-project-hub}" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" RELEASE_ID="${PLATFORM_DEPLOY_RELEASE:-$(git -C "$REPO_ROOT" rev-parse --verify HEAD)}" [[ "$RELEASE_ID" =~ ^[A-Za-z0-9._-]+$ ]] || { echo "invalid PLATFORM_DEPLOY_RELEASE: $RELEASE_ID" >&2 exit 1 } ALLOW_EMPTY_FLEET="${ALLOW_EMPTY_FLEET:-0}" HUB_DIR="$BASE/releases/$RELEASE_ID/hub" RELEASE_DIR="$BASE/releases/$RELEASE_ID" SSH_OPTS=(-i "$SSH_KEY" -p "$PORT" -o BatchMode=yes -o StrictHostKeyChecking=accept-new) echo "[fleet] fleet=$FLEET domain=*.$FLEET_DOMAIN_SUFFIX host=$DEPLOY_USER@$HOST release=$RELEASE_ID" # --- 1. Publish immutable release (shared; flock so parallel fleet jobs do not race) --- release_ready=false if ssh "${SSH_OPTS[@]}" "$DEPLOY_USER@$HOST" "test -f '$RELEASE_DIR/.complete'"; then release_ready=true echo "[fleet] release already complete: $RELEASE_DIR" fi if [ "$release_ready" = false ]; then # Drop a prior incomplete tree for this release id (failed CI leave leftovers). ssh "${SSH_OPTS[@]}" "$DEPLOY_USER@$HOST" bash -s <&2 continue fi # Prefer unit file ceilings (MemoryMax=16G) over systemctl's byte form. memory_max="$(sed -n 's/^MemoryMax=//p' "/etc/systemd/system/$unit" | tail -n1)" cpu_quota="$(sed -n 's/^CPUQuota=//p' "/etc/systemd/system/$unit" | tail -n1)" tasks_max="$(sed -n 's/^TasksMax=//p' "/etc/systemd/system/$unit" | tail -n1)" if [ -z "$port" ] || [ -z "$workspace" ] || [ -z "$memory_max" ] || [ -z "$cpu_quota" ] || [ -z "$tasks_max" ]; then echo "[fleet] error: $instance_id missing PORT/workspace/ceilings (port=$port workspace=$workspace memory=$memory_max cpu=$cpu_quota tasks=$tasks_max)" >&2 exit 1 fi echo "[fleet] roll $instance_id → $HUB_DIR ($public_url port=$port)" BASE="$BASE" \ HUB_DIR="$HUB_DIR" \ INSTANCE_ID="$instance_id" \ WORKSPACE_ROOT="$workspace" \ PORT="$port" \ MEMORY_MAX="$memory_max" \ CPU_QUOTA="$cpu_quota" \ TASKS_MAX="$tasks_max" \ bash "$HUB_DIR/deploy/install_service.sh" systemctl restart "$unit" systemctl is-active --quiet "$unit" health="http://127.0.0.1:${port}/api/healthz" ok=false for _ in $(seq 1 45); do if curl --fail --silent "$health" >/dev/null 2>&1; then ok=true break fi sleep 1 done if [ "$ok" != true ]; then curl --fail --silent --show-error "$health" >/dev/null fi echo "[fleet] healthy $instance_id ($health)" matched=$((matched + 1)) done if [ "$matched" -eq 0 ]; then msg="[fleet] no silos matched *.$FLEET_DOMAIN_SUFFIX under $BASE/.secrets" if [ "$ALLOW_EMPTY_FLEET" = "1" ]; then echo "$msg (ALLOW_EMPTY_FLEET=1)" exit 0 fi echo "$msg" >&2 exit 1 fi echo "[fleet] rolled $matched silo(s) on $FLEET" REMOTE echo "[fleet] done fleet=$FLEET release=$RELEASE_ID hub=$HUB_DIR"