diff options
| -rw-r--r-- | AGENTS.md | 7 | ||||
| -rw-r--r-- | CLAUDE.md | 2 | ||||
| -rw-r--r-- | README.md | 55 | ||||
| -rwxr-xr-x | scripts/deploy.sh | 52 | ||||
| -rwxr-xr-x | scripts/post-receive.sample | 32 |
5 files changed, 94 insertions, 54 deletions
@@ -29,12 +29,7 @@ cd frontend && npm run lint && npm run build Topology: uvicorn on `127.0.0.1:8001`, `next start` on `127.0.0.1:3001`, nginx TLS reverse-proxy (`/api/` → backend, `/` → frontend). See `README.md` for the full deploy and redeploy commands. Service user is `www-data`; code lives at `/var/www/prism-v2/`. -Redeploy: - -```bash -cd /var/www/prism-v2 && sudo ./scripts/deploy.sh -# --no-pull to skip git pull; --install to also refresh systemd units + nginx site -``` +Deploy flow: bare repo at `/srv/git/prism-v2.git`; pushing master triggers a post-receive hook that `git checkout -f`s the tree into `/var/www/prism-v2/` and runs `scripts/deploy.sh` (build + restart + smoke check). `/var/www/prism-v2/` has no `.git/`. Manual deploy on the server: `sudo ./scripts/deploy.sh` (add `--install` to refresh systemd units / nginx site). See README for first-time setup. ## Coding Style @@ -41,7 +41,7 @@ Key files: Topology: uvicorn on `127.0.0.1:8001`, `next start` on `127.0.0.1:3001`, nginx TLS reverse-proxy at `prism.tylerhoang.xyz` (`/api/` → backend, `/` → frontend). Service user `www-data`, code at `/var/www/prism-v2/`. Full deploy instructions in `README.md`. -**Redeploy:** `sudo ./scripts/deploy.sh` from `/var/www/prism-v2` (idempotent — pull, install deps as `www-data`, build, restart, smoke-check). Flags: `--no-pull`, `--install` (refresh systemd units + nginx site). +**Deploy flow:** bare repo at `/srv/git/prism-v2.git`; pushing master triggers a post-receive hook that `git checkout -f`s into `/var/www/prism-v2/` and runs `scripts/deploy.sh`. `/var/www/prism-v2/` has NO `.git/` — never try to `git pull` there. Manual server-side deploy: `sudo ./scripts/deploy.sh` (`--install` also refreshes systemd units + nginx site). Sample hook lives at `scripts/post-receive.sample`. **Logs / status:** ```bash @@ -51,36 +51,63 @@ SQLite lives at `backend/data/prism.db`. Backend seeds a `default` profile on st ## Production Deployment -Target topology: backend on `127.0.0.1:8001`, frontend on `127.0.0.1:3001`, nginx terminates TLS and reverse-proxies. Code lives at `/var/www/prism-v2/` owned by `www-data`. +Target topology: -Use `scripts/deploy.sh` on the server — it is idempotent and handles both initial install and redeploys. +- Bare repo at `/srv/git/prism-v2.git` (push target) +- Working tree at `/var/www/prism-v2/` owned by `www-data` (no `.git/` — populated by post-receive hook via `git --work-tree=... checkout -f`) +- Backend on `127.0.0.1:8001`, frontend on `127.0.0.1:3001` +- nginx terminates TLS and reverse-proxies `/api/` → backend, `/` → frontend -### First-time setup on a fresh server +Workflow: `git push prod master` on your laptop → post-receive hook checks out the tree into `/var/www/prism-v2/` and runs `scripts/deploy.sh`, which builds + restarts. + +### First-time setup ```bash -# As root / via sudo -mkdir -p /var/www && chown www-data:www-data /var/www -sudo -u www-data git clone <repo-url> /var/www/prism-v2 -cd /var/www/prism-v2 +# 1. Create bare repo +sudo mkdir -p /srv/git && sudo git init --bare /srv/git/prism-v2.git + +# 2. Create working tree dir +sudo install -d -o www-data -g www-data /var/www/prism-v2 -# Install systemd units + nginx site + build + start +# 3. Push from your laptop so the working tree gets populated +# (add the remote on your laptop: git remote add prod user@host:/srv/git/prism-v2.git) +# git push prod master + +# 4. On the server: install systemd units + nginx site + build + start +cd /var/www/prism-v2 sudo ./scripts/deploy.sh --install -# First-time TLS (Certbot edits the nginx server block in-place) +# 5. First-time TLS (Certbot edits the nginx server block in-place) sudo certbot --nginx -d prism.tylerhoang.xyz sudo systemctl reload nginx + +# 6. Install the post-receive hook so future pushes auto-deploy +sudo cp /var/www/prism-v2/scripts/post-receive.sample /srv/git/prism-v2.git/hooks/post-receive +sudo chmod +x /srv/git/prism-v2.git/hooks/post-receive + +# 7. Sudoers entry so the git-push user can run deploy.sh without a password +# (replace GIT_USER with the account that owns /srv/git or receives the push) +echo 'GIT_USER ALL=(root) NOPASSWD: /var/www/prism-v2/scripts/deploy.sh' | sudo tee /etc/sudoers.d/prism-v2-deploy +sudo chmod 0440 /etc/sudoers.d/prism-v2-deploy +``` + +### Subsequent deploys + +From your laptop: + +```bash +git push prod master # post-receive hook checks out + runs deploy.sh ``` -### Redeploy +Manual deploy on the server (e.g. retry after a failed build, or to refresh systemd/nginx): ```bash cd /var/www/prism-v2 -sudo ./scripts/deploy.sh # pull + build + restart + smoke check -sudo ./scripts/deploy.sh --no-pull # rebuild + restart without git pull -sudo ./scripts/deploy.sh --install # also refresh systemd units / nginx site +sudo ./scripts/deploy.sh # build + restart + smoke check +sudo ./scripts/deploy.sh --install # also refresh systemd units / nginx site ``` -The script runs all build steps as `www-data` (with `HOME` + `NPM_CONFIG_CACHE` set), restarts both services, and curls `/health` on the backend and `/` on the frontend before exiting. +`deploy.sh` is idempotent: ensures ownership, builds the backend venv and `npm ci && npm run build` as `www-data` (with `HOME` + `NPM_CONFIG_CACHE` set), restarts both services, and curls `/health` and `/` before exiting. It does NOT touch git — the post-receive hook owns checkout. ### Ops diff --git a/scripts/deploy.sh b/scripts/deploy.sh index f192e72..5eae840 100755 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -2,12 +2,12 @@ # # Production deploy script for Prism v2. # -# Runs on the production server (in /var/www/prism-v2). Idempotent — handles -# both first-time install and redeploys. +# Runs on the production server, AFTER the working tree has been updated +# (typically by a git post-receive hook that does `git checkout -f` into +# /var/www/prism-v2). This script does NOT pull — it only builds + restarts. # # 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 # build + restart + smoke-check # sudo ./scripts/deploy.sh --install # also install systemd units + nginx site # sudo ./scripts/deploy.sh --help @@ -20,17 +20,15 @@ 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\}//' + sed -n '2,14p' "$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 ;; @@ -39,26 +37,13 @@ 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 [[ $EUID -ne 0 ]]; then + echo "deploy.sh must be run as root (use sudo)" >&2 + exit 1 +fi -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 +if [[ ! -d "$APP_DIR/backend" || ! -d "$APP_DIR/frontend" ]]; then + echo "Expected working tree at $APP_DIR (backend/ + frontend/ not found)" >&2 exit 1 fi @@ -66,13 +51,15 @@ 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" +install -d -o "$APP_USER" -g "$APP_GROUP" "$APP_DIR/frontend/.npm" +install -d -o "$APP_USER" -g "$APP_GROUP" "$APP_DIR/backend/data" -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 +run_as_app() { + sudo -u "$APP_USER" \ + env HOME="$APP_DIR/frontend" \ + NPM_CONFIG_CACHE="$APP_DIR/frontend/.npm" \ + "$@" +} log "Backend: venv + dependencies" if [[ ! -x "$APP_DIR/backend/.venv/bin/pip" ]]; then @@ -108,7 +95,6 @@ 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 diff --git a/scripts/post-receive.sample b/scripts/post-receive.sample new file mode 100755 index 0000000..3ee898d --- /dev/null +++ b/scripts/post-receive.sample @@ -0,0 +1,32 @@ +#!/bin/bash +# +# Sample git post-receive hook for /srv/git/prism-v2.git on the VPS. +# +# Install: +# sudo cp /var/www/prism-v2/scripts/post-receive.sample /srv/git/prism-v2.git/hooks/post-receive +# sudo chmod +x /srv/git/prism-v2.git/hooks/post-receive +# +# Required sudoers entry so the git user can restart services without a password. +# Adjust GIT_USER if your git is owned by a different account. +# GIT_USER ALL=(root) NOPASSWD: /var/www/prism-v2/scripts/deploy.sh + +set -euo pipefail + +BRANCH="master" +WEB_DIR="/var/www/prism-v2" +GIT_DIR="/srv/git/prism-v2.git" +LOG="/var/log/git-deploy.log" + +exec >> "$LOG" 2>&1 + +while read -r oldrev newrev refname; do + if [ "$refname" = "refs/heads/$BRANCH" ]; then + echo "$(date -Is): Checking out $BRANCH to $WEB_DIR" + sudo -u www-data git --work-tree="$WEB_DIR" --git-dir="$GIT_DIR" checkout -f "$BRANCH" + + echo "$(date -Is): Running deploy.sh" + sudo "$WEB_DIR/scripts/deploy.sh" + + echo "$(date -Is): Done." + fi +done |
