all repos — legit @ 48ed004f7e6813803d3e9f2d5ffcce98f41b2b75

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

docs: restructure and add agent guide, changelog

Rename license and readme to standard capitalised filenames. Add
AGENTS.md (codebase guide for AI agents) and CHANGELOG.md (Keep a
Changelog). Expand .gitignore with Go project defaults.

πŸ’˜ Generated with Crush

Assisted-by: DeepSeek-V3.2 (Thinking Mode) via Crush <crush@charm.land>
Maxwell Jensen 85795372+maxwelljens@users.noreply.github.com
Tue, 12 May 2026 11:32:10 +0200
commit

48ed004f7e6813803d3e9f2d5ffcce98f41b2b75

parent

5acac24dede0143e6415d83d94a66017fd3c2692

8 files changed, 689 insertions(+), 111 deletions(-)

jump to
M .gitignore.gitignore

@@ -1,2 +1,30 @@

+# Binaries for programs and plugins legit result +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Code coverage profiles and other test artifacts +*.out +coverage.* +*.coverprofile +profile.cov + +# Dependency directories (remove the comment below to include it) +# vendor/ + +# Go workspace file +go.work +go.work.sum + +# env file +.env + +# Honk +.crush
A AGENTS.md

@@ -0,0 +1,127 @@

+# legit β€” Agent Guide + +A Go git web frontend. Pronounced like a beret-wearing Frenchman. + +## Licensing + +See `NOTICE` in the project root for the full licensing breakdown. In short: + +- **Original code** by Anirudh Oppiliappan: MIT (see `license`) +- **Modifications by Maxwell Jensen** <maxwelljensen@posteo.net>: EUPL-1.2 + +The `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. + +## Essential Commands + +| Action | Command | +|--------|---------| +| Build | `go build -o legit` | +| Run | `go run . --config ./config.yaml` | +| Lint/Typecheck | `golangci-lint run` or `go vet ./...` | +| Tests | `go test ./...` | + +No Makefile. No `go generate` targets. No pre-existing test files. + +## Project Structure + +``` +β”œβ”€β”€ main.go # Entrypoint: flags, config, unveil, serve +β”œβ”€β”€ config/ +β”‚ └── config.go # YAML config struct + reader +β”œβ”€β”€ routes/ +β”‚ β”œβ”€β”€ routes.go # Handlers: Index, RepoIndex, RepoTree, FileContent, Log, Diff, Refs, Archive, ServeStatic +β”‚ β”œβ”€β”€ handler.go # Route registration via http.NewServeMux + git HTTP protocol multiplexing +β”‚ β”œβ”€β”€ template.go # Template rendering helpers, chroma syntax highlighting, file display +β”‚ β”œβ”€β”€ util.go # Helpers: getDisplayName, isGoModule, getDescription, isIgnored, isUnlisted, MIME helpers +β”‚ └── git.go # Git HTTP smart protocol: InfoRefs(), UploadPack() via exec'd git-upload-pack +β”œβ”€β”€ git/ +β”‚ β”œβ”€β”€ git.go # GitRepo struct: Open, Commits, LastCommit, FileContent, Tags, Branches, FindMainBranch, WriteTar +β”‚ β”œβ”€β”€ tree.go # FileTree listing, NiceTree struct +β”‚ β”œβ”€β”€ diff.go # Diff/NiceDiff structs, go-gitdiff parsing +β”‚ └── service/ +β”‚ β”œβ”€β”€ service.go # git-upload-pack exec wrapper, pack-line/flush wire protocol +β”‚ └── write_flusher.go # http.Flusher-aware writer for streaming smart protocol responses +β”œβ”€β”€ templates/ # Go html/template files (10 templates) +β”œβ”€β”€ static/ # style.css + legit.png favicon +β”œβ”€β”€ config.yaml # Default config +β”œβ”€β”€ contrib/ # Dockerfile, docker-compose.yml, systemd unit +β”œβ”€β”€ flake.nix # Nix flake for build + Docker image +└── .github/workflows/ # Docker build + push to ghcr.io on master/tags +``` + +## Application Architecture & Request Flow + +1. **`main.go`**: reads YAML config, calls `UnveilPaths()` (no-op on non-OpenBSD via build tags), creates `http.ServeMux` from `routes.Handlers()`, starts HTTP server. + +2. **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. + +3. **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. + +4. **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!"`. + +5. **Archive downloads** (`routes.go:Archive`): streams tar.gz on-the-fly via `GitRepo.WriteTar()` + `compress/gzip`. + +6. **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`. + +## Key Gotchas + +- **`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. +- **No template caching**: `template.ParseGlob` is called per-request in every handler. This is by design (hot-reload friendly) but a perf footgun. +- **`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. +- **`description` file**: per-repo descriptions are read from a plain `description` file at the repo root, not from git config. +- **`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. +- **Static path traversal**: `ServeStatic` goes through `securejoin` too β€” `{file}` pattern is safe. +- **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. +- **Docker build copies `config.yaml`** into the image. In production, mount a volume override. + +## Route Table + +``` +GET / β†’ Index +GET /static/{file} β†’ ServeStatic +GET /{name} β†’ Multiplex (detects git protocol) +POST /{name} β†’ Multiplex (push blocked) +GET /{name}/tree/{ref}/{rest...} β†’ RepoTree +GET /{name}/blob/{ref}/{rest...} β†’ FileContent +GET /{name}/log/{ref} β†’ Log +GET /{name}/archive/{file} β†’ Archive (only .tar.gz) +GET /{name}/commit/{ref} β†’ Diff +GET /{name}/refs/ β†’ Refs +GET /{name}/{rest...} β†’ Multiplex +POST /{name}/{rest...} β†’ Multiplex +``` + +## Config (`config.yaml`) + +| Field | Description | +|-------|-------------| +| `repo.scanPath` | Directory to scan for repos (flat, no subdirs in active path) | +| `repo.readme` | Ordered list of readme filenames to look for | +| `repo.mainBranch` | Ordered list of branch names to try as default | +| `repo.ignore` | Repo names to 404 | +| `repo.unlisted` | Repo names to hide from index (still accessible via URL) | +| `meta.syntaxHighlight` | Chroma style name for highlighting; empty = no highlight | +| `server.name` | Used for go-import meta tags and clone URLs | + +## Naming & Style Patterns + +- All handlers are methods on `deps struct { c *config.Config }` β€” the dependency injection pattern. +- Template data uses `map[string]any` (or `map[string]interface{}` β€” mixed style). +- Git repo interface: `git.Open(path, ref)` returns `*GitRepo`, then method calls on it. +- `DisplayName` = repo name with `.git` suffix stripped. +- Redundant `return` statements exist at the end of several handlers β€” existing style, don't "fix". +- The `service` package uses adapted code from `charmbracelet/soft-serve` and `sosedoff/gitkit`. + +## Testing + +No tests exist yet. The project has no test files. When writing tests: +- `git.Open()` needs an actual git repo on disk β€” use `git.PlainInit()` to create temp repos in test setup. +- Route tests need an `httptest.ResponseRecorder` + the `deps` struct with a test config pointing at a temp dir with repos. +- The `service` package exec's `git` binary β€” tests need `git` available in PATH. + +## Deploy + +- Run behind TLS-terminating proxy (nginx, relayd). +- Systemd unit at `contrib/legit.service` with `ProtectSystem=strict`. +- Docker images: `ghcr.io/icyphox/legit:{master,latest,vX.Y.Z}`. +- Docker compose at `contrib/docker-compose.yml` mounts repos, config, templates, and static from host.
A CHANGELOG.md

@@ -0,0 +1,56 @@

+# Changelog + +All notable changes to this project are documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic +Versioning](https://semver.org/spec/v2.0.0.html). + +## [0.2.5] β€” 2025-01-26 + +### Changed + +- CSS: font stack updated to system fonts, custom font features removed +- CSS: colours, dark mode refinements +- Routes: README content sanitised for non-Markdown files (uses &lt;pre&gt;) + +## [0.2.4] β€” 2024-10-06 + +### Added + +- Syntax highlighting via chroma with configurable style +(`meta.syntaxHighlight`) +- Unlisted repositories (`repo.unlisted`) +- Annotated lightweight tag support +- Docker build and push workflow (ghcr.io) +- Dockerfile and docker-compose.yml updates + +### Changed + +- Dependencies bumped +- README reworded with Docker image references + +## [0.2.3] β€” 2024-07-13 + +### Added + +- Archive download handler (tar.gz via `/{name}/archive/{file}`) +- Dark theme CSS +- Nix flake for reproducible builds and Docker image +- Repository ignore support (`repo.ignore`) +- `.git` extension stripped from display names + +### Changed + +- Routing switched to Go 1.22+ `net/http` pattern-based router +- Git HTTP smart protocol switched from go-git to system `git-upload-pack` +- Template updated with archive download links + +### Fixed + +- Raw file view accidentally removed code re-added +- `getDisplayName` now works correctly for repos with `.git` suffix + +[0.2.5]: https://git.icyphox.sh/legit/compare/v0.2.4...v0.2.5 +[0.2.4]: https://git.icyphox.sh/legit/compare/v0.2.3...v0.2.4 +[0.2.3]: https://git.icyphox.sh/legit/compare/v0.2.2...v0.2.3
A LICENCE.txt

@@ -0,0 +1,312 @@

+ The MIT License (MIT) + +Copyright (c) Anirudh Oppiliappan <x@icyphox.sh> + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +------------------------------------------------------------------------------- + + EUROPEAN UNION PUBLIC LICENCE v. 1.2 + EUPL Β© the European Union 2007, 2016 + +This European Union Public Licence (the β€˜EUPL’) applies to the Work (as defined +below) which is provided under the terms of this Licence. Any use of the Work, +other than as authorised under this Licence is prohibited (to the extent such +use is covered by a right of the copyright holder of the Work). + +The Work is provided under the terms of this Licence when the Licensor (as +defined below) has placed the following notice immediately following the +copyright notice for the Work: + + Licensed under the EUPL + +or has expressed by any other means his willingness to license under the EUPL. + +1. Definitions + +In this Licence, the following terms have the following meaning: + +- β€˜The Licence’: this Licence. + +- β€˜The Original Work’: the work or software distributed or communicated by the + Licensor under this Licence, available as Source Code and also as Executable + Code as the case may be. + +- β€˜Derivative Works’: the works or software that could be created by the + Licensee, based upon the Original Work or modifications thereof. This Licence + does not define the extent of modification or dependence on the Original Work + required in order to classify a work as a Derivative Work; this extent is + determined by copyright law applicable in the country mentioned in Article 15. + +- β€˜The Work’: the Original Work or its Derivative Works. + +- β€˜The Source Code’: the human-readable form of the Work which is the most + convenient for people to study and modify. + +- β€˜The Executable Code’: any code which has generally been compiled and which is + meant to be interpreted by a computer as a program. + +- β€˜The Licensor’: the natural or legal person that distributes or communicates + the Work under the Licence. + +- β€˜Contributor(s)’: any natural or legal person who modifies the Work under the + Licence, or otherwise contributes to the creation of a Derivative Work. + +- β€˜The Licensee’ or β€˜You’: any natural or legal person who makes any usage of + the Work under the terms of the Licence. + +- β€˜Distribution’ or β€˜Communication’: any act of selling, giving, lending, + renting, distributing, communicating, transmitting, or otherwise making + available, online or offline, copies of the Work or providing access to its + essential functionalities at the disposal of any other natural or legal + person. + +2. Scope of the rights granted by the Licence + +The Licensor hereby grants You a worldwide, royalty-free, non-exclusive, +sublicensable licence to do the following, for the duration of copyright vested +in the Original Work: + +- use the Work in any circumstance and for all usage, +- reproduce the Work, +- modify the Work, and make Derivative Works based upon the Work, +- communicate to the public, including the right to make available or display + the Work or copies thereof to the public and perform publicly, as the case may + be, the Work, +- distribute the Work or copies thereof, +- lend and rent the Work or copies thereof, +- sublicense rights in the Work or copies thereof. + +Those rights can be exercised on any media, supports and formats, whether now +known or later invented, as far as the applicable law permits so. + +In the countries where moral rights apply, the Licensor waives his right to +exercise his moral right to the extent allowed by law in order to make effective +the licence of the economic rights here above listed. + +The Licensor grants to the Licensee royalty-free, non-exclusive usage rights to +any patents held by the Licensor, to the extent necessary to make use of the +rights granted on the Work under this Licence. + +3. Communication of the Source Code + +The Licensor may provide the Work either in its Source Code form, or as +Executable Code. If the Work is provided as Executable Code, the Licensor +provides in addition a machine-readable copy of the Source Code of the Work +along with each copy of the Work that the Licensor distributes or indicates, in +a notice following the copyright notice attached to the Work, a repository where +the Source Code is easily and freely accessible for as long as the Licensor +continues to distribute or communicate the Work. + +4. Limitations on copyright + +Nothing in this Licence is intended to deprive the Licensee of the benefits from +any exception or limitation to the exclusive rights of the rights owners in the +Work, of the exhaustion of those rights or of other applicable limitations +thereto. + +5. Obligations of the Licensee + +The grant of the rights mentioned above is subject to some restrictions and +obligations imposed on the Licensee. Those obligations are the following: + +Attribution right: The Licensee shall keep intact all copyright, patent or +trademarks notices and all notices that refer to the Licence and to the +disclaimer of warranties. The Licensee must include a copy of such notices and a +copy of the Licence with every copy of the Work he/she distributes or +communicates. The Licensee must cause any Derivative Work to carry prominent +notices stating that the Work has been modified and the date of modification. + +Copyleft clause: If the Licensee distributes or communicates copies of the +Original Works or Derivative Works, this Distribution or Communication will be +done under the terms of this Licence or of a later version of this Licence +unless the Original Work is expressly distributed only under this version of the +Licence β€” for example by communicating β€˜EUPL v. 1.2 only’. The Licensee +(becoming Licensor) cannot offer or impose any additional terms or conditions on +the Work or Derivative Work that alter or restrict the terms of the Licence. + +Compatibility clause: If the Licensee Distributes or Communicates Derivative +Works or copies thereof based upon both the Work and another work licensed under +a Compatible Licence, this Distribution or Communication can be done under the +terms of this Compatible Licence. For the sake of this clause, β€˜Compatible +Licence’ refers to the licences listed in the appendix attached to this Licence. +Should the Licensee's obligations under the Compatible Licence conflict with +his/her obligations under this Licence, the obligations of the Compatible +Licence shall prevail. + +Provision of Source Code: When distributing or communicating copies of the Work, +the Licensee will provide a machine-readable copy of the Source Code or indicate +a repository where this Source will be easily and freely available for as long +as the Licensee continues to distribute or communicate the Work. + +Legal Protection: This Licence does not grant permission to use the trade names, +trademarks, service marks, or names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and +reproducing the content of the copyright notice. + +6. Chain of Authorship + +The original Licensor warrants that the copyright in the Original Work granted +hereunder is owned by him/her or licensed to him/her and that he/she has the +power and authority to grant the Licence. + +Each Contributor warrants that the copyright in the modifications he/she brings +to the Work are owned by him/her or licensed to him/her and that he/she has the +power and authority to grant the Licence. + +Each time You accept the Licence, the original Licensor and subsequent +Contributors grant You a licence to their contributions to the Work, under the +terms of this Licence. + +7. Disclaimer of Warranty + +The Work is a work in progress, which is continuously improved by numerous +Contributors. It is not a finished work and may therefore contain defects or +β€˜bugs’ inherent to this type of development. + +For the above reason, the Work is provided under the Licence on an β€˜as is’ basis +and without warranties of any kind concerning the Work, including without +limitation merchantability, fitness for a particular purpose, absence of defects +or errors, accuracy, non-infringement of intellectual property rights other than +copyright as stated in Article 6 of this Licence. + +This disclaimer of warranty is an essential part of the Licence and a condition +for the grant of any rights to the Work. + +8. Disclaimer of Liability + +Except in the cases of wilful misconduct or damages directly caused to natural +persons, the Licensor will in no event be liable for any direct or indirect, +material or moral, damages of any kind, arising out of the Licence or of the use +of the Work, including without limitation, damages for loss of goodwill, work +stoppage, computer failure or malfunction, loss of data or any commercial +damage, even if the Licensor has been advised of the possibility of such damage. +However, the Licensor will be liable under statutory product liability laws as +far such laws apply to the Work. + +9. Additional agreements + +While distributing the Work, You may choose to conclude an additional agreement, +defining obligations or services consistent with this Licence. However, if +accepting obligations, You may act only on your own behalf and on your sole +responsibility, not on behalf of the original Licensor or any other Contributor, +and only if You agree to indemnify, defend, and hold each Contributor harmless +for any liability incurred by, or claims asserted against such Contributor by +the fact You have accepted any warranty or additional liability. + +10. Acceptance of the Licence + +The provisions of this Licence can be accepted by clicking on an icon β€˜I agree’ +placed under the bottom of a window displaying the text of this Licence or by +affirming consent in any other similar way, in accordance with the rules of +applicable law. Clicking on that icon indicates your clear and irrevocable +acceptance of this Licence and all of its terms and conditions. + +Similarly, you irrevocably accept this Licence and all of its terms and +conditions by exercising any rights granted to You by Article 2 of this Licence, +such as the use of the Work, the creation by You of a Derivative Work or the +Distribution or Communication by You of the Work or copies thereof. + +11. Information to the public + +In case of any Distribution or Communication of the Work by means of electronic +communication by You (for example, by offering to download the Work from a +remote location) the distribution channel or media (for example, a website) must +at least provide to the public the information requested by the applicable law +regarding the Licensor, the Licence and the way it may be accessible, concluded, +stored and reproduced by the Licensee. + +12. Termination of the Licence + +The Licence and the rights granted hereunder will terminate automatically upon +any breach by the Licensee of the terms of the Licence. + +Such a termination will not terminate the licences of any person who has +received the Work from the Licensee under the Licence, provided such persons +remain in full compliance with the Licence. + +13. Miscellaneous + +Without prejudice of Article 9 above, the Licence represents the complete +agreement between the Parties as to the Work. + +If any provision of the Licence is invalid or unenforceable under applicable +law, this will not affect the validity or enforceability of the Licence as a +whole. Such provision will be construed or reformed so as necessary to make it +valid and enforceable. + +The European Commission may publish other linguistic versions or new versions of +this Licence or updated versions of the Appendix, so far this is required and +reasonable, without reducing the scope of the rights granted by the Licence. New +versions of the Licence will be published with a unique version number. + +All linguistic versions of this Licence, approved by the European Commission, +have identical value. Parties can take advantage of the linguistic version of +their choice. + +14. Jurisdiction + +Without prejudice to specific agreement between parties, + +- any litigation resulting from the interpretation of this License, arising + between the European Union institutions, bodies, offices or agencies, as a + Licensor, and any Licensee, will be subject to the jurisdiction of the Court + of Justice of the European Union, as laid down in article 272 of the Treaty on + the Functioning of the European Union, + +- any litigation arising between other parties and resulting from the + interpretation of this License, will be subject to the exclusive jurisdiction + of the competent court where the Licensor resides or conducts its primary + business. + +15. Applicable Law + +Without prejudice to specific agreement between parties, + +- this Licence shall be governed by the law of the European Union Member State + where the Licensor has his seat, resides or has his registered office, + +- this licence shall be governed by Belgian law if the Licensor has no seat, + residence or registered office inside a European Union Member State. + +Appendix + +β€˜Compatible Licences’ according to Article 5 EUPL are: + +- GNU General Public License (GPL) v. 2, v. 3 +- GNU Affero General Public License (AGPL) v. 3 +- Open Software License (OSL) v. 2.1, v. 3.0 +- Eclipse Public License (EPL) v. 1.0 +- CeCILL v. 2.0, v. 2.1 +- Mozilla Public Licence (MPL) v. 2 +- GNU Lesser General Public Licence (LGPL) v. 2.1, v. 3 +- Creative Commons Attribution-ShareAlike v. 3.0 Unported (CC BY-SA 3.0) for + works other than software +- European Union Public Licence (EUPL) v. 1.1, v. 1.2 +- QuΓ©bec Free and Open-Source Licence β€” Reciprocity (LiLiQ-R) or Strong + Reciprocity (LiLiQ-R+). + +The European Commission may update this Appendix to later versions of the above +licences without producing a new version of the EUPL, as long as they provide +the rights granted in Article 2 of this Licence and protect the covered Source +Code from exclusive appropriation. + +All other changes or additions to this Appendix require the production of a new +EUPL version.
A README.md

@@ -0,0 +1,165 @@

+<h1 align="center">legit</h1> + +<p align="center"> + <strong>A git web frontend written in Go.</strong> + <br> + Pronounced however you like; I prefer channeling my inner beret-wearing + Frenchman, and saying <em>"Oui, il est le git!"</em> +</p> + +![Codeberg Release](https://img.shields.io/gitea/v/release/maxwelljensen/legit?gitea_url=https%3A%2F%2Fcodeberg.org&style=for-the-badge) +![Codeberg License](assets/eupl-12-badge.svg) + +--- + +## What is legit? + +`legit` is a self-hosted git repository browser for the web. Point it at a +directory full of repos and it'll give you a clean, modern web interface for +browsing files, viewing commits, exploring branches and tags, and cloning over +HTTPS. + +It's not CGI. It's not Gitea. It's just a single binary that serves your repos. + +## Quick start + +```bash +# Build +go build -o legit + +# Run +./legit --config ./config.yaml +``` + +You'll need a `config.yaml` (see [Configuration](#configuration)) and at least +one bare git repo in your `scanPath`. + +## Features + +| | | +|---|--------------------| +| πŸ–₯️ | Browse repos, files, trees, and commits | +| 🌲 | File tree view with mode and size | +| πŸ“œ | Commit log with full diff output | +| 🏷️ | Tag and branch listing | +| πŸ“¦ | Archive downloads (tar.gz) | +| 🎨 | Syntax highlighting (chroma) | +| πŸͺ¨ | Markdown readme rendering | +| πŸ”— | Cloning over HTTPS (smart HTTP protocol) | +| πŸ“„ | Templated HTML (fully customisable) | +| πŸŒ™ | Dark mode (CSS media query) | + +## How do I configure legit? + +Configuration is via `config.yaml`. By default it looks in the current +directory; use `--config <path>` to point elsewhere. + +```yaml +repo: + scanPath: /var/www/git + readme: + - README.md + - README + mainBranch: + - master + - main + ignore: + - foo + unlisted: + - private-repo +dirs: + templates: ./templates + static: ./static +meta: + title: git good + description: come get your free software + syntaxHighlight: monokailight +server: + name: git.example.com + host: 127.0.0.1 + port: 5555 +``` + +| Field | Description | +|-------|-------------| +| `repo.scanPath` | Directory containing repos (flat β€” subdirectories are not traversed) | +| `repo.readme` | Readme filenames to look for (first match wins) | +| `repo.mainBranch` | Branch names to try as default branch | +| `repo.ignore` | Repos to exclude entirely (returns 404) | +| `repo.unlisted` | Repos to hide from the index (still accessible by URL) | +| `dirs.templates` | Path to custom Go html/template files | +| `dirs.static` | Path to custom static assets (CSS, images) | +| `meta.syntaxHighlight` | [Chroma style](https://swapoff.org/chroma/playground/) for syntax highlighting; empty = no highlighting | +| `server.name` | Used for `go-import` meta tags and clone URLs | + +## What about cloning? + +Cloning works over HTTPS via git's smart HTTP protocol. + +``` +git clone https://git.example.com/my-repo +``` + +**Things to know:** +- Cloning only works with **bare repos** (a limitation of git itself β€” non-bare + repos still display fine in the web UI). +- Pushing over HTTPS is deliberately **disabled**. Use SSH. +- Run legit behind a TLS-terminating proxy (nginx, relayd, etc.). + +## How does one host it? + +```bash +# Docker +docker run -p 5555:5555 \ + -v /var/www/git:/var/www/git \ + ghcr.io/icyphox/legit:latest + +# systemd (see contrib/legit.service) +systemctl enable --now legit +``` + +Pre-built Docker images are available at +`ghcr.io/icyphox/legit:{master,latest,vX.Y.Z}`. + +## Building + +```bash +go build -o legit +``` + +No external dependencies beyond the Go toolchain. A Nix flake is also available +for reproducible builds (`nix build`). + +## How does it work? + +``` +HTTP request + ↓ + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ main.go β”‚ Parse config, unveil(2) on OpenBSD + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + ↓ + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ routes β”‚ Route requests: web UI or git smart protocol + β”‚ handler.go β”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + ↓ + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ git/ β”‚ Read repos via go-git, exec git-upload-pack + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + ↓ + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ templates β”‚ Render HTML via Go html/template + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +The `/{name}` endpoint is a **multiplexer**: it detects git HTTP protocol +requests (`info/refs?service=git-upload-pack`, `git-upload-pack` POST) and +routes them to the git backend, or renders the web UI otherwise. + +## Licence + +Original code (c) Anirudh Oppiliappan β€” MIT. +Modifications (c) Maxwell Jensen β€” [European Union Public Licence 1.2](https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12). + +See `license` and `NOTICE` for details.
A assets/eupl-12-badge.svg

@@ -0,0 +1,1 @@

+<svg xmlns="http://www.w3.org/2000/svg" width="175.75" height="28" role="img" aria-label="LICENCE: EUPL 1.2"><title>LICENCE: EUPL 1.2</title><a target="_blank" href="https://interoperable-europe.ec.europa.eu/collection/eupl/eupl-text-eupl-12"><g shape-rendering="crispEdges"><rect width="92.75" height="28" fill="#555"/><rect x="92.75" width="83" height="28" fill="#4c1"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="100"><image x="9" y="7" width="14" height="14" href="data:image/svg+xml;base64,PHN2ZyBmaWxsPSJ3aGl0ZXNtb2tlIiByb2xlPSJpbWciIHZpZXdCb3g9IjAgMCAyNCAyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48dGl0bGU+RXVyb3BlYW4gVW5pb248L3RpdGxlPjxwYXRoIGQ9Ik0xMS4zNzMgMS45NCAxMC4zNiAxLjJoMS4yNTNMMTIgMGwuMzg3IDEuMmgxLjI1M2wtMS4wMTMuNzQuMzg2IDEuMjA3TDEyIDIuNGwtMS4wMTMuNzQ3Wm0xLjI1NCAyMC44Ni4zODYgMS4yTDEyIDIzLjI2bC0xLjAxMy43NC4zODYtMS4yLTEuMDEzLS43NGgxLjI1M0wxMiAyMC44NTNsLjM4NyAxLjIwN2gxLjI1M1pNMS42NCAxMi44bC0xLjAxMy43NDcuMzg2LTEuMkwwIDExLjYyN2gxLjI1M2wuMzg3LTEuMi4zODcgMS4yaDEuMjZsLTEuMDIuNzQ2LjM4NiAxLjItMS4wMTMtLjc0NlptNS44MDctOS40NjcuMzg2IDEuMkw2LjgyIDMuOGwtMS4wMTMuNzQuMzg2LTEuMkw1LjE4IDIuNmgxLjI1M2wuMzg3LTEuMi4zODcgMS4ySDguNDZabS00Ljc4IDMuMDguMzg2LTEuMi4zOTQgMS4yaDEuMjJsLTEuMDE0Ljc0Ny4zODcgMS4yLTEuMDItLjc0N0wyIDguMzZsLjM4Ny0xLjItMS4wMTQtLjc0N1pNMS4zODcgMTYuODRoMS4yOGwuMzg2LTEuMi4zOTQgMS4yaDEuMjJsLTEuMDE0Ljc0Ny4zODcgMS4yLTEuMDItLjc0LTEuMDIuNzQuMzg3LTEuMi0xLjAxNC0uNzQ3Wm00LjgwNiA0LjU2LTEuMDEzLS43MzNoMS4yNTNsLjM4Ny0xLjIuMzg3IDEuMkg4LjQ2bC0xLjAxMy43MzMuMzg2IDEuMi0xLjAxMy0uNzQtMS4wMTMuNzRabTE2Ljc5NC05LjAyNy4zODYgMS4yLTEuMDEzLS43NDYtMS4wMjcuNzQ2LjM4Ny0xLjItMS4wMi0uNzQ2SDIybC4zODctMS4yLjM4NiAxLjJIMjRabS02LjQzNC05LjA0TDE1LjU0IDIuNmgxLjI1M2wuMzg3LTEuMi4zODcgMS4yaDEuMjUzbC0xLjAxMy43MzMuMzg2IDEuMkwxNy4xOCAzLjhsLTEuMDEzLjc0LjM4Ni0xLjJabTQgMy4wNzQuMzk0LTEuMi4zODYgMS4yaDEuMjU0bC0uOTg3Ljc1My4zODcgMS4yLTEuMDE0LS43NDctMS4wMi43NDcuMzg3LTEuMi0xLjAwNy0uNzQ3Wm0uNzggMTAuNDMzaDEuMjU0bC0uOTg3Ljc0Ny4zODcgMS4yLTEuMDE0LS43NC0xLjAyLjc0LjM4Ny0xLjItMS4wMDctLjc0N2gxLjI1NGwuMzkzLTEuMi4zODcgMS4yem0tMi41MTMgMy44MjctMS4wMTMuNzMzLjM4NiAxLjItMS4wMTMtLjc0LTEuMDEzLjc0LjM4Ni0xLjItMS4wMTMtLjczM2gxLjI1M2wuMzg3LTEuMi4zODcgMS4yeiIvPjwvc3ZnPg=="/><text transform="scale(.1)" x="548.75" y="175" textLength="517.5" fill="#fff">LICENCE</text><text transform="scale(.1)" x="1342.5" y="175" textLength="590" fill="#fff" font-weight="bold">EUPL 1.2</text></g></a></svg>
D license

@@ -1,23 +0,0 @@

-The MIT License (MIT) - -Copyright (c) Anirudh Oppiliappan <x@icyphox.sh> - -Permission is hereby granted, free of charge, to any person obtaining a -copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be included -in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -
D readme

@@ -1,88 +0,0 @@

-legit ------ - -A git web frontend written in Go. - -Pronounced however you like; I prefer channeling my inner beret-wearing -Frenchman, and saying "Oui, il est le git!" - -But yeah it's pretty legit, no cap on god fr fr. - - -FEATURES - -β€’ Fully customizable templates and stylesheets. -β€’ Cloning over http(s). -β€’ Less archaic HTML. -β€’ Not CGI. - - -INSTALLING - -Clone it, 'go build' it. - - -CONFIG - -Uses yaml for configuration. Looks for a 'config.yaml' in the current -directory by default; pass the '--config' flag to point it elsewhere. - -Example config.yaml: - - repo: - scanPath: /var/www/git - readme: - - readme - - README - - readme.md - - README.md - mainBranch: - - master - - main - ignore: - - foo - - bar - dirs: - templates: ./templates - static: ./static - meta: - title: git good - description: i think it's a skill issue - syntaxHighlight: monokailight - server: - name: git.icyphox.sh - host: 127.0.0.1 - port: 5555 - -These options are fairly self-explanatory, but of note are: - -β€’ repo.scanPath: where all your git repos live (or die). legit doesn't - traverse subdirs yet. -β€’ dirs: use this to override the default templates and static assets. -β€’ repo.readme: readme files to look for. -β€’ repo.mainBranch: main branch names to look for. -β€’ repo.ignore: repos to ignore, relative to scanPath. -β€’ repo.unlisted: repos to hide, relative to scanPath. -β€’ server.name: used for go-import meta tags and clone URLs. -β€’ meta.syntaxHighlight: this is used to select the syntax theme to render. If left - blank or removed, the native theme will be used. If an invalid theme is set in this field, - it will default to "monokailight". For more information - about themes, please refer to chroma's gallery [1]. - - -NOTES - -β€’ Run legit behind a TLS terminating proxy like relayd(8) or nginx. -β€’ Cloning only works in bare repos -- this is a limitation inherent to git. You - can still view non-bare repos just fine in legit. -β€’ Pushing over https, while supported, is disabled because auth is a - pain. Use ssh. -β€’ Paths are unveil(2)'d on OpenBSD. -β€’ Docker images are available ghcr.io/icyphox/legit:{master,latest,vX.Y.Z}. [2] - -LICENSE - -legit is licensed under MIT. - -[1]: https://swapoff.org/chroma/playground/ -[2]: https://github.com/icyphox/legit/pkgs/container/legit