RustForge

rustforge

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

rustforge / src / main.rs
2027 bytes ยท raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
//! Entry point: read the config, start the web server.
//!
//! Usage: rustforge [CONFIG.toml] [--listen ADDR]

use rustforge::config::Config;
use rustforge::web::{self, AppState};
use std::path::PathBuf;

const USAGE: &str = "usage: rustforge [CONFIG.toml] [--listen ADDR]

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!("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("rustforge.toml");
    let mut listen_override = None;
    let mut args = std::env::args().skip(1);
    while let Some(arg) = args.next() {
        match arg.as_str() {
            "-h" | "--help" => {
                println!("{USAGE}");
                return Ok(());
            }
            "--listen" => listen_override = Some(args.next().ok_or("--listen needs an address")?),
            other if other.starts_with('-') => return Err(format!("unknown option {other}\n\n{USAGE}")),
            other => config_path = PathBuf::from(other),
        }
    }

    let mut config = Config::load(&config_path)?;
    if let Some(listen) = listen_override {
        config.server.listen = listen;
    }
    let repos = config.validate()?;
    let listen = config.server.listen.clone();

    let state = AppState::new(config.server, repos);
    let listener = tokio::net::TcpListener::bind(&listen)
        .await
        .map_err(|e| format!("cannot listen on {listen}: {e}"))?;
    eprintln!(
        "RustForge v{} serving {} repo(s) on http://{listen}/",
        env!("CARGO_PKG_VERSION"),
        state.repos.len()
    );

    axum::serve(listener, web::router(state))
        .with_graceful_shutdown(async {
            let _ = tokio::signal::ctrl_c().await;
        })
        .await
        .map_err(|e| e.to_string())
}