RustForge

rustforge

RustForge: the read-only, cgit-style git web host serving this site

clone: git clone https://git.rustbelt.dev/rustforge
BranchCommit messageAuthorAge
mainMirror Foundry OS (/workspace/foundry) to RustForgeTyler Fowler1 hour ago

Recent commits

AgeCommit messageAuthorId
1 hour agoMirror Foundry OS (/workspace/foundry) to RustForgeTyler Fowler7ec421cbf6
1 hour agoRefresh screenshots with the RustForge nameTyler Fowler3951a9ce85
1 hour agoRename forge to RustForgeTyler Fowleraee55b07f0
2 hours agoAdd deployment for the RustForge containerTyler Fowler0cdbd22f2e
4 hours agoAdd screenshots and pad the binary-file note in diffsTyler Fowler1bfcd03e62
4 hours agoforge v0.1: read-only cgit-style git web hostTyler Fowler28ce798b92

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".

repo summary

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 clone hint, 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 clone over 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

URLPage
/repo index
/{repo}/summary
/{repo}/refsbranches and tags
/{repo}/log?h=main&ofs=50log, page 2
/{repo}/commit/{id}commit and diff (full or short id)
/{repo}/tree/{path}?h=v1.0directory
/{repo}/blob/{path}highlighted file
/{repo}/raw/{path}raw file
/{repo}/info/refs, /{repo}/git-upload-packused by git clone

How it works

A request goes through four steps:

  1. Routing (src/web.rs). axum matches the URL to a handler function, for example /{repo}/log goes to log(). 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.
  2. Reading git (src/git.rs). The handler opens the repo with git2 (bindings to libgit2) and collects plain structs such as CommitInfo and FileDiff. libgit2 calls block the thread, so they run in tokio::task::spawn_blocking and 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).
  3. 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, and javascript: 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.
  4. Cloning (src/smart_http.rs). Rather than reimplementing git's wire protocol, RustForge runs the real git upload-pack --stateless-rpc, which is what git's own git-http-backend does, 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-pack behind an SSH server with per-user keys (e.g. the russh crate, or authorized_keys with 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_url set, 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.