rustforge
RustForge: the read-only, cgit-style git web host serving this site
| author | Tyler Fowler <tyler@localhost> | 2026-10-31 18:19:33 -0700 |
|---|---|---|
| commit | aee55b07f0cb2997663861e939d9bd7f7f097f38 | |
| parent | 0cdbd22f2e0592b42ddbd472d3b22edf1d4c16db | |
| tree | browse files at this commit | |
Rename forge to RustForge
- crate and binary: rustforge; display name RustForge in page titles
("<page> - <repo> - RustForge"), the footer, error pages and the
default site_title
- config: forge.toml -> rustforge.toml; server paths /opt/rustforge,
/etc/rustforge/rustforge.toml, systemd unit rustforge.service, system
user rustforge (/srv/git unchanged)
- scripts: RUSTFORGE_* env vars, mirrors in /workspace/.rustforge-mirrors,
the served repo is now 'rustforge' (was rust-githost)
- README and DEPLOY.md updated, with a note on the migration| M | Cargo.lock | +19 -19 |
| M | Cargo.toml | +1 -1 |
| M | DEPLOY.md | +32 -21 |
| M | README.md | +12 -13 |
| R | deploy/forge.service → deploy/rustforge.service | +9 -9 |
| R | deploy/forge.toml.example → deploy/rustforge.toml.example | +4 -4 |
| R | forge.toml → rustforge.toml | +2 -2 |
| M | scripts/deploy.sh | +16 -16 |
| M | scripts/sync-mirrors.sh | +10 -10 |
| M | src/config.rs | +2 -1 |
| M | src/lib.rs | +1 -1 |
| M | src/main.rs | +8 -8 |
| M | src/pages.rs | +3 -2 |
| M | src/web.rs | +1 -1 |
| M | static/style.css | +1 -1 |
| M | tests/integration.rs | +10 -6 |
16 files changed, 131 insertions, 115 deletions
@@ -207,25 +207,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]]-name = "forge"-version = "0.1.0"-dependencies = [- "axum",- "flate2",- "git2",- "http-body-util",- "maud",- "pulldown-cmark",- "serde",- "syntect",- "tempfile",- "tokio",- "tokio-util",- "toml",- "tower",-]--[[package]] name = "form_urlencoded" version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index"@@ -637,6 +618,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" [[package]]+name = "rustforge"+version = "0.1.0"+dependencies = [+ "axum",+ "flate2",+ "git2",+ "http-body-util",+ "maud",+ "pulldown-cmark",+ "serde",+ "syntect",+ "tempfile",+ "tokio",+ "tokio-util",+ "toml",+ "tower",+]++[[package]] name = "rustix" version = "1.1.6" source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1,5 +1,5 @@ [package]-name = "forge"+name = "rustforge" version = "0.1.0" edition = "2021" description = "A small, read-only, cgit-style git web host"
@@ -1,8 +1,8 @@-# Deploying forge to RustForge+# Deploying RustForge -RustForge is a Debian 13 (trixie) Proxmox LXC on Hetzner:+The RustForge container is a Debian 13 (trixie) Proxmox LXC on Hetzner: `ssh -p 2222 rust@88.99.212.146`, private IP **172.16.10.10**.-forge listens on `0.0.0.0:8090` inside the container. TLS and the public ports+RustForge listens on `0.0.0.0:8090` inside the container. TLS and the public ports 80/443 belong to a separate proxy-manager container (e.g. Nginx Proxy Manager). ## Proxy manager setting@@ -15,25 +15,25 @@ http://172.16.10.10:8090 Scheme `http`, forward host `172.16.10.10`, port `8090`. Websockets and caching are not needed. Leave request buffering off if you can, so large clones-stream. Once the domain exists, set `clone_base_url` in `/etc/forge/forge.toml`-(it's a TODO there) and run `sudo systemctl restart forge`.+stream. Once the domain exists, set `clone_base_url` in `/etc/rustforge/rustforge.toml`+(it's a TODO there) and run `sudo systemctl restart rustforge`. ## Layout on the server | Path | What | Owner | |------|------|-------|-| `/opt/forge/bin/forge` | the binary, built on the dev box (CSS is compiled in, no static files needed) | root, 755 |-| `/opt/forge/README.md` | docs | root |-| `/etc/forge/forge.toml` | config (from `deploy/forge.toml.example`) | root:forge, 640 |-| `/etc/systemd/system/forge.service` | unit (from `deploy/forge.service`) | root |-| `/srv/git/<name>.git` | bare mirrors that forge serves | forge:forge |+| `/opt/rustforge/bin/rustforge` | the binary, built on the dev box (CSS is compiled in, no static files needed) | root, 755 |+| `/opt/rustforge/README.md` | docs | root |+| `/etc/rustforge/rustforge.toml` | config (from `deploy/rustforge.toml.example`) | root:rustforge, 640 |+| `/etc/systemd/system/rustforge.service` | unit (from `deploy/rustforge.service`) | root |+| `/srv/git/<name>.git` | bare mirrors that RustForge serves | rustforge:rustforge | -The service runs as the system user `forge` (no login shell, home `/srv/git`).+The service runs as the system user `rustforge` (no login shell, home `/srv/git`). It has `Restart=on-failure` and a read-only view of the whole filesystem (`ProtectSystem=strict`, `NoNewPrivileges`, `PrivateTmp`, no capabilities).-Logs go to `journalctl -u forge`.+Logs go to `journalctl -u rustforge`. -Packages installed on the server: `git` (forge runs `git upload-pack` for+Packages installed on the server: `git` (RustForge runs `git upload-pack` for clones), `ca-certificates`, `rsync` (receiving end of the mirror sync), `curl` (for checks), and `unattended-upgrades` (Debian security updates). @@ -41,17 +41,17 @@ clones), `ca-certificates`, `rsync` (receiving end of the mirror sync), `curl` ```sh sudo apt-get update && sudo apt-get install -y --no-install-recommends git ca-certificates rsync curl unattended-upgrades-sudo useradd --system --user-group --home-dir /srv/git --no-create-home --shell /usr/sbin/nologin forge-sudo install -d -m 755 -o forge -g forge /srv/git+sudo useradd --system --user-group --home-dir /srv/git --no-create-home --shell /usr/sbin/nologin rustforge+sudo install -d -m 755 -o rustforge -g rustforge /srv/git ``` -## Updating forge+## Updating RustForge From the dev box (it never builds on the server): ```sh scripts/deploy.sh # build --release, copy binary + unit, restart-scripts/deploy.sh --config # same, and overwrite /etc/forge/forge.toml from the example+scripts/deploy.sh --config # same, and overwrite /etc/rustforge/rustforge.toml from the example ``` ## Syncing the repos@@ -60,13 +60,13 @@ The working repos in `/workspace` must never get a git remote or be pushed from (other agents work in them). `scripts/sync-mirrors.sh` publishes them anyway: -1. It keeps a private bare mirror per repo in `/workspace/.forge-mirrors/<name>.git`+1. It keeps a private bare mirror per repo in `/workspace/.rustforge-mirrors/<name>.git` (`git clone --mirror` once, then `git remote update --prune` *inside the mirror*). Only branches and tags are published. 2. It rsyncs the mirror over ssh to `/srv/git/<name>.git`, objects first and refs second, so a half-finished sync never points at missing objects. The server keeps its own `config` and `hooks`.-3. It runs `chown -R forge:forge` on the server.+3. It runs `chown -R rustforge:rustforge` on the server. ```sh scripts/sync-mirrors.sh # all repos@@ -75,7 +75,7 @@ scripts/sync-mirrors.sh rust-kernel # just one It is idempotent, so run it whenever you want the site updated (or from cron). To add a repo, add `name=/path` to `REPOS` in the script and a `[[repo]]` entry-to `/etc/forge/forge.toml`, then restart forge. `/workspace/rustbelt-os` is+to `/etc/rustforge/rustforge.toml`, then restart rustforge. `/workspace/rustbelt-os` is already in the list and is skipped until it exists. ## Checks@@ -83,5 +83,16 @@ already in the list and is skipped until it exists. ```sh curl -s -o /dev/null -w '%{http_code}\n' localhost:8090/ git clone http://127.0.0.1:8090/rust-kernel /tmp/x-systemctl status forge+systemctl status rustforge ```++## History: rename from `forge` (Nov 2026)++The project started as `forge`. The rename to RustForge moved the following on the server:+`forge.service` → `rustforge.service`, `/opt/forge` → `/opt/rustforge`,+`/etc/forge/forge.toml` → `/etc/rustforge/rustforge.toml`, system user+`forge` → `rustforge`, and the served repo `rust-githost` → `rustforge`. The+old unit, binary, config, user and `/srv/git/rust-githost.git` were removed.+On the dev box the repo moved from `/workspace/rust-githost` (now a symlink)+to `/workspace/rustforge`, and the mirrors moved from `/workspace/.forge-mirrors`+to `/workspace/.rustforge-mirrors`.
@@ -1,11 +1,10 @@-# forge+# 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). -> `forge` is a placeholder name. To rename it, change `name` in `Cargo.toml`-> and the `forge::` imports in `src/main.rs` and `tests/integration.rs`.+> The crate and binary are called `rustforge`; the site shows "RustForge".  @@ -27,22 +26,22 @@ Screenshots: [index](docs/index.png), [summary](docs/summary.png), [commit diff] ```sh cargo build --release-./target/release/forge forge.toml # or: cargo run --release -- forge.toml+./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: `forge [CONFIG.toml] [--listen ADDR]`. The config path defaults to `forge.toml`.+Options: `rustforge [CONFIG.toml] [--listen ADDR]`. The config path defaults to `rustforge.toml`. Running it in the background: ```sh-nohup ./target/release/forge forge.toml > /tmp/githost.log 2>&1 &+nohup ./target/release/rustforge rustforge.toml > /tmp/rustforge.log 2>&1 & ``` ## Configuration -`forge.toml` lists the repos to serve. Nothing else on disk is reachable.+`rustforge.toml` lists the repos to serve. Nothing else on disk is reachable. ```toml [server]@@ -95,7 +94,7 @@ A request goes through four steps: - `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, forge runs the real `git upload-pack --stateless-rpc`, which is+ 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. @@ -108,7 +107,7 @@ are never served as HTML or SVG. ``` src/main.rs command line, starts the server src/lib.rs module list-src/config.rs forge.toml parsing and validation+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@@ -134,12 +133,12 @@ path traversal attempts. It then starts a real server and runs `git clone` ## Deployment -forge runs on the RustForge container behind a proxy manager. The binary is+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](DEPLOY.md), including the systemd unit (`deploy/forge.service`)-and the production config (`deploy/forge.toml.example`).+[DEPLOY.md](DEPLOY.md), including the systemd unit (`deploy/rustforge.service`)+and the production config (`deploy/rustforge.toml.example`). ## Roadmap @@ -149,4 +148,4 @@ and the production config (`deploy/forge.toml.example`). - **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 forge (or a cut-down build) on Rustbelt itself.+- **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.
@@ -1,27 +1,27 @@-# systemd unit for forge. Install to /etc/systemd/system/forge.service, then:-# sudo systemctl daemon-reload && sudo systemctl enable --now forge+# systemd unit for RustForge. Install to /etc/systemd/system/rustforge.service, then:+# sudo systemctl daemon-reload && sudo systemctl enable --now rustforge [Unit]-Description=forge - read-only cgit-style git web host-Documentation=file:///opt/forge/README.md+Description=RustForge - read-only cgit-style git web host+Documentation=file:///opt/rustforge/README.md After=network-online.target Wants=network-online.target [Service] Type=simple-User=forge-Group=forge-ExecStart=/opt/forge/bin/forge /etc/forge/forge.toml+User=rustforge+Group=rustforge+ExecStart=/opt/rustforge/bin/rustforge /etc/rustforge/rustforge.toml WorkingDirectory=/srv/git Environment=HOME=/srv/git Restart=on-failure RestartSec=2s # --- hardening --------------------------------------------------------------# forge only *reads* repos (git upload-pack never writes), so the whole+# RustForge only *reads* repos (git upload-pack never writes), so the whole # filesystem is read-only to it; /tmp is a private, empty tmpfs. NoNewPrivileges=true ProtectSystem=strict-ReadOnlyPaths=/srv/git /etc/forge /opt/forge+ReadOnlyPaths=/srv/git /etc/rustforge /opt/rustforge ProtectHome=true PrivateTmp=true PrivateDevices=true
@@ -1,4 +1,4 @@-# Production config for the RustForge container (copied to /etc/forge/forge.toml).+# Production config for the RustForge container (copied to /etc/rustforge/rustforge.toml). # Repos are bare mirrors kept up to date by scripts/sync-mirrors.sh. [server]@@ -18,6 +18,6 @@ path = "/srv/git/rust-kernel.git" description = "Rustbelt: a hobby x86_64 kernel written in Rust" [[repo]]-name = "rust-githost"-path = "/srv/git/rust-githost.git"-description = "forge: the read-only, cgit-style git web host serving this site"+name = "rustforge"+path = "/srv/git/rustforge.git"+description = "RustForge: the read-only, cgit-style git web host serving this site"
@@ -1,10 +1,10 @@-# forge configuration.+# RustForge configuration. # Only the repositories listed here are served. Paths may point at a bare repo # (foo.git) or a normal checkout (foo/ containing .git/). [server] listen = "127.0.0.1:8090"-site_title = "Tyler's git"+site_title = "RustForge" # Base URL shown in "git clone" hints. Leave unset to use the request's Host. # clone_base_url = "https://git.example.com"
@@ -1,29 +1,29 @@ #!/usr/bin/env bash-# Build forge here and install it on the server (never builds on the server).+# Build RustForge here and install it on the server (never builds on the server). # scripts/deploy.sh # binary + unit; keeps the server's config-# scripts/deploy.sh --config # also overwrite /etc/forge/forge.toml+# scripts/deploy.sh --config # also overwrite /etc/rustforge/rustforge.toml set -euo pipefail cd "$(dirname "$0")/.." -SSH_HOST="${FORGE_SSH_HOST:-rust@88.99.212.146}"-SSH_PORT="${FORGE_SSH_PORT:-2222}"+SSH_HOST="${RUSTFORGE_SSH_HOST:-rust@88.99.212.146}"+SSH_PORT="${RUSTFORGE_SSH_PORT:-2222}" SSH=(ssh -o BatchMode=yes -p "$SSH_PORT" "$SSH_HOST") SCP=(scp -q -o BatchMode=yes -P "$SSH_PORT") cargo build --release-"${SCP[@]}" target/release/forge deploy/forge.service deploy/forge.toml.example README.md "$SSH_HOST:/tmp/"+"${SCP[@]}" target/release/rustforge deploy/rustforge.service deploy/rustforge.toml.example README.md "$SSH_HOST:/tmp/" "${SSH[@]}" "set -e- sudo install -d -m 755 /opt/forge/bin- sudo install -m 755 /tmp/forge /opt/forge/bin/forge- sudo install -m 644 /tmp/README.md /opt/forge/README.md- sudo install -m 644 /tmp/forge.service /etc/systemd/system/forge.service- sudo install -d -m 755 /etc/forge- if [ '${1:-}' = '--config' ] || [ ! -f /etc/forge/forge.toml ]; then- sudo install -m 640 -o root -g forge /tmp/forge.toml.example /etc/forge/forge.toml+ sudo install -d -m 755 /opt/rustforge/bin+ sudo install -m 755 /tmp/rustforge /opt/rustforge/bin/rustforge+ sudo install -m 644 /tmp/README.md /opt/rustforge/README.md+ sudo install -m 644 /tmp/rustforge.service /etc/systemd/system/rustforge.service+ sudo install -d -m 755 /etc/rustforge+ if [ '${1:-}' = '--config' ] || [ ! -f /etc/rustforge/rustforge.toml ]; then+ sudo install -m 640 -o root -g rustforge /tmp/rustforge.toml.example /etc/rustforge/rustforge.toml fi- rm -f /tmp/forge /tmp/forge.service /tmp/forge.toml.example /tmp/README.md+ rm -f /tmp/rustforge /tmp/rustforge.service /tmp/rustforge.toml.example /tmp/README.md sudo systemctl daemon-reload- sudo systemctl enable --quiet forge- sudo systemctl restart forge+ sudo systemctl enable --quiet rustforge+ sudo systemctl restart rustforge sleep 1- systemctl is-active forge"+ systemctl is-active rustforge"
@@ -1,5 +1,5 @@ #!/usr/bin/env bash-# Mirror local working repos to the forge server, WITHOUT touching them.+# Mirror local working repos to the RustForge server, WITHOUT touching them. # # The working repos under /workspace have a strict "no remotes, no pushes" # rule (other agents work in them). So instead of pushing, this script:@@ -8,22 +8,22 @@ # (git clone --mirror the first time, git remote update afterwards; # the only remote that ever exists is inside the mirror itself), # 2. rsyncs that bare mirror over ssh to /srv/git/<name>.git on the server,-# 3. fixes ownership there to the `forge` user.+# 3. fixes ownership there to the `rustforge` user. # # Safe to run as often as you like (idempotent). Usage: # scripts/sync-mirrors.sh # sync every repo in the list below # scripts/sync-mirrors.sh rust-kernel # sync just one set -euo pipefail -SSH_HOST="${FORGE_SSH_HOST:-rust@88.99.212.146}"-SSH_PORT="${FORGE_SSH_PORT:-2222}"-MIRROR_DIR="${FORGE_MIRROR_DIR:-/workspace/.forge-mirrors}"-REMOTE_ROOT="${FORGE_REMOTE_ROOT:-/srv/git}"+SSH_HOST="${RUSTFORGE_SSH_HOST:-rust@88.99.212.146}"+SSH_PORT="${RUSTFORGE_SSH_PORT:-2222}"+MIRROR_DIR="${RUSTFORGE_MIRROR_DIR:-/workspace/.rustforge-mirrors}"+REMOTE_ROOT="${RUSTFORGE_REMOTE_ROOT:-/srv/git}" # name=source-path. Missing sources are skipped with a note. REPOS=( "rust-kernel=/workspace/rust-kernel"- "rust-githost=/workspace/rust-githost"+ "rustforge=/workspace/rustforge" "rustbelt-os=/workspace/rustbelt-os" ) @@ -60,19 +60,19 @@ sync_one() { fi # 2. Make sure the server-side bare repo exists (its own config and hooks).- "${SSH[@]}" "$SSH_HOST" "sudo test -d '$remote' || sudo -u forge git init --quiet --bare '$remote'"+ "${SSH[@]}" "$SSH_HOST" "sudo test -d '$remote' || sudo -u rustforge git init --quiet --bare '$remote'" # 3. Copy objects first, then refs/HEAD, so the server never sees a ref # pointing at an object that hasn't arrived yet. The server keeps its own # config/hooks/description (the mirror's config points at /workspace).- local rsync=(rsync -a --chown=forge:forge --rsync-path="sudo rsync" -e "${SSH[*]}")+ local rsync=(rsync -a --chown=rustforge:rustforge --rsync-path="sudo rsync" -e "${SSH[*]}") "${rsync[@]}" "$mirror/objects/" "$SSH_HOST:$remote/objects/" "${rsync[@]}" --delete \ --exclude=/config --exclude=/hooks/ --exclude=/description --exclude=/FETCH_HEAD \ "$mirror/" "$SSH_HOST:$remote/" # 4. Ownership (rsync --chown already does this; this is belt and braces).- "${SSH[@]}" "$SSH_HOST" "sudo chown -R forge:forge '$remote'"+ "${SSH[@]}" "$SSH_HOST" "sudo chown -R rustforge:rustforge '$remote'" log "$name: $(git -C "$mirror" rev-parse --abbrev-ref HEAD) @ $(git -C "$mirror" rev-parse --short HEAD) -> $SSH_HOST:$remote" }
@@ -45,7 +45,7 @@ fn default_listen() -> String { } fn default_title() -> String {- "git".to_string()+ "RustForge".to_string() } #[derive(Debug, Clone, Deserialize)]@@ -161,6 +161,7 @@ mod tests { fn defaults_apply() { let cfg = Config::from_toml("").unwrap(); assert_eq!(cfg.server.listen, "127.0.0.1:8090");+ assert_eq!(cfg.server.site_title, "RustForge"); assert!(cfg.repos.is_empty()); }
@@ -1,4 +1,4 @@-//! forge: a small, read-only, cgit-style git web host.+//! RustForge: a small, read-only, cgit-style git web host. //! //! Module map (read them in this order if you're new): //! - [`config`] – the TOML file that lists which repos to serve
@@ -1,27 +1,27 @@ //! Entry point: read the config, start the web server. //!-//! Usage: forge [CONFIG.toml] [--listen ADDR]+//! Usage: rustforge [CONFIG.toml] [--listen ADDR] -use forge::config::Config;-use forge::web::{self, AppState};+use rustforge::config::Config;+use rustforge::web::{self, AppState}; use std::path::PathBuf; -const USAGE: &str = "usage: forge [CONFIG.toml] [--listen ADDR]+const USAGE: &str = "usage: rustforge [CONFIG.toml] [--listen ADDR] -Serves the git repositories listed in CONFIG.toml (default: forge.toml)+Serves the git repositories listed in CONFIG.toml (default: rustforge.toml) as a read-only website, and allows `git clone` over HTTP."; #[tokio::main] async fn main() { if let Err(e) = run().await {- eprintln!("forge: {e}");+ eprintln!("rustforge: {e}"); std::process::exit(1); } } async fn run() -> Result<(), String> { // Tiny hand-rolled argument parsing; enough for two options.- let mut config_path = PathBuf::from("forge.toml");+ let mut config_path = PathBuf::from("rustforge.toml"); let mut listen_override = None; let mut args = std::env::args().skip(1); while let Some(arg) = args.next() {@@ -48,7 +48,7 @@ async fn run() -> Result<(), String> { .await .map_err(|e| format!("cannot listen on {listen}: {e}"))?; eprintln!(- "forge v{} serving {} repo(s) on http://{listen}/",+ "RustForge v{} serving {} repo(s) on http://{listen}/", env!("CARGO_PKG_VERSION"), state.repos.len() );
@@ -49,7 +49,8 @@ fn layout(title: &str, site_title: &str, body: Markup) -> Markup { meta charset="utf-8"; meta name="viewport" content="width=device-width, initial-scale=1"; meta name="color-scheme" content="light dark";- title { (title) }+ // e.g. "log - rust-kernel - RustForge"+ title { (title) @if title != site_title { " - " (site_title) } } link rel="stylesheet" href="/static/style.css"; link rel="stylesheet" href="/static/highlight.css"; }@@ -58,7 +59,7 @@ fn layout(title: &str, site_title: &str, body: Markup) -> Markup { a.site-title href="/" { (site_title) } } main { (body) }- footer { "generated by forge v" (env!("CARGO_PKG_VERSION")) }+ footer { "generated by RustForge v" (env!("CARGO_PKG_VERSION")) } } } }
@@ -148,7 +148,7 @@ impl From<git::Error> for AppError { impl IntoResponse for AppError { fn into_response(self) -> Response {- let page = pages::error("forge", self.status.as_u16(), &self.message);+ let page = pages::error("RustForge", self.status.as_u16(), &self.message); (self.status, Html(page.into_string())).into_response() } }
@@ -1,4 +1,4 @@-/* forge: plain, cgit-like styling. Light by default, dark when the OS asks. */+/* RustForge: plain, cgit-like styling. Light by default, dark when the OS asks. */ :root { --bg: #ffffff; --fg: #1f2328; --muted: #6a737d; --link: #0645ad; --border: #d0d7de; --head: #f6f8fa; --hover: #f3f6fa;
@@ -3,9 +3,9 @@ use axum::body::Body; use axum::http::{Request, StatusCode};-use forge::config::Config;-use forge::web::{router, AppState}; use http_body_util::BodyExt;+use rustforge::config::Config;+use rustforge::web::{router, AppState}; use std::path::Path; use std::process::Command; use tower::ServiceExt; // for `oneshot`@@ -47,7 +47,7 @@ fn fixture() -> Fixture { std::fs::write( work.join("README.md"),- "# Hello Forge\n\nSome **bold** text.\n\n<script>alert('readme')</script>\n",+ "# Hello Readme\n\nSome **bold** text.\n\n<script>alert('readme')</script>\n", ) .unwrap(); std::fs::create_dir(work.join("src")).unwrap();@@ -72,11 +72,11 @@ fn fixture() -> Fixture { Fixture { dir, head, change_commit } } -fn state_for(f: &Fixture) -> forge::web::Shared {+fn state_for(f: &Fixture) -> rustforge::web::Shared { let toml = format!( r#" [server]- site_title = "test forge"+ site_title = "test site" clone_base_url = "http://example.test" [[repo]]@@ -116,6 +116,10 @@ async fn index_lists_repos_and_escapes_descriptions() { assert!(body.contains("non-bare <b>repo</b>")); assert!(!body.contains("<b>repo</b>")); assert!(body.contains("ago"), "idle column should show an age");+ assert!(body.contains("<title>test site</title>"));+ assert!(body.contains("generated by RustForge v"));+ let (_, body) = get(&f, "/work/log").await;+ assert!(body.contains("<title>log - work - test site</title>"), "{body}"); } #[tokio::test]@@ -127,7 +131,7 @@ async fn summary_shows_refs_commits_and_safe_readme() { assert!(body.contains("feature/x"), "branch listed"); assert!(body.contains("v1.0"), "tag listed"); assert!(body.contains("change <greeting>"), "commit subject escaped");- assert!(body.contains("<h1>Hello Forge</h1>"), "README rendered");+ assert!(body.contains("<h1>Hello Readme</h1>"), "README rendered"); assert!(body.contains("<strong>bold</strong>")); assert!(!body.contains("<script>"), "raw HTML in README must not pass through"); assert!(body.contains(&format!("git clone http://example.test/{repo}")));