diff --git a/docs/03-deployment/CROMERO_DAPP_DEPLOYMENT.md b/docs/03-deployment/CROMERO_DAPP_DEPLOYMENT.md new file mode 100644 index 00000000..c384dcd7 --- /dev/null +++ b/docs/03-deployment/CROMERO_DAPP_DEPLOYMENT.md @@ -0,0 +1,78 @@ +# CROMERO Dapp — Phoenix Deploy + +Deploys [`d-bis/CROMERO`](https://gitea.d-bis.org/d-bis/CROMERO) (a +Vite + React + thirdweb v5 dapp) to +`https://d-bis.org/ecosystem/cromero/`. + +## Pipeline + +1. Push to `main` on `d-bis/CROMERO` triggers + `.gitea/workflows/deploy-to-phoenix.yml`, which POSTs + `{repo, sha, branch, target: "default"}` to the Phoenix Deploy API. +2. Phoenix runs the registered target (see + `phoenix-deploy-api/deploy-targets.json`): + `bash scripts/deployment/phoenix-deploy-cromero-from-workspace.sh`. +3. The script builds the staged workspace (`npm ci && npm run build`), + copies `dist/` through `r630-01` into NPMplus CT `10233`, and lands + files in `/var/www/ecosystem/cromero/`. +4. Healthcheck: `https://d-bis.org/ecosystem/cromero/` must return + HTTP 200 with `
` in the body. + +## Live NPMplus topology + +The primary NPMplus ingress is Dockerized inside CT `10233`, reachable via +Proxmox host `r630-01` (`192.168.11.11`). Direct SSH to +`192.168.11.167` is not assumed. The deploy script therefore uses +`pct push` / `pct exec` through the Proxmox host and keeps these paths +aligned: + +- CT host path: `/var/www/ecosystem/cromero/` +- Persistent NPMplus data path: `/opt/npmplus/html/ecosystem/cromero/` +- Nginx container path: `/var/www -> /data/html` + +## One-time nginx setup on the NPMplus host + +Install this once in the NPMplus advanced-config tab for the `d-bis.org` +proxy host: + +```nginx +location = /ecosystem/cromero { + return 301 /ecosystem/cromero/; +} + +location /ecosystem/cromero/ { + alias /var/www/ecosystem/cromero/; + index index.html; + try_files $uri /ecosystem/cromero/index.html; +} +``` + +Then run nginx config validation/reload inside the NPMplus container. + +The Vite app builds with `base: "/ecosystem/cromero/"` so hashed asset URLs +resolve under that subpath. + +## Required Actions secrets/vars on `d-bis/CROMERO` + +| Name | Type | Value | +| --- | --- | --- | +| `PHOENIX_DEPLOY_URL` | secret | `http://192.168.11.59:4001/api/deploy` | +| `PHOENIX_DEPLOY_TOKEN` | secret | matches `PHOENIX_DEPLOY_SECRET` on the Phoenix host | +| `VITE_THIRDWEB_CLIENT_ID` | secret | thirdweb publishable Client ID | +| `VITE_PROJECT_WALLET_ADDRESS` | var | recipient `0x...` address | +| `VITE_CHAIN_138_RPC` | var (optional) | defaults to `https://rpc.d-bis.org` | +| `VITE_CHAIN_138_EXPLORER` | var (optional) | defaults to `https://explorer.d-bis.org` | + +## Manual trigger from a LAN box with `phoenix-deploy-api` access + +```bash +curl -sSf -X POST "http://192.168.11.59:4001/api/deploy" -H "Authorization: Bearer ${PHOENIX_DEPLOY_TOKEN}" -H "Content-Type: application/json" -d '{"repo":"d-bis/CROMERO","branch":"main","target":"default"}' +``` + +## Dry run + +From this repo: + +```bash +PHOENIX_DEPLOY_WORKSPACE=/path/to/staged/CROMERO bash scripts/deployment/phoenix-deploy-cromero-from-workspace.sh --dry-run +``` diff --git a/phoenix-deploy-api/deploy-targets.json b/phoenix-deploy-api/deploy-targets.json index 5f9ffa01..182c1b19 100644 --- a/phoenix-deploy-api/deploy-targets.json +++ b/phoenix-deploy-api/deploy-targets.json @@ -102,6 +102,30 @@ "timeout_ms": 15000 } }, + { + "repo": "Gov_Web_Portals/DBIS", + "branch": "main", + "target": "dbis-portal-live", + "description": "Redeploy the DBIS public portal on CT 7804 from the staged DBIS checkout overlaid into the Gov Portals workspace.", + "cwd": "${PHOENIX_REPO_ROOT}", + "command": [ + "bash", + "scripts/deployment/phoenix-deploy-dbis-portal-live-from-workspace.sh" + ], + "required_env": [ + "PHOENIX_REPO_ROOT", + "PHOENIX_DEPLOY_WORKSPACE" + ], + "timeout_sec": 2400, + "healthcheck": { + "url": "https://d-bis.org/.well-known/trust.json", + "expect_status": 200, + "expect_body_includes": "\"organization\"", + "attempts": 12, + "delay_ms": 5000, + "timeout_ms": 15000 + } + }, { "repo": "d-bis/CurrenciCombo", "branch": "main", @@ -125,6 +149,29 @@ "timeout_ms": 15000 } }, + { + "repo": "d-bis/CROMERO", + "branch": "main", + "target": "default", + "description": "Deploy CROMERO dapp from the staged Gitea workspace: build dist/, rsync to NPMplus host /var/www/ecosystem/cromero/, served at https://d-bis.org/ecosystem/cromero/.", + "cwd": "${PHOENIX_REPO_ROOT}", + "command": [ + "bash", + "scripts/deployment/phoenix-deploy-cromero-from-workspace.sh" + ], + "required_env": [ + "PHOENIX_REPO_ROOT", + "PHOENIX_DEPLOY_WORKSPACE" + ], + "healthcheck": { + "url": "https://d-bis.org/ecosystem/cromero/", + "expect_status": 200, + "expect_body_includes": "
", + "attempts": 12, + "delay_ms": 5000, + "timeout_ms": 15000 + } + }, { "repo": "d-bis/proxmox", "branch": "main", diff --git a/scripts/deployment/phoenix-deploy-cromero-from-workspace.sh b/scripts/deployment/phoenix-deploy-cromero-from-workspace.sh new file mode 100755 index 00000000..cf6042a7 --- /dev/null +++ b/scripts/deployment/phoenix-deploy-cromero-from-workspace.sh @@ -0,0 +1,101 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" +if [[ -f "$PROJECT_ROOT/scripts/lib/load-project-env.sh" ]]; then + # shellcheck source=/dev/null + source "$PROJECT_ROOT/scripts/lib/load-project-env.sh" +fi +if [[ -f "$PROJECT_ROOT/config/ip-addresses.conf" ]]; then + # shellcheck source=/dev/null + source "$PROJECT_ROOT/config/ip-addresses.conf" 2>/dev/null || true +fi + +PHOENIX_DEPLOY_WORKSPACE="${PHOENIX_DEPLOY_WORKSPACE:-}" +NPMPLUS_PROXMOX_HOST="${NPMPLUS_PROXMOX_HOST:-${PROXMOX_HOST_R630_01:-192.168.11.11}}" +NPMPLUS_VMID="${NPMPLUS_VMID:-10233}" +NPMPLUS_DEPLOY_ROOT="${NPMPLUS_DEPLOY_ROOT:-/var/www/ecosystem/cromero}" +NPMPLUS_DATA_ROOT="${NPMPLUS_DATA_ROOT:-/opt/npmplus/html/ecosystem/cromero}" +PUBLIC_URL="${PUBLIC_URL:-https://d-bis.org/ecosystem/cromero/}" +DRY_RUN="${DRY_RUN:-0}" +SSH_OPTS=(-o BatchMode=yes -o ConnectTimeout=15 -o StrictHostKeyChecking=accept-new) +TMP_ARCHIVE="/tmp/cromero-dapp-dist-$$.tgz" + +usage() { printf 'Usage: %s [--dry-run]\n' "$(basename "$0")"; } +log() { printf '[cromero-phoenix] %s\n' "$*" >&2; } +die() { printf '[cromero-phoenix][FATAL] %s\n' "$*" >&2; exit 1; } +need_cmd() { command -v "$1" >/dev/null 2>&1 || die "missing required command: $1"; } +run() { if [[ "$DRY_RUN" == 1 ]]; then printf '[dry-run] %q ' "$@" >&2; printf '\n' >&2; else "$@"; fi; } +cleanup() { rm -f "$TMP_ARCHIVE"; } +trap cleanup EXIT + +while [[ $# -gt 0 ]]; do + case "$1" in + --dry-run) DRY_RUN=1; shift ;; + -h|--help) usage; exit 0 ;; + *) die "unknown arg: $1" ;; + esac +done + +for cmd in ssh scp tar curl node npm; do need_cmd "$cmd"; done +[[ -n "$PHOENIX_DEPLOY_WORKSPACE" ]] || die "PHOENIX_DEPLOY_WORKSPACE is required" +[[ -d "$PHOENIX_DEPLOY_WORKSPACE" ]] || die "staged workspace missing: $PHOENIX_DEPLOY_WORKSPACE" + +log "building CROMERO from staged workspace: $PHOENIX_DEPLOY_WORKSPACE" +pushd "$PHOENIX_DEPLOY_WORKSPACE" >/dev/null +if [[ -f package-lock.json ]]; then + run npm ci --no-audit --no-fund +else + run npm install --no-audit --no-fund +fi +run env \ + VITE_THIRDWEB_CLIENT_ID="${VITE_THIRDWEB_CLIENT_ID:-}" \ + VITE_PROJECT_WALLET_ADDRESS="${VITE_PROJECT_WALLET_ADDRESS:-0x3E309b87fA79092767531a0A6F5B6c3480737c5e}" \ + VITE_CHAIN_138_RPC="${VITE_CHAIN_138_RPC:-https://rpc.d-bis.org}" \ + VITE_CHAIN_138_EXPLORER="${VITE_CHAIN_138_EXPLORER:-https://explorer.d-bis.org}" \ + npm run build +[[ -f dist/index.html ]] || die "build produced no dist/index.html" +tar -C "$PHOENIX_DEPLOY_WORKSPACE" -czf "$TMP_ARCHIVE" dist +popd >/dev/null + +log "deploying dist/ through $NPMPLUS_PROXMOX_HOST to CT $NPMPLUS_VMID:$NPMPLUS_DEPLOY_ROOT" +run scp "${SSH_OPTS[@]}" "$TMP_ARCHIVE" "root@$NPMPLUS_PROXMOX_HOST:/tmp/cromero-dapp-dist.tgz" +if [[ "$DRY_RUN" == 1 ]]; then + log "dry-run complete" + exit 0 +fi +ssh "${SSH_OPTS[@]}" "root@$NPMPLUS_PROXMOX_HOST" bash -s -- "$NPMPLUS_VMID" "$NPMPLUS_DEPLOY_ROOT" "$NPMPLUS_DATA_ROOT" <<'INNER' +set -euo pipefail +vmid="$1" +deploy_root="$2" +data_root="$3" +pct exec "$vmid" -- bash -lc " +set -euo pipefail +mkdir -p '$data_root' /var/www/ecosystem +if [ -e '$deploy_root' ] && [ ! -L '$deploy_root' ]; then rm -rf '$deploy_root'; fi +ln -sfn '$data_root' '$deploy_root' +rm -rf /tmp/cromero-dist +" +pct push "$vmid" /tmp/cromero-dapp-dist.tgz /tmp/cromero-dapp-dist.tgz +pct exec "$vmid" -- bash -lc " +set -euo pipefail +rm -rf /tmp/cromero-dist +mkdir -p /tmp/cromero-dist '$data_root' +tar -xzf /tmp/cromero-dapp-dist.tgz -C /tmp/cromero-dist +find '$data_root' -mindepth 1 -maxdepth 1 -exec rm -rf {} + +cp -R /tmp/cromero-dist/dist/. '$data_root/' +chown -R root:root /opt/npmplus/html/ecosystem /var/www/ecosystem +chmod 755 /opt/npmplus/html /opt/npmplus/html/ecosystem '$data_root' /var/www /var/www/ecosystem +if command -v docker >/dev/null 2>&1; then + docker exec npmplus sh -lc 'rm -rf /var/www && ln -s /data/html /var/www && nginx -t && nginx -s reload' +fi +rm -rf /tmp/cromero-dist /tmp/cromero-dapp-dist.tgz +" +rm -f /tmp/cromero-dapp-dist.tgz +INNER + +log "verifying $PUBLIC_URL" +body="$(curl -fsS --max-time 20 "$PUBLIC_URL")" || die "public URL failed: $PUBLIC_URL" +printf '%s' "$body" | grep -F '
' >/dev/null || die "public URL missing React root" +log "CROMERO Phoenix deploy completed" diff --git a/scripts/deployment/phoenix-deploy-dbis-portal-live-from-workspace.sh b/scripts/deployment/phoenix-deploy-dbis-portal-live-from-workspace.sh new file mode 100755 index 00000000..b4eae43b --- /dev/null +++ b/scripts/deployment/phoenix-deploy-dbis-portal-live-from-workspace.sh @@ -0,0 +1,186 @@ +#!/usr/bin/env bash +# Deploy the DBIS public portal from a Phoenix Deploy API staged DBIS checkout. +# +# The DBIS repo is normally a submodule of Gov_Web_Portals/gov-portals-monorepo +# and depends on the parent workspace package @public-web-portals/shared. This +# wrapper builds a temporary monorepo-shaped workspace, overlays the staged DBIS +# source into it, syncs that tree to CT 7804, then rebuilds/restarts DBIS. + +set -euo pipefail + +die() { + echo "ERROR: $*" >&2 + exit 1 +} + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" + +source "$PROJECT_ROOT/config/ip-addresses.conf" 2>/dev/null || true +[ -f "$PROJECT_ROOT/.env" ] && set +u && source "$PROJECT_ROOT/.env" 2>/dev/null || true && set -u + +PHOENIX_REPO_ROOT="${PHOENIX_REPO_ROOT:-$PROJECT_ROOT}" +PHOENIX_DEPLOY_WORKSPACE="${PHOENIX_DEPLOY_WORKSPACE:-}" +GOV_PORTALS_REPO_URL="${GOV_PORTALS_REPO_URL:-https://gitea.d-bis.org/Gov_Web_Portals/gov-portals-monorepo.git}" +GOV_PORTALS_REF="${GOV_PORTALS_REF:-main}" + +VMID_GOV_PORTALS="${VMID_GOV_PORTALS:-7804}" +IP_GOV_PORTALS_DEV="${IP_GOV_PORTALS_DEV:-192.168.11.54}" +PROXMOX_HOST="${DBIS_PORTAL_PROXMOX_HOST:-${PROXMOX_HOST_GOV_PORTALS:-192.168.11.14}}" +CT_APP_DIR="${DBIS_PORTAL_CT_DIR:-/srv/gov-portals}" +SERVICE_NAME="${DBIS_PORTAL_SERVICE:-gov-portal-DBIS}" +DBIS_PORT="${DBIS_PORT:-3001}" + +[[ -d "$PHOENIX_REPO_ROOT" ]] || die "PHOENIX_REPO_ROOT does not exist: $PHOENIX_REPO_ROOT" +[[ -n "$PHOENIX_DEPLOY_WORKSPACE" ]] || die "PHOENIX_DEPLOY_WORKSPACE is required" +[[ -d "$PHOENIX_DEPLOY_WORKSPACE" ]] || die "staged DBIS workspace missing: $PHOENIX_DEPLOY_WORKSPACE" +[[ "$CT_APP_DIR" != "/" ]] || die "refusing to deploy into /" + +TMP_DIR="$(mktemp -d)" +BUILD_CONTEXT="$TMP_DIR/gov-portals" +ARCHIVE="$TMP_DIR/gov-portals-dbis-live.tgz" +REMOTE_ARCHIVE="/tmp/gov-portals-dbis-live-${PHOENIX_DEPLOY_SHA:-manual}-$$.tgz" + +cleanup() { + rm -rf "$TMP_DIR" +} +trap cleanup EXIT + +echo "Preparing DBIS live deploy context" +echo " DBIS source: $PHOENIX_DEPLOY_WORKSPACE" +echo " parent repo: $GOV_PORTALS_REPO_URL#$GOV_PORTALS_REF" +echo " target: CT $VMID_GOV_PORTALS ($IP_GOV_PORTALS_DEV), service $SERVICE_NAME, port $DBIS_PORT" + +git_auth_args=() +if [[ -n "${GITEA_TOKEN:-}" ]]; then + git_auth_args=(-c "http.extraHeader=Authorization: token ${GITEA_TOKEN}") +fi + +git "${git_auth_args[@]}" clone --depth 1 --branch "$GOV_PORTALS_REF" "$GOV_PORTALS_REPO_URL" "$BUILD_CONTEXT" + +rm -rf "$BUILD_CONTEXT/DBIS" +mkdir -p "$BUILD_CONTEXT/DBIS" +tar \ + --exclude=.git \ + --exclude=node_modules \ + --exclude=.next \ + --exclude='*.tsbuildinfo' \ + -C "$PHOENIX_DEPLOY_WORKSPACE" \ + -cf - . | tar -C "$BUILD_CONTEXT/DBIS" -xf - + +tar \ + --exclude=.git \ + --exclude=node_modules \ + --exclude=.next \ + --exclude='*.tsbuildinfo' \ + -C "$BUILD_CONTEXT" \ + -czf "$ARCHIVE" . + +echo "Uploading deploy archive to Proxmox host $PROXMOX_HOST" +scp -q -o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new "$ARCHIVE" "root@$PROXMOX_HOST:$REMOTE_ARCHIVE" + +echo "Pushing archive into CT $VMID_GOV_PORTALS" +ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new "root@$PROXMOX_HOST" \ + "pct push $VMID_GOV_PORTALS '$REMOTE_ARCHIVE' '$REMOTE_ARCHIVE'" + +echo "Extracting, building, and restarting DBIS inside CT $VMID_GOV_PORTALS" +ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new "root@$PROXMOX_HOST" \ + "pct exec $VMID_GOV_PORTALS -- bash -s" </dev/null 2>&1; then + apt-get update -qq + apt-get install -y -qq curl ca-certificates +fi + +NODE_MAJOR="\$(node -p 'process.versions.node.split(\".\")[0]' 2>/dev/null || echo 0)" +if [ "\$NODE_MAJOR" -lt 20 ]; then + curl -fsSL https://deb.nodesource.com/setup_20.x | bash - + apt-get install -y nodejs + hash -r +fi + +export PATH="/usr/local/bin:/usr/bin:/bin:\$PATH" +if ! command -v pnpm >/dev/null 2>&1; then + npm install -g pnpm@8.15.0 + hash -r +fi +PNPM_BIN="\$(command -v pnpm || true)" +if [ -z "\$PNPM_BIN" ]; then + for candidate in /usr/local/bin/pnpm /usr/bin/pnpm; do + if [ -x "\$candidate" ]; then + PNPM_BIN="\$candidate" + break + fi + done +fi +[ -n "\$PNPM_BIN" ] || { echo "pnpm is required but was not found after install" >&2; exit 1; } + +cd "\$CT_APP_DIR" +"\$PNPM_BIN" install --frozen-lockfile +"\$PNPM_BIN" --filter portal-dbis build + +cat > "/etc/systemd/system/\$SERVICE_NAME.service" </dev/null +CT_SCRIPT + +ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new "root@$PROXMOX_HOST" "rm -f '$REMOTE_ARCHIVE'" >/dev/null 2>&1 || true + +echo "DBIS live deployment complete." +echo "Local origin check: http://$IP_GOV_PORTALS_DEV:$DBIS_PORT/"