184 lines
5.0 KiB
Bash
Executable File
184 lines
5.0 KiB
Bash
Executable File
#!/bin/bash
|
|
#
|
|
# Sync the portal-public Next.js standalone build to the Sankofa / Proxmox runtime.
|
|
# Target topology:
|
|
# NPMplus -> order-haproxy (VMID 10210) -> order-portal-public (VMID 10090)
|
|
#
|
|
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
source "${SCRIPT_DIR}/config.sh"
|
|
|
|
APP_NAME="portal-public"
|
|
APP_DIR="${PROJECT_ROOT}/apps/${APP_NAME}"
|
|
LOCAL_BUILD_TARBALL="${ARTIFACTS_DIR}/${APP_NAME}-${ENVIRONMENT}.tgz"
|
|
LOCAL_STAGE_DIR="${ARTIFACTS_DIR}/${APP_NAME}-${ENVIRONMENT}-stage"
|
|
REMOTE_TGZ="/tmp/${APP_NAME}-${ENVIRONMENT}-$$.tgz"
|
|
CT_TGZ="/tmp/${APP_NAME}.tgz"
|
|
|
|
DRY_RUN=false
|
|
SKIP_BUILD=false
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--dry-run)
|
|
DRY_RUN=true
|
|
shift
|
|
;;
|
|
--skip-build)
|
|
SKIP_BUILD=true
|
|
shift
|
|
;;
|
|
*)
|
|
error_exit "Unknown option: $1"
|
|
;;
|
|
esac
|
|
done
|
|
|
|
if [ ! -d "${APP_DIR}" ]; then
|
|
error_exit "Portal app not found at ${APP_DIR}"
|
|
fi
|
|
|
|
check_prerequisites
|
|
check_proxmox_access
|
|
|
|
if [ "${SKIP_BUILD}" != "true" ]; then
|
|
log_step "Building ${APP_NAME} standalone bundle..."
|
|
rm -rf "${APP_DIR}/.next"
|
|
pnpm --dir "${APP_DIR}" build || error_exit "Failed to build ${APP_NAME}"
|
|
else
|
|
log_info "Skipping portal build because --skip-build was provided"
|
|
fi
|
|
|
|
if [ ! -f "${APP_DIR}/.next/standalone/apps/${APP_NAME}/server.js" ]; then
|
|
error_exit "Expected standalone output missing for ${APP_NAME}"
|
|
fi
|
|
|
|
log_step "Preparing deployment artifact..."
|
|
rm -rf "${LOCAL_STAGE_DIR}"
|
|
mkdir -p "${LOCAL_STAGE_DIR}/standalone/apps/${APP_NAME}/.next"
|
|
|
|
cp -R "${APP_DIR}/.next/standalone/." "${LOCAL_STAGE_DIR}/standalone/"
|
|
cp -R "${APP_DIR}/.next/static" "${LOCAL_STAGE_DIR}/standalone/apps/${APP_NAME}/.next/static"
|
|
cp -R "${APP_DIR}/public" "${LOCAL_STAGE_DIR}/standalone/apps/${APP_NAME}/public"
|
|
|
|
tar czf "${LOCAL_BUILD_TARBALL}" -C "${LOCAL_STAGE_DIR}" standalone
|
|
log_success "Created deployment artifact ${LOCAL_BUILD_TARBALL}"
|
|
|
|
if [ "${DRY_RUN}" = "true" ]; then
|
|
log_info "Dry run only. Would push ${LOCAL_BUILD_TARBALL} to CT ${ORDER_PORTAL_PUBLIC_VMID} on ${ORDER_PORTAL_PUBLIC_HOST}"
|
|
exit 0
|
|
fi
|
|
|
|
log_step "Uploading artifact to Proxmox host..."
|
|
scp ${SSH_OPTS} "${LOCAL_BUILD_TARBALL}" "${PROXMOX_SSH_USER}@${ORDER_PORTAL_PUBLIC_HOST}:${REMOTE_TGZ}"
|
|
|
|
log_step "Deploying ${APP_NAME} to CT ${ORDER_PORTAL_PUBLIC_VMID}..."
|
|
ssh ${SSH_OPTS} "${PROXMOX_SSH_USER}@${ORDER_PORTAL_PUBLIC_HOST}" bash -s -- \
|
|
"${ORDER_PORTAL_PUBLIC_VMID}" \
|
|
"${REMOTE_TGZ}" \
|
|
"${CT_TGZ}" \
|
|
"${ORDER_PORTAL_PUBLIC_APP_DIR}" \
|
|
"${ORDER_PORTAL_PUBLIC_SERVICE}" \
|
|
"${ORDER_PORTAL_PUBLIC_PORT}" \
|
|
"${THE_ORDER_PUBLIC_URL}" \
|
|
"${SANKOFA_PHOENIX_URL}" \
|
|
"${SANKOFA_PORTAL_URL}" \
|
|
"${ORDER_PORTAL_PUBLIC_IP}" <<'REMOTE_EOF'
|
|
set -euo pipefail
|
|
|
|
VMID="$1"
|
|
REMOTE_TGZ="$2"
|
|
CT_TGZ="$3"
|
|
APP_DIR="$4"
|
|
SERVICE_NAME="$5"
|
|
PORT="$6"
|
|
ORDER_URL="$7"
|
|
PHOENIX_URL="$8"
|
|
PORTAL_URL="$9"
|
|
PORTAL_IP="${10}"
|
|
|
|
pct push "${VMID}" "${REMOTE_TGZ}" "${CT_TGZ}"
|
|
rm -f "${REMOTE_TGZ}"
|
|
|
|
pct exec "${VMID}" -- bash -s -- \
|
|
"${CT_TGZ}" \
|
|
"${APP_DIR}" \
|
|
"${SERVICE_NAME}" \
|
|
"${PORT}" \
|
|
"${ORDER_URL}" \
|
|
"${PHOENIX_URL}" \
|
|
"${PORTAL_URL}" <<'CT_EOF'
|
|
set -euo pipefail
|
|
|
|
CT_TGZ="$1"
|
|
APP_DIR="$2"
|
|
SERVICE_NAME="$3"
|
|
PORT="$4"
|
|
ORDER_URL="$5"
|
|
PHOENIX_URL="$6"
|
|
PORTAL_URL="$7"
|
|
|
|
command -v node >/dev/null || { echo "ERROR: node is required inside the target CT"; exit 1; }
|
|
|
|
RELEASES_DIR="${APP_DIR}/releases"
|
|
SHARED_DIR="${APP_DIR}/shared"
|
|
CURRENT_LINK="${APP_DIR}/current"
|
|
RELEASE_DIR="${RELEASES_DIR}/$(date +%Y%m%d-%H%M%S)"
|
|
|
|
mkdir -p "${RELEASES_DIR}" "${SHARED_DIR}" "${RELEASE_DIR}"
|
|
tar xzf "${CT_TGZ}" -C "${RELEASE_DIR}"
|
|
rm -f "${CT_TGZ}"
|
|
ln -sfn "${RELEASE_DIR}/standalone" "${CURRENT_LINK}"
|
|
|
|
cat > "${SHARED_DIR}/runtime.env" <<ENV_EOF
|
|
NODE_ENV=production
|
|
PORT=${PORT}
|
|
HOSTNAME=0.0.0.0
|
|
NEXT_PUBLIC_ORDER_PUBLIC_URL=${ORDER_URL}
|
|
NEXT_PUBLIC_SANKOFA_PHOENIX_URL=${PHOENIX_URL}
|
|
NEXT_PUBLIC_SANKOFA_PORTAL_URL=${PORTAL_URL}
|
|
NEXT_PUBLIC_IDENTITY_SERVICE_URL=${PHOENIX_URL}
|
|
NEXT_PUBLIC_ERESIDENCY_SERVICE_URL=${PHOENIX_URL}
|
|
NEXT_PUBLIC_FINANCE_SERVICE_URL=${PHOENIX_URL}
|
|
NEXT_PUBLIC_DATAROOM_SERVICE_URL=${PHOENIX_URL}
|
|
ENV_EOF
|
|
|
|
cat > "/etc/systemd/system/${SERVICE_NAME}.service" <<UNIT_EOF
|
|
[Unit]
|
|
Description=The Order portal-public (Sankofa Phoenix runtime)
|
|
After=network-online.target
|
|
Wants=network-online.target
|
|
|
|
[Service]
|
|
Type=simple
|
|
WorkingDirectory=${CURRENT_LINK}/apps/portal-public
|
|
EnvironmentFile=${SHARED_DIR}/runtime.env
|
|
ExecStart=/usr/bin/env node server.js
|
|
Restart=always
|
|
RestartSec=5
|
|
User=root
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
UNIT_EOF
|
|
|
|
systemctl daemon-reload
|
|
systemctl enable "${SERVICE_NAME}" >/dev/null
|
|
systemctl restart "${SERVICE_NAME}"
|
|
systemctl is-active --quiet "${SERVICE_NAME}"
|
|
curl -fsS "http://127.0.0.1:${PORT}/api/health" >/dev/null
|
|
|
|
# Keep the five newest releases.
|
|
if [ -d "${RELEASES_DIR}" ]; then
|
|
ls -1dt "${RELEASES_DIR}"/* 2>/dev/null | tail -n +6 | xargs -r rm -rf --
|
|
fi
|
|
CT_EOF
|
|
|
|
echo "Deployed ${SERVICE_NAME} to CT ${VMID}"
|
|
echo "Direct health: http://${PORTAL_IP}:${PORT}/api/health"
|
|
REMOTE_EOF
|
|
|
|
log_success "${APP_NAME} deployed to ${ORDER_PORTAL_PUBLIC_IP}:${ORDER_PORTAL_PUBLIC_PORT}"
|