summaryrefslogtreecommitdiff
path: root/scripts/deploy.sh
blob: f192e72ddfa377473d691774a09782cee0e99db7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
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