docs/superpowers/specs/2026-03-30-git-collab-server-design.md
Ref: Size: 6.9 KiB
# git-collab-server Design
A minimal, self-contained git hosting server — "Gerrit lite" — companion to the git-collab CLI.
## Goals
- Web UI for read-only browsing of code and collab objects (patches, reviews, issues)
- HTTPS read-only clone (anonymous, no auth)
- SSH read/write clone (key-based auth)
- Single binary, zero external dependencies
- Multi-repo hosting (serve a directory of repos)
## Non-goals
- Web-based write actions (reviews, comments via browser)
- HTTPS push
- User accounts, permissions, or access control beyond SSH key auth
- Syntax highlighting in v1
## Architecture
### Binary
`git-collab-server` — a second `[[bin]]` in the existing Cargo workspace. Shares the `git-collab` lib crate to read patches, issues, reviews, events without duplication.
### Source Layout
```
src/
server/
main.rs ← entry point: parse config, start HTTP + SSH
config.rs ← parse collab-server.toml
http/
mod.rs ← axum router setup
repo_list.rs ← GET / — list repos
repo.rs ← GET /:repo/* — repo pages
git_http.rs ← git smart HTTP protocol (clone)
templates/ ← askama HTML templates
ssh/
mod.rs ← russh server setup
auth.rs ← authorized_keys parsing + key verification
session.rs ← exec handler (git-upload-pack / git-receive-pack)
```
### Dependencies
New dependencies (server-only, gated behind `[[bin]]`):
```toml
axum = "0.8"
tokio = { version = "1", features = ["full"] }
askama = "0.12"
russh = "0.44"
russh-keys = "0.44"
toml = "0.8"
tracing = "0.1"
tracing-subscriber = "0.3"
```
## Configuration
File: `collab-server.toml`, passed via `--config` flag.
```toml
repos_dir = "/srv/git"
http_bind = "0.0.0.0:8080"
ssh_bind = "0.0.0.0:2222"
authorized_keys = "/etc/git-collab-server/authorized_keys"
site_title = "my git server" # optional
```
## HTTP Layer
Two responsibilities on the same axum router.
### Web UI Routes
```
GET / → repo list
GET /:repo → repo overview (recent commits, open patches, open issues)
GET /:repo/commits → full commit log
GET /:repo/tree → file tree at HEAD
GET /:repo/tree/:ref/*path → browse directory
GET /:repo/blob/:ref/*path → file contents
GET /:repo/diff/:oid → single commit diff
GET /:repo/patches → patch list
GET /:repo/patches/:id → patch detail (revisions, reviews, inline comments)
GET /:repo/issues → issue list
GET /:repo/issues/:id → issue detail
```
### Git Smart HTTP (Clone Only)
```
GET /:repo.git/info/refs?service=git-upload-pack
POST /:repo.git/git-upload-pack
```
Spawns `git upload-pack --stateless-rpc` as a subprocess. No auth required. No push over HTTP.
### Rendering
Askama templates compiled at build time. Each route reads collab data via the existing lib (`patch::list`, `issue::list`, etc.) and git data via `git2`.
## SSH Layer
russh-based embedded SSH server.
### Auth
- Public key only. No passwords.
- Keys checked against the `authorized_keys` file. Custom parser (simple format: `ssh-ed25519 <base64-key> <comment>`, one per line).
- `#` lines and blanks ignored.
- File reloaded on SIGHUP — no restart needed to add/remove users.
### Session Exec
Only two commands allowed, anything else is rejected:
```
git-upload-pack '/path/to/repo' ← clone/fetch
git-receive-pack '/path/to/repo' ← push
```
Repo path argument validated against `repos_dir` to prevent path traversal — must resolve to a subdirectory of the configured repos root.
### Host Key
- Ed25519 key generated at first startup, saved to `<config_dir>/server_host_key`.
- Fingerprint printed on startup for operator verification.
### Push Semantics
Push lands commits on branches as normal git. Collab refs (`refs/collab/`) are pushed via `git-collab sync` as usual — the server treats them as normal refs with no special handling.
## Web UI Design
Sidebar navigation (Gerrit-style layout).
### Page Layout
```
┌─────────────────────────────────────────────┐
│ site_title repo_name │
├────────┬────────────────────────────────────┤
│ │ │
│ commits│ (main content area) │
│ tree │ │
│ ────── │ │
│ patches│ │
│ issues │ │
│ │ │
├────────┴────────────────────────────────────┤
│ git-collab-server │
└─────────────────────────────────────────────┘
```
- Sidebar sections: commits, tree, (divider), patches (open count badge), issues (open count badge)
- Active section highlighted
- Repo list page (`/`) has no sidebar — simple list with repo name, last commit date, description
### Styling
- Single embedded `<style>` in base template
- Dark theme, monospace for code, system fonts for nav
- No external CSS/JS CDN, no JavaScript dependencies
- Diffs rendered as colored `<pre>` blocks (green/red lines)
- File contents as plain `<pre>` (no syntax highlighting in v1)
## Repo Discovery
- Scans `repos_dir` for directories containing a `HEAD` file (bare repos) or `.git` subdirectory (non-bare)
- Re-scans on each request (cheap readdir, no watcher)
- Repo name = directory name, minus `.git` suffix if present
## Startup Sequence
1. Parse CLI args (`--config collab-server.toml`) via clap
2. Load and validate config (`repos_dir` must exist)
3. Generate or load SSH host key
4. Print host key fingerprint
5. Start HTTP and SSH servers concurrently via `tokio::select!`
6. Log: `HTTP listening on ..., SSH listening on ..., serving N repos from /path`
## Error Handling
- Invalid repo in URL → 404
- Git errors → 500 with simple error template
- SSH bad command → channel close with error message
- Bad config at startup → exit with clear message to stderr
## Logging
`tracing` crate for structured logs:
- Startup info (bind addresses, repo count, host key fingerprint)
- SSH auth events (accepted/rejected + key fingerprint)
- HTTP request log
## Auth Model
Two separate concerns:
- **SSH access:** Server-level `authorized_keys` file controls who can connect and push/pull over SSH.
- **Collab event signing:** Per-repo `.git/collab/trusted-keys` controls whose collab events (patches, reviews, issues) are trusted. This is unchanged from the CLI tool.