romele / git-trace public
62 commits · 1 branch · 1 tag · 1 contributor (top: Romele) · first commit 3d ago · last push 16m ago
doc/portal.md
# External management portal — billing, provisioning, SSO
git-trace deliberately ships **no** payment logic, no OAuth client,
no third-party SDK. The forge binary is the trust boundary: it
speaks HTTP/Smart-HTTP/SSH and owns one SQLite file. Everything
bureaucratic — signup funnels, Stripe, invoices, org management —
lives in a separate process that drives the forge from the outside.
This document is the contract between the two halves.
## The boundary
```
┌────────────────┐ subprocess ┌──────────────────┐
│ portal (any │ ─────────────► │ git-trace CLI │
│ language: │ or sqlite │ (same host, │
│ sh/python/node │ │ same volume) │
│ Stripe, OAuth, │ ◄───────────── │ │
│ invoicing) │ exit codes └──────────────────┘
└────────────────┘
```
The portal **never** links against the forge and never shares its
memory. It has exactly two sanctioned interfaces:
1. **The CLI** (preferred). Every account mutation the portal needs
is already a subcommand:
| Portal action | Command |
|--------------------------|------------------------------------------------------|
| create account | `git-trace useradd --email=u@x alice` (pw on stdin) |
| reset password | `git-trace passwd alice` (pw on stdin) |
| issue API token | `git-trace mktoken alice portal` |
| change plan/tier | `git-trace tier alice pro +365` (`+days`, `YYYY-MM-DD`, or `never`) |
| downgrade | `git-trace tier alice free` |
| change quota | `git-trace quota alice 2048` (MiB; `0` = unlimited) |
| repo for a user | `git-trace mkrepo [--private] alice name "desc"` |
| SSH keys | `git-trace sshkey list alice`, `sshkey rm <id>` |
| inspect state | `git-trace tier alice`, `git-trace quota alice` — or read-only SQL on `gt.db` |
Invoke the CLI as a dedicated OS user (same one the daemon runs
as, e.g. `git`), capture the exit code — `0` = done. This is the
*only* write path recommended for production: it applies the same
validation and audit trail as interactive use.
2. **Direct SQLite** (read-only, or carefully). `gt.db` is a plain
SQLite file; a portal may open it read-only for listings
(`SELECT name, tier, tier_expires_at FROM users`) — SQLite WAL
means readers never block the daemon. Writing directly is
possible but forfeits validation/audit; if you do it, keep writes
idempotent and understand you are bypassing `db_audit`.
## Billing flow (reference)
```
Stripe webhook ─► portal verifies signature ─► payment ok
└─► git-trace tier alice pro +365 (exit 0)
└─► git-trace quota alice 2048 (exit 0, MiB)
└─► portal sends "welcome" mail with
https://forge.example/register links (or pre-made account)
```
Subscription expiry needs no cron inside the forge: `tier_expires_at`
is already enforced by `auth.c` — when the clock passes the column,
the user reads as `free`. A nightly `git-trace tier <u> free` sweep
from cron is optional housekeeping, not a requirement.
## Shared authentication (optional)
If the portal and the forge must appear as one site, put a reverse
proxy in front:
```
browser ─► Caddy/Nginx ── / ─► git-trace serve :8081
└── /portal ─► portal :9000
```
- The forge must keep its own session cookie (`gt_session`) as the
authority for `/` — do **not** inject `X-User` headers into the
forge; it doesn't read them. Federation of identity happens at
account-provisioning time (the portal creates the account), not
per-request.
- `GIT_TRACE_BASE_URL` tells the forge its public origin so
same-origin POST checks keep working behind the proxy.
- TLS terminates at the proxy; the forge keeps listening on a
loopback-only port.
## Rules for portal authors
- Never expose `gt.db` on a writable network share.
- The CLI is the API: parse nothing but exit codes and stdout.
- Password changes still go through `/account` in the forge UI, or
`useradd`-equivalent flows owned by the portal — pick one to avoid
confusion.
- The `admin` flag (`git-trace useradd --admin`) is the "root of the
instance": grant it only through operator tooling, never from a
webhook.
- Audit: every CLI mutation lands in `audit_log` — reconcile it
from the portal side by diffing `users.tier` against your billing
records nightly.