rustforge
RustForge: the read-only, cgit-style git web host serving this site
git clone https://git.rustbelt.dev/rustforge| Branch | Commit message | Author | Age |
|---|---|---|---|
| main | Mirror Foundry OS (/workspace/foundry) to RustForge | Tyler Fowler | 1 hour ago |
Recent commits
| Age | Commit message | Author | Id |
|---|---|---|---|
| 1 hour ago | Mirror Foundry OS (/workspace/foundry) to RustForge | Tyler Fowler | 7ec421cbf6 |
| 1 hour ago | Refresh screenshots with the RustForge name | Tyler Fowler | 3951a9ce85 |
| 1 hour ago | Rename forge to RustForge | Tyler Fowler | aee55b07f0 |
| 2 hours ago | Add deployment for the RustForge container | Tyler Fowler | 0cdbd22f2e |
| 4 hours ago | Add screenshots and pad the binary-file note in diffs | Tyler Fowler | 1bfcd03e62 |
| 4 hours ago | forge v0.1: read-only cgit-style git web host | Tyler Fowler | 28ce798b92 |
README.md
RustForge
A small, read-only, cgit-style git web host written in Rust. Version 0.1, built to be the self-hosted home for Tyler's hobby projects (starting with the Rustbelt kernel).
The crate and binary are called
rustforge; the site shows "RustForge".

What it does
- Repo index: every configured repo with its description and how long since the last commit.
- Summary: branches, tags, the 10 latest commits, a
git clonehint, and the README rendered from Markdown. - Log: 50 commits per page with newer/older links.
?h=<branch|tag|commit>picks where to start. - Commit: author, message, parents, a diffstat and a unified diff with green/red added/removed lines.
- Tree and blob: browse directories. Files get syntax highlighting and clickable line numbers (
#n42). - Raw: download any file as-is.
- Refs: all branches and tags.
git cloneover HTTP (smart protocol, v0 and v2). Pushing is refused.- Light and dark themes (follows your OS setting). No JavaScript anywhere.
Screenshots: index, summary, commit diff, commit diff, dark theme, blob view.
Quick start
cargo build --release
./target/release/rustforge rustforge.toml # or: cargo run --release -- rustforge.toml
# open http://127.0.0.1:8090/
git clone http://127.0.0.1:8090/rust-kernel
Options: rustforge [CONFIG.toml] [--listen ADDR]. The config path defaults to rustforge.toml.
Running it in the background:
nohup ./target/release/rustforge rustforge.toml > /tmp/rustforge.log 2>&1 &
Configuration
rustforge.toml lists the repos to serve. Nothing else on disk is reachable.
[server]
listen = "127.0.0.1:8090"
site_title = "Tyler's git"
# clone_base_url = "https://git.example.com" # shown in clone hints
[[repo]]
name = "rust-kernel" # URL: /rust-kernel/
path = "/workspace/rust-kernel" # a normal checkout or a bare foo.git
description = "Rustbelt: a hobby x86_64 kernel written in Rust"
git must be installed and on PATH, because cloning runs git upload-pack.
URLs
| URL | Page |
|---|---|
/ | repo index |
/{repo}/ | summary |
/{repo}/refs | branches and tags |
/{repo}/log?h=main&ofs=50 | log, page 2 |
/{repo}/commit/{id} | commit and diff (full or short id) |
/{repo}/tree/{path}?h=v1.0 | directory |
/{repo}/blob/{path} | highlighted file |
/{repo}/raw/{path} | raw file |
/{repo}/info/refs, /{repo}/git-upload-pack | used by git clone |
How it works
A request goes through four steps:
- Routing (
src/web.rs). axum matches the URL to a handler function, for example/{repo}/loggoes tolog(). Each handler first looks the repo name up in the config. Unknown names get a 404, which is why nothing outside the config can be served. - Reading git (
src/git.rs). The handler opens the repo with git2 (bindings to libgit2) and collects plain structs such asCommitInfoandFileDiff. libgit2 calls block the thread, so they run intokio::task::spawn_blockingand don't hold up other requests. File paths from the URL are never used on the real filesystem. They are looked up inside git's tree objects, after..and odd characters have been rejected (util::clean_repo_path). - Rendering HTML (
src/pages.rs). Pages are maud templates, which are Rust code checked at compile time. Every(value)is HTML-escaped automatically. The only pre-built HTML comes from:src/markdown.rs: READMEs rendered with pulldown-cmark. Raw HTML in the Markdown becomes text, andjavascript:links are removed.src/highlight.rs: syntect, which emits<span class="hl-…">with the text escaped. Colors come from generated CSS for a light and a dark theme.
- Cloning (
src/smart_http.rs). Rather than reimplementing git's wire protocol, RustForge runs the realgit upload-pack --stateless-rpc, which is what git's owngit-http-backenddoes, and pipes the HTTP request and response through it. upload-pack only reads, so a clone can't change the repo.
Every response also carries a strict Content-Security-Policy (no scripts at
all) and X-Content-Type-Options: nosniff as a second safety net. Raw files
are never served as HTML or SVG.
Project layout
src/main.rs command line, starts the server
src/lib.rs module list
src/config.rs rustforge.toml parsing and validation
src/web.rs routes + handlers
src/git.rs reading repos with libgit2
src/pages.rs HTML templates
src/markdown.rs README rendering
src/highlight.rs syntax highlighting
src/smart_http.rs git clone support
src/util.rs dates, ages, path/ref checks, URL encoding
static/style.css the stylesheet
tests/integration.rs builds temp repos, checks every page, does real clones
Tests
cargo test
The unit tests cover config parsing, path and ref checks, date formatting,
Markdown sanitizing, and highlighting. The integration test builds a normal and
a bare repo in a temp dir and checks every page type, pagination, escaping and
path traversal attempts. It then starts a real server and runs git clone
(protocol v0 and v2) plus a git push that must be refused.
Deployment
RustForge runs in the RustForge container behind a proxy manager. The binary is
built here and copied over (scripts/deploy.sh), and repos are published as bare
mirrors (scripts/sync-mirrors.sh), so the working repos never need a remote.
The proxy target is http://172.16.10.10:8090. Everything is in
DEPLOY.md, including the systemd unit (deploy/rustforge.service)
and the production config (deploy/rustforge.toml.example).
Roadmap
- Push over SSH:
git-receive-packbehind an SSH server with per-user keys (e.g. therusshcrate, orauthorized_keyswith a forced command). - Users and auth: accounts, private repos, per-repo permissions. Smart-HTTP push could then be added behind auth.
- Nicer browsing: blame, a branch/tag selector, commit search, diffs between any two refs, tarball snapshots, Atom feed of commits.
- Speed: cache rendered pages and the "idle" times, keyed by ref tips.
- Public deploy: a reverse proxy (Caddy or nginx) with TLS in front of
127.0.0.1:8090,clone_base_urlset, and a systemd unit. - Issues, later: a minimal issue tracker, or a mailing-list style flow.
- Self-hosting on Rustbelt: once the kernel has a TCP stack, sockets and
fork/exec, run RustForge (or a cut-down build) on Rustbelt itself.