git-trace

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.