#!/usr/bin/env bash # Deploy the current Next.js standalone frontend to VMID 5000. # This is the canonical deployment path for the current SolaceScan frontend. # It builds the local frontend, uploads the standalone bundle, installs a systemd # service, and starts the Node server on 127.0.0.1:3000 inside the container. set -euo pipefail VMID="${VMID:-5000}" FRONTEND_PORT="${FRONTEND_PORT:-3000}" SERVICE_NAME="solacescanscout-frontend" APP_ROOT="/opt/solacescanscout/frontend" PROXMOX_R630_02="${PROXMOX_HOST_R630_02:-192.168.11.12}" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" WORKSPACE_ROOT="$(cd "$REPO_ROOT/.." && pwd)" FRONTEND_ROOT="${REPO_ROOT}/frontend" SERVICE_TEMPLATE="${REPO_ROOT}/deployment/systemd/solacescanscout-frontend.service" NGINX_SNIPPET="${REPO_ROOT}/deployment/common/nginx-next-frontend-proxy.conf" VERIFY_SCRIPT="${WORKSPACE_ROOT}/scripts/verify/check-explorer-e2e.sh" RELEASE_ID="$(date +%Y%m%d_%H%M%S)" TMP_DIR="$(mktemp -d)" ARCHIVE_NAME="solacescanscout-next-${RELEASE_ID}.tar" STATIC_SYNC_FILES=( "index.html" "docs.html" "privacy.html" "terms.html" "acknowledgments.html" "chain138-command-center.html" "favicon.ico" "apple-touch-icon.png" "explorer-spa.js" ) cleanup() { rm -rf "$TMP_DIR" } trap cleanup EXIT if [[ ! -f "$SERVICE_TEMPLATE" ]]; then echo "Missing service template: $SERVICE_TEMPLATE" >&2 exit 1 fi push_into_vmid() { local source_path="$1" local destination_path="$2" local perms="${3:-0644}" if [[ -f /proc/1/cgroup ]] && grep -q "lxc" /proc/1/cgroup 2>/dev/null; then install -D -m "$perms" "$source_path" "$destination_path" elif command -v pct >/dev/null 2>&1; then pct push "$VMID" "$source_path" "$destination_path" --perms "$perms" else local remote_tmp="/tmp/$(basename "$source_path").$$" scp -o ConnectTimeout=10 -o StrictHostKeyChecking=no "$source_path" "root@${PROXMOX_R630_02}:${remote_tmp}" ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=no "root@${PROXMOX_R630_02}" \ "pct push ${VMID} ${remote_tmp} ${destination_path} --perms ${perms} && rm -f ${remote_tmp}" fi } run_in_vmid() { local command="$1" if [[ -f /proc/1/cgroup ]] && grep -q "lxc" /proc/1/cgroup 2>/dev/null; then bash -lc "$command" elif command -v pct >/dev/null 2>&1; then pct exec "$VMID" -- bash -lc "$command" else ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=no "root@${PROXMOX_R630_02}" \ "pct exec ${VMID} -- bash -lc $(printf '%q' "$command")" fi } echo "==========================================" echo "Deploying Next SolaceScan Frontend" echo "==========================================" echo "VMID: $VMID" echo "Frontend root: $FRONTEND_ROOT" echo "Release: $RELEASE_ID" echo "" if [[ "${SKIP_BUILD:-0}" != "1" ]]; then echo "== Building frontend ==" (cd "$FRONTEND_ROOT" && npm run build) echo "" fi if [[ ! -f "${FRONTEND_ROOT}/.next/standalone/server.js" ]]; then echo "Missing standalone server build. Run \`npm run build\` in ${FRONTEND_ROOT} first." >&2 exit 1 fi STAGE_DIR="${TMP_DIR}/stage" mkdir -p "${STAGE_DIR}/.next" cp -R "${FRONTEND_ROOT}/.next/standalone/." "$STAGE_DIR/" cp -R "${FRONTEND_ROOT}/.next/static" "${STAGE_DIR}/.next/static" cp -R "${FRONTEND_ROOT}/public" "${STAGE_DIR}/public" tar -C "$STAGE_DIR" -cf "${TMP_DIR}/${ARCHIVE_NAME}" . cp "$SERVICE_TEMPLATE" "${TMP_DIR}/${SERVICE_NAME}.service" sed -i "s|/opt/solacescanscout/frontend/current|${APP_ROOT}/current|g" "${TMP_DIR}/${SERVICE_NAME}.service" sed -i "s|Environment=PORT=3000|Environment=PORT=${FRONTEND_PORT}|g" "${TMP_DIR}/${SERVICE_NAME}.service" cat > "${TMP_DIR}/install-next-frontend.sh" </dev/null systemctl restart "\${SERVICE_NAME}.service" for attempt in \$(seq 1 45); do if curl -fsS --max-time 5 "http://127.0.0.1:\${FRONTEND_PORT}/" > /tmp/\${SERVICE_NAME}-health.out; then break fi sleep 1 done if ! grep -qiE "SolaceScan|Chain 138 Explorer by DBIS" /tmp/\${SERVICE_NAME}-health.out; then systemctl status "\${SERVICE_NAME}.service" --no-pager || true journalctl -u "\${SERVICE_NAME}.service" -n 50 --no-pager || true echo "Frontend health check did not find the expected SolaceScan marker." >&2 exit 1 fi mkdir -p /var/www/html for relpath in ${STATIC_SYNC_FILES[*]}; do if [[ -f "\${RELEASE_DIR}/public/\${relpath}" ]]; then install -D -m 0644 "\${RELEASE_DIR}/public/\${relpath}" "/var/www/html/\${relpath}" fi done if [[ -d "\${RELEASE_DIR}/public/thirdparty" ]]; then mkdir -p /var/www/html/thirdparty cp -a "\${RELEASE_DIR}/public/thirdparty/." /var/www/html/thirdparty/ fi if [[ -d "\${RELEASE_DIR}/public/config" ]]; then mkdir -p /var/www/html/config cp -a "\${RELEASE_DIR}/public/config/." /var/www/html/config/ fi EOF chmod +x "${TMP_DIR}/install-next-frontend.sh" echo "== Uploading release bundle ==" push_into_vmid "${TMP_DIR}/${ARCHIVE_NAME}" "/tmp/${ARCHIVE_NAME}" 0644 push_into_vmid "${TMP_DIR}/${SERVICE_NAME}.service" "/tmp/${SERVICE_NAME}.service" 0644 push_into_vmid "${TMP_DIR}/install-next-frontend.sh" "/tmp/install-next-frontend.sh" 0755 echo "" echo "== Installing release and restarting service ==" run_in_vmid "/tmp/install-next-frontend.sh" echo "" echo "== Verification ==" run_in_vmid "systemctl is-active ${SERVICE_NAME}.service" run_in_vmid "curl -fsS --max-time 5 http://127.0.0.1:${FRONTEND_PORT}/ | grep -qiE 'SolaceScan|Chain 138 Explorer by DBIS'" echo "Service ${SERVICE_NAME} is running on 127.0.0.1:${FRONTEND_PORT}" echo "" echo "Nginx follow-up:" echo " Switch the explorer server block to proxy / and /_next/ to 127.0.0.1:${FRONTEND_PORT}" echo " while preserving /api/, /api/config/*, /explorer-api/v1/, /token-aggregation/api/v1/, /snap/, and /health." echo " Snippet: ${NGINX_SNIPPET}" if [[ -f "${VERIFY_SCRIPT}" ]]; then echo " After nginx/NPMplus cutover, verify with:" echo " bash ${VERIFY_SCRIPT} https://blockscout.defi-oracle.io" fi echo "" echo "Next frontend deployment complete."