#!/usr/bin/env bash # # Auto-deploy SkateSpots to Netlify when the published origin/main changes. # # Driven by the systemd user timer `skatespots-deploy.timer`. It is SAFE by # construction: it only ever deploys committed code, and it never touches a # dirty working tree. # # One-time setup (see AGENTS.md): # netlify login # netlify link # choose the existing SkateSpots site # # Manual run / debug: # scripts/auto-deploy.sh # journalctl --user -u skatespots-deploy -n 50 # set -euo pipefail REPO="/home/sovereignsk8/SkateSpots" # systemd runs with a minimal PATH; node/npm live under nvm here. export PATH="/home/sovereignsk8/.nvm/versions/node/v22.23.1/bin:$PATH" STATE_DIR="$HOME/.local/state/skatespots-deploy" SHA_FILE="$STATE_DIR/last-deployed-sha" LOCK_FILE="$STATE_DIR/last-lock-hash" mkdir -p "$STATE_DIR" log() { echo "[$(date -Is)] $*"; } # Serialise runs (a slow deploy must not overlap the next timer tick). exec 9>"$STATE_DIR/.lock" if ! flock -n 9; then log "another deploy is already running — skipping" exit 0 fi cd "$REPO" # 1. What is published now? if ! git fetch --quiet origin main; then log "git fetch failed — skipping" exit 0 fi REMOTE_SHA="$(git rev-parse origin/main)" LAST_SHA="$(cat "$SHA_FILE" 2>/dev/null || true)" if [ "$REMOTE_SHA" = "$LAST_SHA" ]; then exit 0 # nothing new since the last deploy fi # 2. Only deploy committed code (ignore untracked build/generated files). if [ -n "$(git status --porcelain --untracked-files=no)" ]; then log "origin/main moved to ${REMOTE_SHA:0:9} but tracked files are modified — skipping" exit 0 fi # 3. Move the repo to the published commit (fast-forward only). if [ "$(git rev-parse HEAD)" != "$REMOTE_SHA" ]; then if git merge --ff-only --quiet origin/main; then log "fast-forwarded to ${REMOTE_SHA:0:9}" else log "local main has diverged from origin/main — skipping" exit 0 fi fi # 4. Re-install only when the lockfile changed (avoid `npm run build`'s own `npm i`, # which can rewrite the lock and dirty the tree). LOCK_HASH="$(git rev-parse HEAD:package-lock.json)" if [ "$LOCK_HASH" != "$(cat "$LOCK_FILE" 2>/dev/null || true)" ]; then log "package-lock changed — npm ci" npm ci --silent echo "$LOCK_HASH" > "$LOCK_FILE" fi # 5. Build the web bundle directly (skip the `npm i` in `npm run build`). log "building…" ./node_modules/.bin/vite build -l error cp dist/index.html dist/404.html # 6. Smoke-test the built bundle — abort if it doesn't boot. # `vite build` never executes its output, so this is the only guard against # a production-only crash (e.g. the manualChunks TDZ) reaching the site. log "smoke-testing the built bundle…" if ! node scripts/smoke-test.mjs; then log "SMOKE TEST FAILED — aborting deploy (the built bundle did not boot)" exit 1 fi # 7. Deploy to Netlify production. log "deploying to Netlify…" netlify deploy --prod --dir=dist echo "$REMOTE_SHA" > "$SHA_FILE" log "deployed ${REMOTE_SHA:0:9} ✓"