diff --git a/hub/deploy/cph-hub.service b/hub/deploy/cph-hub.service index 7f8e708..ba0a346 100644 --- a/hub/deploy/cph-hub.service +++ b/hub/deploy/cph-hub.service @@ -24,3 +24,6 @@ AssertPathExists=/usr/bin/bwrap # bwrap user-namespace setup. The OS-level sandbox (ADR-0018) is the hard # boundary; systemd hardening is kept minimal to avoid interfering with it. NoNewPrivileges=true + +[Install] +WantedBy=multi-user.target diff --git a/hub/deploy/deploy_platform.sh b/hub/deploy/deploy_platform.sh index 1bbf677..90e6730 100755 --- a/hub/deploy/deploy_platform.sh +++ b/hub/deploy/deploy_platform.sh @@ -7,9 +7,11 @@ # PLATFORM_DEPLOY_USER optional, defaults to deploy # PLATFORM_DEPLOY_PORT optional, defaults to 22 # PLATFORM_DEPLOY_BASE optional, defaults to /srv/curriculum-project-hub +# PLATFORM_DEPLOY_HEALTH_URL optional, defaults to http://127.0.0.1:8788/api/healthz # The target host must have Node.js, npm, rsync, PostgreSQL, systemd, bubblewrap # (`bwrap`, for the ADR-0018 agent sandbox), and a writable $PLATFORM_DEPLOY_BASE. -# Use a dedicated `deploy` user that has passwordless sudo for: +# Use a dedicated `deploy` user that has passwordless sudo for systemctl and +# writing /etc/systemd/system/cph-hub.service through deploy/install_service.sh. set -euo pipefail HOST="${PLATFORM_DEPLOY_HOST:?PLATFORM_DEPLOY_HOST required}" @@ -17,12 +19,15 @@ SSH_KEY="${PLATFORM_DEPLOY_SSH_KEY:?PLATFORM_DEPLOY_SSH_KEY required}" DEPLOY_USER="${PLATFORM_DEPLOY_USER:-deploy}" PORT="${PLATFORM_DEPLOY_PORT:-22}" BASE="${PLATFORM_DEPLOY_BASE:-/srv/curriculum-project-hub}" +HEALTH_URL="${PLATFORM_DEPLOY_HEALTH_URL:-http://127.0.0.1:8788/api/healthz}" HUB_DIR="$BASE/hub" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" SSH_OPTS=(-i "$SSH_KEY" -p "$PORT" -o StrictHostKeyChecking=accept-new) +ssh "${SSH_OPTS[@]}" "$DEPLOY_USER@$HOST" "mkdir -p '$HUB_DIR'" + # 1. Rsync the hub/ directory (exclude node_modules/dist, they rebuild on host). rsync -az --delete \ --exclude node_modules --exclude dist --exclude .env \ @@ -34,11 +39,23 @@ ssh "${SSH_OPTS[@]}" "$DEPLOY_USER@$HOST" "cd '$HUB_DIR' && npm ci && npm run bu # 3. Ensure the service is installed (idempotent), then restart. ssh "${SSH_OPTS[@]}" "$DEPLOY_USER@$HOST" " - BASE='$BASE' HUB_DIR='$HUB_DIR' bash '$HUB_DIR/deploy/install_service.sh' || true - sudo systemctl restart cph-hub.service - sleep 2 - sudo systemctl is-active --quiet cph-hub.service || { echo 'service failed to start'; exit 1; } - curl -sf http://127.0.0.1:8788/api/healthz || { echo 'healthz failed'; exit 1; } + set -euo pipefail + if [ \"\$(id -u)\" = \"0\" ]; then + BASE='$BASE' HUB_DIR='$HUB_DIR' bash '$HUB_DIR/deploy/install_service.sh' + systemctl restart cph-hub.service + systemctl is-active --quiet cph-hub.service + else + sudo -n BASE='$BASE' HUB_DIR='$HUB_DIR' bash '$HUB_DIR/deploy/install_service.sh' + sudo -n systemctl restart cph-hub.service + sudo -n systemctl is-active --quiet cph-hub.service + fi + for attempt in \$(seq 1 30); do + if curl --fail --silent '$HEALTH_URL' >/dev/null 2>&1; then + exit 0 + fi + sleep 1 + done + curl --fail --silent --show-error '$HEALTH_URL' >/dev/null " echo "Deployed cph-hub to $HOST ($HUB_DIR)" diff --git a/hub/deploy/install_service.sh b/hub/deploy/install_service.sh index 9e913c6..93aeebb 100755 --- a/hub/deploy/install_service.sh +++ b/hub/deploy/install_service.sh @@ -16,6 +16,7 @@ HOST="${HOST:-127.0.0.1}" PORT="${PORT:-8788}" ENV_FILE="${ENV_FILE:-$BASE/.secrets/platform.env}" NODE_BIN="${NODE_BIN:-$(command -v node || true)}" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" if [ -z "$NODE_BIN" ]; then echo "node must be on PATH, or set NODE_BIN." >&2 @@ -29,6 +30,10 @@ if [ ! -f "$HUB_DIR/dist/server.js" ]; then echo "build missing: run 'npm ci && npm run build' in $HUB_DIR first." >&2 exit 1 fi +if [ ! -f "$SCRIPT_DIR/cph-hub.service" ]; then + echo "service template missing: $SCRIPT_DIR/cph-hub.service" >&2 + exit 1 +fi # Seed the env file if absent. Secrets stay on the server, never in the repo. if [ ! -f "$ENV_FILE" ]; then @@ -47,18 +52,23 @@ FEISHU_BOT_OPEN_ID= PORT=$PORT HOST=$HOST EOF + chmod 0600 "$ENV_FILE" echo "Seeded $ENV_FILE — edit it to fill in ANTHROPIC_AUTH_TOKEN and Feishu creds, then re-run." exit 0 fi # Render the unit with concrete paths. UNIT=/etc/systemd/system/cph-hub.service +TMP_UNIT="$(mktemp)" sed \ -e "s|__SERVICE_USER__|$SERVICE_USER|g" \ -e "s|__HUB_DIR__|$HUB_DIR|g" \ -e "s|__BASE_DIR__|$BASE|g" \ -e "s|__ENV_FILE__|$ENV_FILE|g" \ -e "s|__NODE_BIN__|$NODE_BIN|g" \ + "$SCRIPT_DIR/cph-hub.service" >"$TMP_UNIT" +install -m 0644 "$TMP_UNIT" "$UNIT" +rm -f "$TMP_UNIT" systemctl daemon-reload systemctl enable cph-hub.service