SPB Git
The personal git platform of Simon-Pierre Boucher
Self-hosted software forge · every repo public & clonable · owner-only writes
https://git.spboucher.ai
What it is
SPB Git is a complete, self-hosted GitHub-equivalent built for one person:
- Git hosting — Smart HTTP v2 (streamed
upload-pack/receive-pack), anonymous clone/fetch, PAT-authenticated push, post-receive hooks, zip/tar.gz snapshots. - Showcase web UI — SSR (Nunjucks), dark/light themes, pinned repos, activity feed, contribution heatmap, GitHub-grade README rendering (badges, mermaid, task lists, relative images), shiki-highlighted file browsing with line anchors, commits, diffs, blame, branches, tags, global search, OG cards, Atom feed, sitemap.
spbgitCLI — the terminal command center:init,token,list,create --push,clone --all,status,commit,push,pull,sync,open,info,rm,doctor.- JSON API — public reads, token-gated writes at
/api/v1/*.
Filesystem is the database. Bare repos under the git root are the source of truth; meta.json holds descriptions/topics/pins; everything expensive is cached per <repo>@<sha> and busted on push.
Quick start (development)
npm install # also fetches self-hosted fonts
cp .env.example .env # point the paths at ./dev/* for local work
npm run dev # http://127.0.0.1:7420Mint a first token, then wire up the CLI:
node --input-type=module -e "
import { loadConfig, ensureDirs } from './src/config.mjs';
import { TokenStore } from './src/auth/token.mjs';
const c = loadConfig(); ensureDirs(c);
console.log((await new TokenStore(c.dataDir).create('bootstrap')).token);
"
npm link # exposes `spbgit`
spbgit init # paste server URL + token
spbgit create hello --push # first repo, live immediatelyDeployment (node m3u96a)
# on m3u96a, from the app directory
bash deploy/setup-m3u96a.shThe script is idempotent: checks node ≥ 20 / git / ngrok / pm2, creates the
data prefix (/srv on Linux, ~/srv on macOS), installs production deps,
writes .env, prints the bootstrap PAT once, and starts both pm2 apps
(spbgit-server, spbgit-tunnel). Run pm2 startup once so everything
survives a reboot. DNS: CNAME git.spboucher.ai to the ngrok edge target
(see deploy/ngrok.yml). Nightly backups: cron deploy/backup.sh (keeps 14).
A systemd unit is provided as the documented alternative: deploy/spbgit.service.
Scripts
| Command | Purpose |
|---|---|
npm start / npm run dev |
run the server (dev = watch + template reload) |
npm test |
vitest — unit + full clone/push round-trip e2e |
npm run lint |
eslint |
npm run check:headers |
fail if any file lacks the author header |
npm run inject:headers |
add missing headers in bulk |
npm run fetch:fonts |
(re)download self-hosted Inter / JetBrains Mono |
Security model
- All repositories are public read-only; there is no private flag anywhere.
- Writes require a PAT (
spbgit_<id>_<secret>), stored argon2id-hashed, revocable, with last-used tracking. - Repo names match
^[a-z0-9][a-z0-9._-]{0,63}$; tree paths are traversal-checked twice (validation + prefix check). - Rendered Markdown is sanitized with a strict allowlist (badges,
<details>,<kbd>, align HTML survive; scripts, iframes, event handlers never do). /rawserves withnosniff+ sandbox CSP, and repo HTML is served astext/plain./internal/*hook routes accept loopback connections only.
License
MIT © Simon-Pierre Boucher