← back to Petitionyour
deploy-kamatera.sh
98 lines
#!/bin/bash
# petitionyour.org — Kamatera deploy
#
# GATED — this script is NOT run automatically by any agent. Steve runs it
# himself (or approves the exact `!` commands in the paste-safe runbook).
#
# Pre-reqs this script does NOT do for you (see README.md "Deploy runbook"):
# 1. nginx vhost installed at /etc/nginx/sites-available/petitionyour.org
# (copy deploy/nginx-petitionyour.org.conf) + `nginx -t` verified clean
# + `systemctl reload nginx` — BEFORE requesting the cert (port 80 must
# already answer petitionyour.org for certbot's HTTP-01 challenge).
# 2. Let's Encrypt cert via certbot for petitionyour.org + www.petitionyour.org
# 3. Node + pm2 already installed on Kamatera (they are, fleet-wide)
#
# What this script DOES (idempotent, safe to re-run):
# 1. rsync project to Kamatera at /root/public-projects/petitionyour-org
# 2. npm install --omit=dev
# 3. pm2 startOrReload ecosystem.config.js + pm2 save
# 4. 4-layer health check (origin direct / CF proxied / SSL valid / www)
#
# Usage:
# ./deploy-kamatera.sh # full deploy
# ./deploy-kamatera.sh --dry-run # show what would run, no changes
# ./deploy-kamatera.sh --health-only # only run the post-deploy health check
set -euo pipefail
KAMATERA="root@45.61.58.125"
LOCAL_DIR="$(cd "$(dirname "$0")" && pwd)"
REMOTE_DIR="/root/public-projects/petitionyour-org"
PM2_NAME="petitionyour-org"
DOMAIN="petitionyour.org"
PORT="9730"
DRY_RUN=false
HEALTH_ONLY=false
case "${1:-}" in
--dry-run) DRY_RUN=true ;;
--health-only) HEALTH_ONLY=true ;;
--help|-h) sed -n '2,26p' "$0"; exit 0 ;;
esac
run() {
if $DRY_RUN; then
echo "[DRY-RUN] $*"
else
eval "$@"
fi
}
health_check() {
echo "=== health check (4 layers) ==="
echo -n " 1. origin direct (HTTP, by Host header): "
curl -sI -o /dev/null -w "%{http_code}\n" -m 8 -H "Host: $DOMAIN" "http://45.61.58.125:$PORT/health" || echo FAIL
echo -n " 2. CF proxied (HTTPS): "
curl -sI -o /dev/null -w "%{http_code}\n" -m 8 "https://$DOMAIN/" || echo FAIL
echo -n " 3. SSL cert valid (openssl): "
echo | openssl s_client -connect "$DOMAIN:443" -servername "$DOMAIN" 2>/dev/null | openssl x509 -noout -dates 2>/dev/null || echo FAIL
echo -n " 4. www subdomain: "
curl -sI -o /dev/null -w "%{http_code}\n" -m 8 "https://www.$DOMAIN/" || echo FAIL
echo -n " pm2 status: "
ssh "$KAMATERA" "pm2 jlist 2>/dev/null | python3 -c 'import sys,json; ps=[p for p in json.load(sys.stdin) if p[\"name\"]==\"'$PM2_NAME'\"]; print(ps[0][\"pm2_env\"][\"status\"] if ps else \"not registered\")'" 2>/dev/null
}
if $HEALTH_ONLY; then
health_check
exit 0
fi
echo "=== checking SSH to Kamatera ==="
run "ssh -o ConnectTimeout=8 $KAMATERA 'hostname && uptime' || (echo 'SSH failed'; exit 1)"
echo ""
echo "=== rsync to $REMOTE_DIR ==="
run "ssh $KAMATERA 'mkdir -p $REMOTE_DIR'"
run "rsync -az --delete --exclude=node_modules --exclude=.git --exclude='*.log' --exclude='.env' --exclude='logs/' '$LOCAL_DIR/' '$KAMATERA:$REMOTE_DIR/'"
echo ""
echo "=== npm install --omit=dev ==="
run "ssh $KAMATERA 'cd $REMOTE_DIR && npm install --omit=dev --no-fund --no-audit'"
echo ""
echo "=== pm2 ==="
if $DRY_RUN; then
echo "[DRY-RUN] ssh $KAMATERA 'cd $REMOTE_DIR && pm2 startOrReload ecosystem.config.js && pm2 save'"
else
ssh "$KAMATERA" "cd $REMOTE_DIR && pm2 startOrReload ecosystem.config.js && pm2 save"
fi
echo ""
sleep 3
health_check
echo ""
echo "=== deploy complete ==="
echo "Site: https://$DOMAIN/"
echo "pm2: ssh $KAMATERA 'pm2 logs $PM2_NAME --lines 20'"