From 5fbc175e540803d919863f3d90dffc3c0645a90b Mon Sep 17 00:00:00 2001 From: Tyler Hoang Date: Sat, 30 May 2026 00:21:03 -0700 Subject: feat: add scripts/deploy.sh for idempotent production deploys Handles first-time install (--install) and redeploys: pulls, builds backend and frontend as www-data with the required HOME/NPM_CONFIG_CACHE env, restarts systemd services, and smoke-checks /health and /. Co-Authored-By: Claude Opus 4.7 --- scripts/deploy.sh | 124 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100755 scripts/deploy.sh (limited to 'scripts') diff --git a/scripts/deploy.sh b/scripts/deploy.sh new file mode 100755 index 0000000..f192e72 --- /dev/null +++ b/scripts/deploy.sh @@ -0,0 +1,124 @@ +#!/usr/bin/env bash +# +# Production deploy script for Prism v2. +# +# Runs on the production server (in /var/www/prism-v2). Idempotent — handles +# both first-time install and redeploys. +# +# Usage: +# sudo ./scripts/deploy.sh # full deploy (pull + build + restart) +# sudo ./scripts/deploy.sh --no-pull # build + restart only (skip git pull) +# sudo ./scripts/deploy.sh --install # also install systemd units + nginx site +# sudo ./scripts/deploy.sh --help + +set -euo pipefail + +APP_DIR="${APP_DIR:-/var/www/prism-v2}" +APP_USER="${APP_USER:-www-data}" +APP_GROUP="${APP_GROUP:-www-data}" +DOMAIN="${DOMAIN:-prism.tylerhoang.xyz}" +BACKEND_SVC="prismv2-backend.service" +FRONTEND_SVC="prismv2-frontend.service" + +DO_PULL=1 +DO_INSTALL=0 + +usage() { + sed -n '2,12p' "$0" | sed 's/^# \{0,1\}//' + exit 0 +} + +for arg in "$@"; do + case "$arg" in + --no-pull) DO_PULL=0 ;; + --install) DO_INSTALL=1 ;; + -h|--help) usage ;; + *) echo "Unknown arg: $arg" >&2; exit 2 ;; + esac +done + +log() { printf '\n=== %s ===\n' "$*"; } + +require_root() { + if [[ $EUID -ne 0 ]]; then + echo "deploy.sh must be run as root (use sudo)" >&2 + exit 1 + fi +} + +run_as_app() { + # Run a command as APP_USER with HOME + NPM_CONFIG_CACHE set so npm/git work. + sudo -u "$APP_USER" \ + env HOME="$APP_DIR/frontend" \ + NPM_CONFIG_CACHE="$APP_DIR/frontend/.npm" \ + "$@" +} + +require_root + +if [[ ! -d "$APP_DIR/.git" ]]; then + echo "Not a checkout: $APP_DIR (expected $APP_DIR/.git)" >&2 + echo "Clone the repo to $APP_DIR first." >&2 + exit 1 +fi + +cd "$APP_DIR" + +log "Ensuring ownership of $APP_DIR" +chown -R "$APP_USER:$APP_GROUP" "$APP_DIR" +mkdir -p "$APP_DIR/frontend/.npm" +chown -R "$APP_USER:$APP_GROUP" "$APP_DIR/frontend/.npm" + +if [[ $DO_PULL -eq 1 ]]; then + log "git pull origin master" + sudo -u "$APP_USER" git -C "$APP_DIR" pull --ff-only origin master +fi + +log "Backend: venv + dependencies" +if [[ ! -x "$APP_DIR/backend/.venv/bin/pip" ]]; then + sudo -u "$APP_USER" python3 -m venv "$APP_DIR/backend/.venv" +fi +sudo -u "$APP_USER" "$APP_DIR/backend/.venv/bin/pip" install --quiet --upgrade pip +sudo -u "$APP_USER" "$APP_DIR/backend/.venv/bin/pip" install --quiet -r "$APP_DIR/backend/requirements.txt" + +log "Frontend: npm ci + build" +run_as_app npm --prefix "$APP_DIR/frontend" ci +run_as_app npm --prefix "$APP_DIR/frontend" run build + +if [[ $DO_INSTALL -eq 1 ]]; then + log "Installing systemd units" + cp "$APP_DIR/systemd/$BACKEND_SVC" "/etc/systemd/system/$BACKEND_SVC" + cp "$APP_DIR/systemd/$FRONTEND_SVC" "/etc/systemd/system/$FRONTEND_SVC" + systemctl daemon-reload + systemctl enable "$BACKEND_SVC" "$FRONTEND_SVC" + + log "Installing nginx site" + cp "$APP_DIR/nginx/$DOMAIN.conf" "/etc/nginx/sites-available/$DOMAIN" + ln -sf "/etc/nginx/sites-available/$DOMAIN" "/etc/nginx/sites-enabled/$DOMAIN" + nginx -t + systemctl reload nginx +fi + +log "Restarting services" +systemctl restart "$BACKEND_SVC" "$FRONTEND_SVC" + +log "Smoke checks" +sleep 2 +if curl -fsS http://127.0.0.1:8001/health >/dev/null; then + echo " backend /health OK" +else + echo " backend /health FAILED" >&2 + echo " journalctl -u $BACKEND_SVC -n 50 --no-pager:" >&2 + journalctl -u "$BACKEND_SVC" -n 50 --no-pager >&2 || true + exit 1 +fi +if curl -fsS -o /dev/null -w '%{http_code}\n' http://127.0.0.1:3001/ | grep -qE '^(200|301|302|307|308)$'; then + echo " frontend / OK" +else + echo " frontend / FAILED" >&2 + journalctl -u "$FRONTEND_SVC" -n 50 --no-pager >&2 || true + exit 1 +fi + +log "Deploy complete" +systemctl status "$BACKEND_SVC" "$FRONTEND_SVC" --no-pager --lines=0 || true -- cgit v1.3-2-g0d8e