romele / git-trace public
62 commits · 1 branch · 1 tag · 1 contributor (top: Romele) · first commit 3d ago · last push 17m ago
doc/deploy.md
# git-trace — deployment manifesto
One binary, one SQLite file, the system `git` toolchain. Nothing else
is required at runtime: no JavaScript, no framework, no runtime VM.
Everything below assumes a stock Linux box (glibc or musl, systemd,
openssh-server, git ≥ 2.30).
**Fast path**: the `deploy/` directory is a ready-made bundle —
`install.sh <domain>` creates the `git` user, installs the binary,
wires a dedicated forge sshd on :2222 (admin sshd untouched), the
hardened systemd units and a nightly backup cron. The rest of this
document explains every step it performs and the manual equivalents.
--------------------------------------------------------------------
## 1. Build and install
```sh
apt install build-essential libsqlite3-dev git # Debian/Ubuntu
# dnf install gcc make sqlite-devel git # Fedora
git clone <this-repo> && cd git-trace
make # strict C11 build
./git-trace --version # sanity check
sudo install -m755 git-trace /usr/local/bin/git-trace
```
The binary is self-contained; `libsqlite3` and `libpthread` are the
only runtime libraries. `git`, `tar`, `sh` must be on `PATH` (they
are on every sane system). `curl` is optional and only used for
*HTTPS* webhook delivery — plain-HTTP endpoints work without it.
Create a dedicated system account that owns the data:
```sh
sudo useradd --system --home-dir /srv/git-trace --shell /usr/sbin/nologin git
sudo mkdir -p /srv/git-trace/{repos,var}
sudo chown -R git: /srv/git-trace
```
Layout: `/srv/git-trace/repos/<owner>/<name>.git` (bare repos),
`/srv/git-trace/var/git-trace.db` (SQLite: users, sessions, ACLs,
issues, MRs, patch sets, audit log, webhook queue, CI runs).
--------------------------------------------------------------------
## 2. The HTTP daemon (systemd)
`/etc/systemd/system/git-trace.service`:
```ini
[Unit]
Description=git-trace forge
After=network.target
[Service]
Type=simple
User=git
Environment=GIT_TRACE_REPOS=/srv/git-trace/repos
Environment=GIT_TRACE_DB=/srv/git-trace/var/git-trace.db
Environment=GIT_TRACE_BASE_URL=https://git.example.com
# Environment=GIT_TRACE_OPEN_REG=1 # public signup (optional)
ExecStart=/usr/local/bin/git-trace serve -p 8080
Restart=on-failure
RestartSec=2
# hardening — the daemon only ever writes under /srv/git-trace
NoNewPrivileges=true
ProtectSystem=strict
ReadWritePaths=/srv/git-trace /tmp
ProtectHome=true
PrivateTmp=true
ProtectKernelTunables=true
ProtectControlGroups=true
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
MemoryMax=1G
[Install]
WantedBy=multi-user.target
```
```sh
sudo systemctl daemon-reload
sudo systemctl enable --now git-trace
```
Notes:
- `GIT_TRACE_BASE_URL` **matters behind any reverse proxy**: browser
POSTs carry the proxy's public Origin while the daemon sees the
internal `Host`; without it form posts are refused with
`403 cross-origin request refused`. Set it to the public URL,
`https://git.example.com` — scheme and path are ignored, only
`host[:port]` is compared.
- The daemon speaks plain HTTP only. Never expose port 8080
directly to the internet; terminate TLS in front (§5).
- `GIT_TRACE_OPEN_REG=1` enables `/register`; leave it off for a
private instance and create users with `useradd`.
- `GIT_TRACE_NAME` sets the instance name shown on the landing page
and dashboard (default `git-trace`).
- Billing/signup portals belong **outside** the binary: drive the
CLI (`useradd`, `tier`, `quota`, `mktoken`) from a separate
service — see `doc/portal.md`.
--------------------------------------------------------------------
## 3. SSH transport (gitolite model)
One OS account (`git`) serves every forge user; public keys in the
database are rendered as forced commands bound to forge identities.
```sh
# generate the authorized_keys file from the DB
sudo -u git env GIT_TRACE_REPOS=/srv/git-trace/repos \
GIT_TRACE_DB=/srv/git-trace/var/git-trace.db \
git-trace keyfile --write
# writes /srv/git-trace/var/authorized_keys
```
Keys added or removed through the web UI (`/account` → ssh keys) or
the `sshkey` CLI regenerate this file automatically; `keyfile --write`
is only needed for the initial setup or after a restore.
`/etc/ssh/sshd_config.d/git-trace.conf`:
```
Match User git
AuthorizedKeysFile /srv/git-trace/var/authorized_keys
PasswordAuthentication no
AllowAgentForwarding no
AllowTcpForwarding no
PermitTunnel no
X11Forwarding no
```
Every generated line looks like:
```
command="GIT_TRACE_REPOS=… GIT_TRACE_DB=… \
/usr/local/bin/git-trace shell 7",no-pty,no-port-forwarding,… \
ssh-ed25519 AAAA…
```
so the key only ever reaches `git-trace shell`, which whitelists
`git-upload-pack`/`git-receive-pack`, applies the same `repo_perm`
ACL as HTTP, then `exec`s git. There is no shell access.
**After adding keys** (`/account` → ssh keys, or
`git-trace sshkey add …`), regenerate the file:
```sh
sudo -u git … git-trace keyfile --write
```
or use `AuthorizedKeysCommand` instead of a file (the command prints
the key list on every sshd lookup — slower but always current):
```
Match User git
AuthorizedKeysCommand /usr/local/bin/git-trace keyfile
AuthorizedKeysCommandUser git
```
Tell users the clone URL on each repo page:
`Environment=GIT_TRACE_SSH_URL=ssh://git@git.example.com`.
--------------------------------------------------------------------
## 4. Mail-in patches (MTA alias)
`git-trace mailrecv <owner> <repo>` reads one RFC822 message on
stdin, authenticates it (`X-Git-Trace-Token:` or registered From
address), parses the mbox and files a patch set.
Postfix — `/etc/aliases`:
```
patches-demo: "|/usr/local/bin/git-trace mailrecv alice demo"
```
then `newaliases`, and mail to `patches-demo@host` lands in the repo.
Exim equivalent: a `pipe` transport with `command = …`.
Per-user tokens come from `git-trace mktoken alice mail` (shown once)
or `/account` → api tokens; put it in the outbound message header.
Alternatively, register the sender's address on the account profile
— `From` matching alone authenticates.
--------------------------------------------------------------------
## 5. TLS termination
The daemon is HTTP-only; put a minimal proxy in front. Any of these
works — the only requirements are forwarding `Host` *or* setting
`GIT_TRACE_BASE_URL`, and not buffering request bodies (git push).
nginx:
```
server {
listen 443 ssl;
server_name git.example.com;
ssl_certificate /etc/letsencrypt/live/git.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/git.example.com/privkey.pem;
client_max_body_size 0; # pushes can be large
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_request_buffering off;
proxy_read_timeout 300s;
}
}
```
caddy — two lines:
```
git.example.com
reverse_proxy 127.0.0.1:8080
```
(Caddy forwards `Host` unchanged by default.)
--------------------------------------------------------------------
## 6. Backup
Two artifacts, both trivially copyable:
```sh
# atomic consistent snapshot of all state
sqlite3 /srv/git-trace/var/git-trace.db ".backup /srv/backups/gt-$(date +%F).db"
# the bare repositories — plain files, rsync-safe
rsync -a --delete /srv/git-trace/repos/ backup:/srv/git-trace/repos/
```
Cron the pair nightly; restore = drop the files back and start the
service. No dump formats, no export tooling.
--------------------------------------------------------------------
## 7. Upgrades
```sh
cd git-trace && git pull && make
sudo install -m755 git-trace /usr/local/bin/git-trace
sudo systemctl restart git-trace
# propagate new hooks (update, post-receive) to every repo:
sudo -u git env GIT_TRACE_REPOS=/srv/git-trace/repos \
GIT_TRACE_DB=/srv/git-trace/var/git-trace.db \
git-trace rehook
```
`rehook` is idempotent and safe to run after every upgrade; it
rewrites `update`/`post-receive` under each `*.git/hooks/` so pushes
pick up new branch-protection, webhook and CI logic immediately.
SQLite schema migrations run automatically at open.
--------------------------------------------------------------------
## 8. Day-one checklist
```sh
git-trace useradd --admin you # password on stdin
git-trace mkrepo you infrastructure # first repo
git-trace keyfile --write # sshd keys
systemctl enable --now git-trace ssh
```
Then in the browser: `/` lists repos, `/account` manages SSH keys,
API tokens, email (mail-in auth) and password; `/new` creates repos;
per-repo `settings` exposes ACLs, branch rules, webhooks, CI.
Remember: CI (`ci_enabled`) executes repository-controlled code with
daemon privileges — enable it only on repositories whose writers you
trust, and consider running the daemon under a dedicated uid as
shown above.