diff --git a/AGENTS.md b/AGENTS.md index 21834d9..340d4fe 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -657,6 +657,7 @@ Rules: ### Deploy - **Web (auto)**: a systemd user timer (`skatespots-deploy.timer`) runs `scripts/auto-deploy.sh` every ~3 min. When `origin/main` changes it builds and runs `netlify deploy --prod --dir=dist`. It deploys **only committed code** (skips if tracked files are modified) and never mutates a dirty tree. **Doc/scripts-only commits are skipped** (matches `AGENTS.md`/`*.md`/`scripts/`/`cvm-server/`/`skills/`) — Netlify bills **15 credits per production deploy**, so don't spend them on non-site changes. One-time setup: `netlify login` + `netlify link`. Logs: `journalctl --user -u skatespots-deploy`. Manual run: `scripts/auto-deploy.sh`. + - ⚠️ **Deploy throttle (2026-09-28):** production deploys are capped to one per **24h** by default (after a week of 68 deploys ≈ **1,020 credits** — a third of the monthly allowance). Batching a day's commits into one 15-credit deploy is the single biggest credit lever. Override with `SKATESPOTS_DEPLOY_MIN_INTERVAL=` (`0` disables); deploy on demand with **`scripts/auto-deploy.sh --force`**. A throttled tick deliberately records nothing, so the pending change still deploys on a later tick; a **failed** deploy doesn't set the clock, so it retries immediately. - ⚠️ Shakespeare's "Deploy" rebuilds **its own workspace**, which is NOT updated by git pushes — so pushing to the repo does not change what Shakespeare deploys. Prefer the auto-deploy timer (or drag `dist/` to Netlify). - **Web (manual)**: `npm run build` produces the web `dist/` (with `_headers`/`_redirects`); deploy by dragging `dist/` to the Netlify site's Deploys tab. - **Zapstore (Android)**, from the repo root: `SIGN_WITH='bunker://...' zsp publish zapstore.yaml` diff --git a/scripts/auto-deploy.sh b/scripts/auto-deploy.sh index 670aa88..fd78e15 100755 --- a/scripts/auto-deploy.sh +++ b/scripts/auto-deploy.sh @@ -23,8 +23,19 @@ 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" +INTERVAL_FILE="$STATE_DIR/last-deploy-epoch" mkdir -p "$STATE_DIR" +# Production deploys cost 15 Netlify credits each, and this timer polls every +# ~3 minutes — so a busy session can spend hundreds of credits on deploys. Enforce +# a minimum gap between successful production deploys (default 24h). Override with +# SKATESPOTS_DEPLOY_MIN_INTERVAL= (0 disables). To deploy immediately, +# bypassing the wait: scripts/auto-deploy.sh --force +FORCE=0 +[ "${1:-}" = "--force" ] && FORCE=1 +MIN_INTERVAL="${SKATESPOTS_DEPLOY_MIN_INTERVAL:-86400}" +case "$MIN_INTERVAL" in ''|*[!0-9]*) MIN_INTERVAL=0 ;; esac + log() { echo "[$(date -Is)] $*"; } # Serialise runs (a slow deploy must not overlap the next timer tick). @@ -68,6 +79,19 @@ if [ -n "$LAST_SHA" ] && git cat-file -e "${LAST_SHA}^{commit}" 2>/dev/null; the fi fi +# 2c. Minimum interval between production deploys (15 credits each). On skip we do +# NOT record the SHA, so the pending change is still waiting next tick. +if [ "$FORCE" -eq 0 ] && [ "$MIN_INTERVAL" -gt 0 ]; then + LAST_EPOCH="$(cat "$INTERVAL_FILE" 2>/dev/null || echo 0)" + case "$LAST_EPOCH" in ''|*[!0-9]*) LAST_EPOCH=0 ;; esac + ELAPSED=$(( $(date +%s) - LAST_EPOCH )) + if [ "$ELAPSED" -lt "$MIN_INTERVAL" ]; then + WAIT_MIN=$(( (MIN_INTERVAL - ELAPSED + 59) / 60 )) + log "origin/main moved to ${REMOTE_SHA:0:9} but last deploy was $((ELAPSED / 60))m ago — batching (min ${MIN_INTERVAL}s, ~${WAIT_MIN}m left). Use --force to deploy now." + exit 0 + fi +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 @@ -106,4 +130,5 @@ log "deploying to Netlify…" netlify deploy --prod --dir=dist echo "$REMOTE_SHA" > "$SHA_FILE" +date +%s > "$INTERVAL_FILE" log "deployed ${REMOTE_SHA:0:9} ✓"