SPB Git

spb/spbgit Public MIT

SPB Git — the platform hosting itself

JavaScript 73.9% CSS 11.7% Nunjucks 11.6% Shell 2.7%
5.0 KB

# SPB Git

The personal git platform of Simon-Pierre Boucher

node license runtime deploy

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.
  • spbgit CLI — the terminal command center: init, token, list, create --push, clone --all, status, commit, push, pull, sync, open, info, rm, doctor, release.
  • Releases — GitHub-style binary assets (dmg, pkg, zip…) attached to tags, streamed to disk with sha256 sidecars, public downloads at /releases/<repo>/<tag>/<file>:
    bash
    git tag v1.0.0 && git push --tags
    spbgit release upload myapp v1.0.0 MyApp.dmg   # → https://git.spboucher.ai/releases/myapp/v1.0.0/MyApp.dmg
    spbgit release list myapp
  • 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)

bash
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:7420

Mint a first token, then wire up the CLI:

bash
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 immediately

# Deployment (node m3u96a)

bash
# on m3u96a, from the app directory
bash deploy/setup-m3u96a.sh

The 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).
  • /raw serves with nosniff + sandbox CSP, and repo HTML is served as text/plain.
  • /internal/* hook routes accept loopback connections only.

# License

MIT © Simon-Pierre Boucher