a73x

956ea42a

Add git-collab-server design spec

a73x   2026-03-30 16:52

Spec for a minimal self-contained git hosting server ("Gerrit lite"):
axum HTTP (web UI + smart HTTP clone), russh SSH (key-based push/pull),
askama templates, multi-repo hosting, sidebar nav web UI.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

diff --git a/docs/superpowers/specs/2026-03-30-git-collab-server-design.md b/docs/superpowers/specs/2026-03-30-git-collab-server-design.md
new file mode 100644
index 0000000..cc5868c
--- /dev/null
+++ b/docs/superpowers/specs/2026-03-30-git-collab-server-design.md
@@ -0,0 +1,204 @@
# 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.