all repos — legit @ bf654749b4fa39c80daa14d37496e8825fef5c09

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 ./cmd/legit` |
 19| Run | `go run ./cmd/legit --config ./config.yaml` |
 20| Release build | `goreleaser --snapshot --clean` |
 21| Lint/Typecheck | `golangci-lint run` or `go vet ./...` |
 22| Tests | `go test ./...` |
 23
 24No Makefile. No `go generate` targets. No pre-existing test files.
 25
 26## Project Structure
 27
 28```
 29├── cmd/legit/
 30│   ├── main.go             # Entrypoint: args (go-arg), config (viper), unveil, serve
 31│   ├── unveil.go           # OpenBSD unveil (build tag: openbsd)
 32│   └── unveil_stub.go      # Unveil no-op (build tag: !openbsd)
 33├── internal/
 34│   ├── config/
 35│   │   └── config.go       # YAML config struct + viper reader
 36│   ├── routes/
 37│   │   ├── routes.go       # Handlers: Index, RepoIndex, RepoTree, FileContent, Log, Diff, Refs, Archive, ServeStatic
 38│   │   ├── handler.go      # Route registration via http.NewServeMux + git HTTP protocol multiplexing
 39│   │   ├── template.go     # Template rendering helpers, chroma syntax highlighting, file display
 40│   │   ├── util.go         # Helpers: getDisplayName, isGoModule, getDescription, isIgnored, isUnlisted, MIME helpers
 41│   │   └── git.go          # Git HTTP smart protocol: InfoRefs(), UploadPack() via exec'd git-upload-pack
 42│   └── git/
 43│       ├── git.go          # GitRepo struct: Open, Commits, LastCommit, FileContent, Tags, Branches, FindMainBranch, WriteTar
 44│       ├── tree.go         # FileTree listing, NiceTree struct
 45│       ├── diff.go         # Diff/NiceDiff structs, go-gitdiff parsing
 46│       └── service/
 47│           ├── service.go  # git-upload-pack exec wrapper, pack-line/flush wire protocol
 48│           └── write_flusher.go  # http.Flusher-aware writer for streaming smart protocol responses
 49├── templates/              # Go html/template files (10 templates)
 50├── static/                 # style.css + legit.png favicon
 51├── assets/                 # EUPL badge SVG
 52├── config.yaml             # Default config
 53├── .goreleaser.yaml        # GoReleaser release build config
 54├── contrib/                # Dockerfile, docker-compose.yml, systemd unit
 55├── flake.nix               # Nix flake for build + Docker image
 56└── .github/workflows/      # Docker build + push to ghcr.io on master/tags
 57```
 58
 59## Application Architecture & Request Flow
 60
 611. **`cmd/legit/main.go`**: parses args via `go-arg`, reads config via `viper` (with `LEGIT_` env var overrides), calls `UnveilPaths()` (no-op on non-OpenBSD via build tags), creates `http.ServeMux` from `routes.Handlers()`, starts HTTP server. Version info (`legit --version`) injected via goreleaser ldflags.
 62
 632. **Routing** (`internal/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.
 64
 653. **Git operations** (`internal/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.
 66
 674. **Git HTTP smart protocol** (`internal/routes/git.go` + `internal/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!"`.
 68
 695. **Archive downloads** (`internal/routes/routes.go:Archive`): streams tar.gz on-the-fly via `GitRepo.WriteTar()` + `compress/gzip`.
 70
 716. **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`.
 72
 73## Key Gotchas
 74
 75- **`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.
 76- **No template caching**: `template.ParseGlob` is called per-request in every handler. This is by design (hot-reload friendly) but a perf footgun.
 77- **`description` file**: per-repo descriptions are read from a plain `description` file at the repo root, not from git config.
 78- **`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.
 79- **Static path traversal**: `ServeStatic` goes through `securejoin` too — `{file}` pattern is safe.
 80- **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.
 81- **Docker build copies `config.yaml`** into the image. In production, mount a volume override.
 82
 83## Route Table
 84
 85```
 86GET  /                                  → Index
 87GET  /static/{file}                     → ServeStatic
 88GET  /{name}                            → Multiplex (detects git protocol)
 89POST /{name}                            → Multiplex (push blocked)
 90GET  /{name}/tree/{ref}/{rest...}       → RepoTree
 91GET  /{name}/blob/{ref}/{rest...}       → FileContent
 92GET  /{name}/log/{ref}                  → Log
 93GET  /{name}/archive/{file}             → Archive (only .tar.gz)
 94GET  /{name}/commit/{ref}               → Diff
 95GET  /{name}/refs/                      → Refs
 96GET  /{name}/{rest...}                  → Multiplex
 97POST /{name}/{rest...}                  → Multiplex
 98```
 99
100## Config (`config.yaml`)
101
102| Field | Description |
103|-------|-------------|
104| `repo.scanPath` | Directory to scan for repos (flat, no subdirs in active path) |
105| `repo.readme` | Ordered list of readme filenames to look for |
106| `repo.mainBranch` | Ordered list of branch names to try as default |
107| `repo.ignore` | Repo names to 404 |
108| `repo.unlisted` | Repo names to hide from index (still accessible via URL) |
109| `meta.syntaxHighlight` | Chroma style name for highlighting; empty = no highlight |
110| `server.name` | Used for go-import meta tags and clone URLs |
111
112## Naming & Style Patterns
113
114- All handlers are methods on `deps struct { c *config.Config }` — the dependency injection pattern.
115- Template data uses `map[string]any` (or `map[string]interface{}` — mixed style).
116- Git repo interface: `git.Open(path, ref)` returns `*GitRepo`, then method calls on it.
117- `DisplayName` = repo name with `.git` suffix stripped.
118- Redundant `return` statements exist at the end of several handlers — existing style, don't "fix".
119- The `service` package uses adapted code from `charmbracelet/soft-serve` and `sosedoff/gitkit`.
120
121## Testing
122
123No tests exist yet. The project has no test files. When writing tests:
124- `git.Open()` needs an actual git repo on disk — use `git.PlainInit()` to create temp repos in test setup.
125- Route tests need an `httptest.ResponseRecorder` + the `deps` struct with a test config pointing at a temp dir with repos.
126- The `service` package exec's `git` binary — tests need `git` available in PATH.
127
128## Deploy
129
130- Run behind TLS-terminating proxy (nginx, relayd).
131- Systemd unit at `contrib/legit.service` with `ProtectSystem=strict`.
132- Docker images: `ghcr.io/<owner>/legit:{master,latest,vX.Y.Z}` (uses `${{ github.repository }}` from the workflow).
133- Docker compose at `contrib/docker-compose.yml` mounts repos, config, templates, and static from host.