all repos — legit @ dec130f2c26807b7c22e1762a6ca559fb66d14d3

Unnamed repository; edit this file 'description' to name the repository.

AGENTS.md (view raw)

  1# legit — Agent Guide
  2
  3A Go git web frontend. Pronounced like a beret-wearing Frenchman.
  4
  5## Licensing
  6
  7See `NOTICE` in the project root for the full licensing breakdown. In short:
  8
  9- **Original code** by Anirudh Oppiliappan: MIT (see `license`)
 10- **Modifications by Maxwell Jensen** <maxwelljensen@posteo.net>: EUPL-1.2
 11
 12The `license` file remains MIT. Each file's copyright and license is documented in `NOTICE` rather than file headers. New files should add an entry to `NOTICE` and may include a short comment header referencing it.
 13
 14## Essential Commands
 15
 16| Action | Command |
 17|--------|---------|
 18| Build | `go build -o legit` |
 19| Run | `go run . --config ./config.yaml` |
 20| Lint/Typecheck | `golangci-lint run` or `go vet ./...` |
 21| Tests | `go test ./...` |
 22
 23No Makefile. No `go generate` targets. No pre-existing test files.
 24
 25## Project Structure
 26
 27```
 28├── main.go               # Entrypoint: flags, config, unveil, serve
 29├── config/
 30│   └── config.go         # YAML config struct + reader
 31├── routes/
 32│   ├── routes.go         # Handlers: Index, RepoIndex, RepoTree, FileContent, Log, Diff, Refs, Archive, ServeStatic
 33│   ├── handler.go        # Route registration via http.NewServeMux + git HTTP protocol multiplexing
 34│   ├── template.go       # Template rendering helpers, chroma syntax highlighting, file display
 35│   ├── util.go           # Helpers: getDisplayName, isGoModule, getDescription, isIgnored, isUnlisted, MIME helpers
 36│   └── git.go            # Git HTTP smart protocol: InfoRefs(), UploadPack() via exec'd git-upload-pack
 37├── git/
 38│   ├── git.go            # GitRepo struct: Open, Commits, LastCommit, FileContent, Tags, Branches, FindMainBranch, WriteTar
 39│   ├── tree.go           # FileTree listing, NiceTree struct
 40│   ├── diff.go           # Diff/NiceDiff structs, go-gitdiff parsing
 41│   └── service/
 42│       ├── service.go    # git-upload-pack exec wrapper, pack-line/flush wire protocol
 43│       └── write_flusher.go  # http.Flusher-aware writer for streaming smart protocol responses
 44├── templates/            # Go html/template files (10 templates)
 45├── static/               # style.css + legit.png favicon
 46├── config.yaml           # Default config
 47├── contrib/              # Dockerfile, docker-compose.yml, systemd unit
 48├── flake.nix             # Nix flake for build + Docker image
 49└── .github/workflows/    # Docker build + push to ghcr.io on master/tags
 50```
 51
 52## Application Architecture & Request Flow
 53
 541. **`main.go`**: reads YAML config, calls `UnveilPaths()` (no-op on non-OpenBSD via build tags), creates `http.ServeMux` from `routes.Handlers()`, starts HTTP server.
 55
 562. **Routing** (`routes/handler.go`): uses Go 1.22+ pattern-based `http.NewServeMux` with `{name}`, `{ref}`, `{rest...}` path values. A key pattern — the root `/{name}` and `/{name}/{rest...}` handlers go through **`Multiplex()`** which detects git HTTP smart protocol requests (`info/refs?service=git-upload-pack`, `git-upload-pack` POST) and routes them to git backend, else falls through to `RepoIndex` for web rendering.
 57
 583. **Git operations** (`git/` package): uses `go-git/v5` (pinned to v5.6.1 via replace directive in go.mod). `git.Open(path, ref)` resolves refs to hashes and returns a `GitRepo`. File browsing, commit log, tree listing all use the in-memory go-git API.
 59
 604. **Git HTTP smart protocol** (`routes/git.go` + `git/service/`): shells out to `git-upload-pack --stateless-rpc` for clone/fetch over HTTPS. **Push is explicitly blocked** at the handler level with `"no pushing allowed!"`.
 61
 625. **Archive downloads** (`routes.go:Archive`): streams tar.gz on-the-fly via `GitRepo.WriteTar()` + `compress/gzip`.
 63
 646. **Templates**: all rendered via `template.ParseGlob(tpath)` per request (not cached — re-parsed every time). Templates are split into `define` blocks: `head`, `nav`, `repoheader`, `index`, `repo`, `tree`, `file`, `log`, `commit`, `refs`, `404`, `500`.
 65
 66## Key Gotchas
 67
 68- **`go.mod replace directives`**: `go-git/v5` is pinned to v5.6.1 (not v5.13.x as in go.sum) via a replace directive. `go-gitdiff` has `sergi/go-diff` replaced to v1.1.0. These overrides are intentional — don't remove them.
 69- **No template caching**: `template.ParseGlob` is called per-request in every handler. This is by design (hot-reload friendly) but a perf footgun.
 70- **`getAllRepos()` is unused** — leftover code that does recursive repo scanning (bare repo detection via HEAD file). The active Index handler reads repos at the top level of `scanPath` only.
 71- **`description` file**: per-repo descriptions are read from a plain `description` file at the repo root, not from git config.
 72- **`securejoin` everywhere**: all path construction from user input (repo name, file paths) uses `github.com/cyphar/filepath-securejoin` — a security-critical pattern. Never use raw `filepath.Join` with user input.
 73- **Static path traversal**: `ServeStatic` goes through `securejoin` too — `{file}` pattern is safe.
 74- **Build tags for unveil**: `unveil.go` is `//go:build openbsd`, `unveil_stub.go` is `//go:build !openbsd`. OpenBSD only: calls `unix.Unveil()` to restrict filesystem access.
 75- **Docker build copies `config.yaml`** into the image. In production, mount a volume override.
 76
 77## Route Table
 78
 79```
 80GET  /                                  → Index
 81GET  /static/{file}                     → ServeStatic
 82GET  /{name}                            → Multiplex (detects git protocol)
 83POST /{name}                            → Multiplex (push blocked)
 84GET  /{name}/tree/{ref}/{rest...}       → RepoTree
 85GET  /{name}/blob/{ref}/{rest...}       → FileContent
 86GET  /{name}/log/{ref}                  → Log
 87GET  /{name}/archive/{file}             → Archive (only .tar.gz)
 88GET  /{name}/commit/{ref}               → Diff
 89GET  /{name}/refs/                      → Refs
 90GET  /{name}/{rest...}                  → Multiplex
 91POST /{name}/{rest...}                  → Multiplex
 92```
 93
 94## Config (`config.yaml`)
 95
 96| Field | Description |
 97|-------|-------------|
 98| `repo.scanPath` | Directory to scan for repos (flat, no subdirs in active path) |
 99| `repo.readme` | Ordered list of readme filenames to look for |
100| `repo.mainBranch` | Ordered list of branch names to try as default |
101| `repo.ignore` | Repo names to 404 |
102| `repo.unlisted` | Repo names to hide from index (still accessible via URL) |
103| `meta.syntaxHighlight` | Chroma style name for highlighting; empty = no highlight |
104| `server.name` | Used for go-import meta tags and clone URLs |
105
106## Naming & Style Patterns
107
108- All handlers are methods on `deps struct { c *config.Config }` — the dependency injection pattern.
109- Template data uses `map[string]any` (or `map[string]interface{}` — mixed style).
110- Git repo interface: `git.Open(path, ref)` returns `*GitRepo`, then method calls on it.
111- `DisplayName` = repo name with `.git` suffix stripped.
112- Redundant `return` statements exist at the end of several handlers — existing style, don't "fix".
113- The `service` package uses adapted code from `charmbracelet/soft-serve` and `sosedoff/gitkit`.
114
115## Testing
116
117No tests exist yet. The project has no test files. When writing tests:
118- `git.Open()` needs an actual git repo on disk — use `git.PlainInit()` to create temp repos in test setup.
119- Route tests need an `httptest.ResponseRecorder` + the `deps` struct with a test config pointing at a temp dir with repos.
120- The `service` package exec's `git` binary — tests need `git` available in PATH.
121
122## Deploy
123
124- Run behind TLS-terminating proxy (nginx, relayd).
125- Systemd unit at `contrib/legit.service` with `ProtectSystem=strict`.
126- Docker images: `ghcr.io/icyphox/legit:{master,latest,vX.Y.Z}`.
127- Docker compose at `contrib/docker-compose.yml` mounts repos, config, templates, and static from host.