← back to Dw Pairs Well
deploy/kamatera-bootstrap.sh
107 lines
#!/usr/bin/env bash
# Idempotent Kamatera bootstrap for dw-pairs-well.
# Run AS ROOT on 45.61.58.125 after rsync has placed code at
# /root/Projects/dw-pairs-well/ and after DNS pairs.designerwallcoverings.com
# resolves to 45.61.58.125.
#
# ssh root@45.61.58.125 'bash /root/Projects/dw-pairs-well/deploy/kamatera-bootstrap.sh'
set -euo pipefail
PROJECT_DIR=/root/Projects/dw-pairs-well
DOMAIN=pairs.designerwallcoverings.com
PORT=9813
PM2_NAME=dw-pairs-well
NGINX_CONF=/etc/nginx/sites-available/${DOMAIN}
NGINX_LINK=/etc/nginx/sites-enabled/${DOMAIN}
echo "==[1/7] dependencies =================================================="
node -v >/dev/null 2>&1 || { echo "ERROR: node not installed"; exit 1; }
command -v pm2 >/dev/null 2>&1 || npm i -g pm2
command -v certbot >/dev/null 2>&1 || { apt-get update && apt-get install -y certbot python3-certbot-nginx; }
echo "==[2/7] npm install (production) ====================================="
cd "$PROJECT_DIR"
[ -f .env ] || { echo "ERROR: $PROJECT_DIR/.env missing — copy .env.example and edit"; exit 1; }
npm install --omit=dev --silent --no-audit --no-fund
echo "==[3/7] nginx vhost (HTTP only, certbot will add the TLS block) ======"
cat > "${NGINX_CONF}" <<NGINX
server {
listen 80;
listen [::]:80;
server_name ${DOMAIN};
# HTTP is left open here ONLY long enough for certbot's HTTP-01 challenge.
# Certbot will rewrite this file to redirect → HTTPS after issuance.
location /.well-known/acme-challenge/ { root /var/www/html; }
location / {
proxy_pass http://127.0.0.1:${PORT};
proxy_http_version 1.1;
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme;
proxy_connect_timeout 5s;
proxy_read_timeout 30s;
}
access_log /var/log/nginx/${DOMAIN}.access.log;
error_log /var/log/nginx/${DOMAIN}.error.log;
}
NGINX
ln -sfn "${NGINX_CONF}" "${NGINX_LINK}"
nginx -t
systemctl reload nginx
echo "==[4/7] DNS sanity check (must already point at 45.61.58.125) ========"
RESOLVED=$(dig +short "${DOMAIN}" @1.1.1.1 | head -1 || true)
if [ "${RESOLVED}" != "45.61.58.125" ] && [ -n "${RESOLVED}" ]; then
# CF proxy returns CF IPs (104.x / 172.67.x) — that's fine
case "${RESOLVED}" in
104.*|172.67.*|172.64.*|108.162.*) echo " → Cloudflare-proxied: ${RESOLVED} (ok)";;
*) echo " !! WARN: ${DOMAIN} → ${RESOLVED} (expected 45.61.58.125 or CF). Continuing anyway." ;;
esac
elif [ -z "${RESOLVED}" ]; then
echo " !! ERROR: ${DOMAIN} does not resolve. Add the DNS record first."
exit 1
else
echo " → resolves direct: 45.61.58.125 (ok)"
fi
echo "==[5/7] Let's Encrypt cert ==========================================="
if [ ! -d "/etc/letsencrypt/live/${DOMAIN}" ]; then
certbot --nginx --non-interactive --agree-tos \
--email steve@designerwallcoverings.com \
-d "${DOMAIN}" --redirect
else
echo " → cert already exists for ${DOMAIN}, skipping issuance"
fi
nginx -t && systemctl reload nginx
echo "==[6/7] pm2 start ===================================================="
cd "${PROJECT_DIR}"
if pm2 describe "${PM2_NAME}" >/dev/null 2>&1; then
pm2 reload "${PM2_NAME}"
else
PORT=${PORT} pm2 start server.js --name "${PM2_NAME}" --update-env
fi
pm2 save
echo "==[7/7] smoke test ==================================================="
sleep 2
echo "--- local :${PORT}/healthz ---"
curl -sS "http://127.0.0.1:${PORT}/healthz" || true
echo
echo "--- https://${DOMAIN}/healthz ---"
curl -sS "https://${DOMAIN}/healthz" || true
echo
echo "--- https://${DOMAIN}/api/pairs?dw_sku=DWRW-74991 (first pair only) ---"
curl -sS "https://${DOMAIN}/api/pairs?dw_sku=DWRW-74991" \
| python3 -c 'import sys,json; d=json.load(sys.stdin); p=d["pairs"][0] if d.get("pairs") else None; print(p["title"], "—", "score", p["score"]) if p else print("no pairs")' \
|| true
echo
echo "✅ done. http://${DOMAIN}/ should now redirect to https://${DOMAIN}/"