Files
probably-rendering-one-more…/scripts/deploy.sh
T
codex-bot 68419e3852
PROMPT CI and deployment / verify-and-deploy (push) Successful in 3m53s
Initialize PROMPT environment and ces-control deployment pipeline
2026-10-22 20:43:02 +08:00

137 lines
4.1 KiB
Bash

#!/usr/bin/env bash
set -Eeuo pipefail
# Run on the actual ces-control host, with root or passwordless sudo.
if [ "$(id -u)" -ne 0 ]; then
exec sudo -n bash "$0" "$@"
fi
RELEASE_SHA="${1:?Release SHA is required}"
NODE_SOURCE="${2:?Node executable path is required}"
[[ "$RELEASE_SHA" =~ ^[0-9a-f]{40}$ ]] || { echo 'Invalid release SHA'; exit 1; }
test -x "$NODE_SOURCE"
test -d /run/systemd/system || { echo 'Deployment requires the host systemd environment'; exit 1; }
systemctl show-environment >/dev/null
test -f .next/standalone/server.js
APP_ROOT=/opt/prompt
DATA_ROOT=/var/lib/prompt
SERVICE=prompt.service
SERVICE_USER=prompt
PORT=18131
RELEASE_DIR="$APP_ROOT/releases/$RELEASE_SHA"
mkdir -p "$APP_ROOT/releases"
exec 9>"$APP_ROOT/.deploy.lock"
flock -w 300 9 || { echo 'Another PROMPT deployment is running'; exit 1; }
# Do not replace the release of a newer main commit after a slower old run.
if [ "$(git -c safe.directory="$PWD" rev-parse HEAD)" != "$RELEASE_SHA" ]; then
echo 'Checkout does not match release SHA'; exit 1
fi
LATEST_MAIN=$(git -c safe.directory="$PWD" ls-remote origin refs/heads/main | cut -f1)
if [ "$LATEST_MAIN" != "$RELEASE_SHA" ]; then
echo 'Skipping deployment: main has a newer commit'
exit 0
fi
PREVIOUS=""
if [ -L "$APP_ROOT/current" ] && [ -d "$APP_ROOT/current" ]; then
PREVIOUS=$(readlink -f "$APP_ROOT/current")
fi
if [ -z "$PREVIOUS" ]; then
"$NODE_SOURCE" -e "const s=require('net').createServer();s.once('error',()=>process.exit(1));s.listen($PORT,'0.0.0.0',()=>s.close());" || {
echo "Port $PORT is already occupied"; exit 1;
}
fi
if ! id "$SERVICE_USER" >/dev/null 2>&1; then
useradd --system --home "$DATA_ROOT" --shell /usr/sbin/nologin "$SERVICE_USER"
fi
install -d -o "$SERVICE_USER" -g "$SERVICE_USER" -m 0750 "$DATA_ROOT"
# Keep runtime data outside the checkout and releases; future modules can use it.
if [ ! -d "$RELEASE_DIR" ]; then
STAGING=$(mktemp -d "$APP_ROOT/releases/.staging.XXXXXX")
cp -a .next/standalone/. "$STAGING/"
mkdir -p "$STAGING/.next" "$STAGING/bin"
cp -a .next/static "$STAGING/.next/static"
if [ -d public ]; then cp -a public "$STAGING/public"; fi
install -m 0755 "$NODE_SOURCE" "$STAGING/bin/node"
printf 'RELEASE_SHA=%s\n' "$RELEASE_SHA" > "$STAGING/release.env"
chmod -R a+rX "$STAGING"
chmod 0755 "$STAGING"
mv "$STAGING" "$RELEASE_DIR"
fi
cat > /etc/systemd/system/prompt.service <<EOF
[Unit]
Description=PROMPT
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=$SERVICE_USER
Group=$SERVICE_USER
WorkingDirectory=$APP_ROOT/current
Environment=NODE_ENV=production
Environment=HOSTNAME=0.0.0.0
Environment=PORT=$PORT
Environment=PROMPT_DATA_DIR=$DATA_ROOT
EnvironmentFile=$APP_ROOT/current/release.env
ExecStart=$APP_ROOT/current/bin/node server.js
Restart=always
RestartSec=5
NoNewPrivileges=true
UMask=0027
[Install]
WantedBy=multi-user.target
EOF
SWITCHED=0
rollback() {
local code=$?
trap - ERR
set +e
if [ "$SWITCHED" -eq 1 ]; then
if [ -n "$PREVIOUS" ]; then
ln -sfn "$PREVIOUS" "$APP_ROOT/.current.rollback"
mv -Tf "$APP_ROOT/.current.rollback" "$APP_ROOT/current"
systemctl restart "$SERVICE"
echo 'Restored previous release'
else
systemctl disable --now "$SERVICE"
rm -f "$APP_ROOT/current"
echo 'Stopped failed first deployment'
fi
fi
echo "Deployment failed (exit $code)"
exit "$code"
}
trap rollback ERR
ln -sfn "$RELEASE_DIR" "$APP_ROOT/.current.next"
mv -Tf "$APP_ROOT/.current.next" "$APP_ROOT/current"
SWITCHED=1
systemctl daemon-reload
systemctl enable "$SERVICE"
systemctl restart "$SERVICE"
HEALTHY=0
for attempt in $(seq 1 30); do
if "$NODE_SOURCE" -e "fetch('http://127.0.0.1:$PORT/api/health').then(async r=>{const d=await r.json();if(!r.ok||d.service!=='prompt'||d.release!=='$RELEASE_SHA')process.exit(1)}).catch(()=>process.exit(1))"; then
HEALTHY=1
break
fi
sleep 2
done
if [ "$HEALTHY" -ne 1 ]; then
echo 'Health check failed; rolling back'
false
fi
systemctl is-enabled "$SERVICE"
systemctl is-active "$SERVICE"
echo "Persistent deployment verified: service=$SERVICE port=$PORT release=$RELEASE_SHA"
echo "Host: $(hostname)"
hostname -I || true