Repository Public MIT Selected
1<!--2 ─────────────────────────────────────────────3 SPB Git — Personal Git Platform4 ─────────────────────────────────────────────5 Author : Simon-Pierre Boucher6 Contact : contact@spboucher.ai7 File : docs/DEPLOY.md8 Purpose : SPB Git 3.0 — deployment on M1M32 (MacLustr gateway), rollback, verification9 License : MIT © Simon-Pierre Boucher10 ─────────────────────────────────────────────11-->12# Deploying SPB Git to M1M32 (3.0 "Monograph")1314Production is the git clone at `M1M32:~/srv/spbgit/app`, run by PM2 as15`spbgit-server` (manifest `M1M32:~/dispatch/apps/spbgit.json`, pinned to16M1M32, published through the MacLustr Tunnel as https://git.spboucher.ai).17`origin` of that clone is spbgit itself (`https://git.spboucher.ai/spbgit.git`,18bare repo `~/srv/git/spbgit.git` on the same machine).1920## 0. Pre-flight (laptop)2122```bash23cd ~/Desktop/Cluster/projets/spbgit24npm run lint && npm run check:headers && npm test # 89 tests25python3 -I scripts/qa/screens.py http://127.0.0.1:7421 /tmp/qa --no-shots # dev server on 7421 → OK — 0 failing26python3 -I scripts/qa/axe.py http://127.0.0.1:7421 # → OK — 0 violation groups27git log --oneline -3 # 3.0.0 commit on main28git push origin main # → spbgit bare repo on M1M3229```3031## 1. Deploy (M1M32)3233```bash34ssh M1M3235export PATH=/opt/homebrew/bin:$PATH36cd ~/srv/spbgit/app37git fetch origin && git status # must be clean, on main38cp -a ~/srv/spbgit/cache ~/srv/spbgit/cache.bak-$(date +%Y%m%d) # optional: 2 GB, speeds up rollback39git pull --ff-only origin main40npm ci --omit=dev --no-audit --no-fund # no dependency change in 3.041node scripts/fetch-fonts.mjs # fonts are tracked in git since 3.0: must print "fonts: 13/13 present"42pm2 restart spbgit-server --update-env43pm2 logs spbgit-server --lines 20 --nostream44```45463.0 changes markup, not data: overviews and the search index are reused. The47calendar (`heatmap-v6.json`) is rebuilt on the first home request (a few48seconds once), and READMEs, highlighted blobs, diffs, insights, OG cards and49favicons are re-rendered lazily under new cache names (`readme-v3`, `md-v3`,50`hl-v3`, `commit-v4`, `insights-v2`, `og-v3`, `favicon-v3-*`). The 2.0 cache51files stay in place (a rollback is warm). (2.0 note: the first home request recomputed the overviews with the52cache shape `overview-v5.json`, `search-index-v2.json`; ~10 s once.) Old `overview-v4.json`/`heatmap-v4.json`53files are simply ignored (delete them with54`find ~/srv/spbgit/cache -name 'overview-v4.json' -delete` when convenient).5556### Recommended at the same time (operational, reversible)5758- **Memory limit**: the 2.0 server idles around 250–350 MB and no longer59 buffers blobs; keep `max_memory_restart: 750M` in the manifest but expect60 zero memory restarts. Check after 24 h: `pm2 describe spbgit-server | grep restarts`.61- **Log rotation**: `~/.pm2/logs/spbgit-server-out.log` was 599 MB.62 `pm2 install pm2-logrotate && pm2 set pm2-logrotate:max_size 50M && pm2 set pm2-logrotate:retain 7`,63 then `pm2 flush spbgit-server` once.64- **AI (optional, off by default)**: add to the manifest env65 `SPBGIT_AI_PROVIDER=anthropic`, `SPBGIT_AI_API_KEY=…` (server-side only),66 optionally `SPBGIT_AI_MODEL` (default `claude-opus-5-5`),67 `SPBGIT_AI_DAILY_BUDGET_TOKENS` (default 500 000). Or point at a local68 OpenAI-compatible server: `SPBGIT_AI_PROVIDER=openai-compatible`,69 `SPBGIT_AI_BASE_URL=http://<node>:<port>/v1`, `SPBGIT_AI_MODEL=…`.70 Every repository is public, so no private code is involved, but enabling a71 remote provider still sends repository excerpts and visitor questions to it —72 decide explicitly.7374## 2. Verify7576```bash77curl -s https://git.spboucher.ai/healthz | jq . # version 3.0.0, features, git concurrency78# (`cache` is null until the background footprint walk has finished; `cacheComputedAt` gives its age)79curl -s -o /dev/null -w '%{http_code} %{time_total}\n' https://git.spboucher.ai/80curl -s -o /dev/null -w '%{http_code}\n' https://git.spboucher.ai/spbgit/blob/main/src/server.mjs81curl -s "https://git.spboucher.ai/api/v1/search?q=server&scope=files&limit=3" | jq .files82curl -s https://git.spboucher.ai/api/v1/stats | jq '{commits, commitsLast52Weeks}' # commits ≥ commitsLast52Weeks83git ls-remote https://git.spboucher.ai/spbgit.git | head -2 # Smart HTTP advertisement84# from the laptop: a real clone + a push to a scratch repo with a PAT85```8687Then the manual Safari/iPhone checklist in `docs/SPBGIT-2-PROGRESS.md` (Phase 7).8889## 3. Rollback9091```bash92ssh M1M3293export PATH=/opt/homebrew/bin:$PATH94cd ~/srv/spbgit/app95git checkout 7864e4d # last 2.0.0 commit (use 810e402 for 1.1.0, v4 "Monolith")96node scripts/fetch-fonts.mjs # 2.0 fonts were git-ignored downloads: fetch them again97npm ci --omit=dev --no-audit --no-fund98pm2 restart spbgit-server --update-env99```100101Data is untouched by 2.0 (bare repos, `meta.json`, `activity.jsonl`,102`clones.json`, `tokens.json`, releases). 2.0 only *adds* `data/ai-usage.json`103(when AI is enabled) and new cache file names; 1.1.0 ignores both. The 1.1.0104caches (`overview-v4.json`, `heatmap-v4.json`, `search-index.json`) are left in105place by 2.0, so a rollback is warm immediately. Return to the current release with106`git checkout main`. 3.0 adds no data file at all: it only writes new cache names.107108## 4. What changed operationally109110| Item | 1.1.0 | 2.0.0 |111|---|---|---|112| `/raw/*` | whole blob buffered (≤ 64 MB) | streamed from `git cat-file`, `Content-Length` from `cat-file -s` |113| git subprocesses | unbounded | 12 concurrent (`SPBGIT_GIT_CONCURRENCY`) |114| Request logging | 2 lines per request (incl. assets) | 1 line per completed request, assets only on error |115| Compression | none in-process | brotli q4 / gzip 5 for text ≥ 1 KB (`@fastify/compress`) |116| Overview cache | per repo, busted by hook only | validated against ref stamps (HEAD, packed-refs, refs/heads, refs/tags mtimes) |117| New routes | — | `/about/metrics`, `/:repo/insights`, `/:repo/research`, `/api/v1/search`, `/api/v1/repos/:name/tree`, `/api/v1/ai/*` (404 unless enabled) |118| Dependencies | — | `@anthropic-ai/sdk`, `@fastify/compress`; dev: `axe-core` |119120## 5. Deployment record — 3.0.0 "Monograph" (2026-11-09)121122- Commits `c462ed4` (redesign) and `23a33be` (`/healthz` fix) pushed to spbgit, pulled on M1M32,123 `node scripts/fetch-fonts.mjs` → `fonts: 13/13 present`, `pm2 restart spbgit-server --update-env`.124 `npm ci` was skipped on purpose: the lock file differs from 2.0.0 by the version field only.125- Verified on https://git.spboucher.ai: `/healthz` (version 3.0.0, 183 repositories, answers in 2 ms),126 home, `/spbgit`, `/wp16_uqo`, `/wp9_uqo`, a blob, a commit diff, `/about/api`, `/about/metrics`,127 the ⌘K palette, at 1440 px and 390 px, light and dark (`docs/screenshots/v3/prod-*.png`);128 security headers and CSP unchanged; `git ls-remote` answers.129- The nine font files downloaded by 2.0 (`inter-400.woff2` … `jetbrains-mono-600.woff2`) are still in130 `src/web/assets/fonts/` on M1M32 as untracked files, so a rollback to 2.0 is immediate. Delete them131 once a rollback is no longer wanted.132- Rate limit reminder when checking by script: 200 requests per minute per IP, static assets and README133 images included — four loads of a badge-heavy README inside a minute return 429.134135### Known production issues that predate 3.0 (not addressed by the redesign)136137- **Memory restart loop.** Since at least 2026-11-08, PM2 restarts `spbgit-server` 10–30 times per hour138 because RSS passes `max_memory_restart` (750 MB) within one to three minutes of each start (RSS up to139 2 GB with a heap under 100 MB). The traffic at those moments is two crawlers walking every blob, tree140 and commit URL at historical SHAs (about 10 requests per second). 3.0 neither causes nor fixes it.141- **Cache growth.** `~/srv/spbgit/cache` weighs 75 GB (83 GB free on the volume), almost entirely142 per-SHA `hl-v2-*` highlight files produced by the same crawl; 3.0 writes `hl-v3-*` beside them.143 Once a rollback is ruled out, the 2.0 files can be removed:144 `find ~/srv/spbgit/cache/repos \( -name 'hl-v2-*' -o -name 'md-v2-*' -o -name 'commit-v3.json' -o -name 'readme-v2.json' -o -name 'insights-v1.json' \) -delete`.145 A lasting answer needs a policy for crawlers on SHA-pinned URLs (robots rules, a tighter rate limit,146 or not caching highlight output for non-branch refs) — an operational decision left to the owner.147