Parent [>]
1mod cli;
2mod error;
3mod filters;
4mod routes;
5mod theme;
6
7use std::sync::OnceLock;
8
9use clap::Parser;
10use tokio::net::TcpListener;
11
12use crate::error::Result;
13use crate::theme::{BASE_CSS, MARKDOWN_CSS};
14
15/// Package name, sourced from `Cargo.toml` at compile time.
16pub const PKG_NAME: &str = env!("CARGO_PKG_NAME");
17
18/// Package version, sourced from `Cargo.toml` at compile time.
19pub const PKG_VERSION: &str = env!("CARGO_PKG_VERSION");
20
21/// Hostname used in breadcrumbs and clone URLs, set once from `--hostname`.
22static HOSTNAME: OnceLock<String> = OnceLock::new();
23
24/// SSH clone URL path prefix, set once from `--ssh-prefix`.
25static SSH_PREFIX: OnceLock<String> = OnceLock::new();
26
27/// Base URL path prefix for reverse proxy support, set once from `--base-url`.
28static BASE_URL: OnceLock<String> = OnceLock::new();
29
30/// Favicon bytes, loaded at startup from the default or a custom path.
31static FAVICON: OnceLock<Vec<u8>> = OnceLock::new();
32
33/// Returns the configured hostname, or `"index"` if none was provided.
34pub fn hostname() -> &'static str {
35    HOSTNAME.get().map_or("index", |s| s.as_str())
36}
37
38/// Returns the configured SSH prefix, or an empty string if none was provided.
39pub fn ssh_prefix() -> &'static str {
40    SSH_PREFIX.get().map_or("", |s| s.as_str())
41}
42
43/// Returns the configured base URL path prefix, or an empty string if none was
44/// provided.
45pub fn base_url() -> &'static str {
46    BASE_URL.get().map_or("", |s| s.as_str())
47}
48
49/// Returns the favicon bytes loaded at startup.
50pub fn favicon() -> &'static [u8] {
51    FAVICON.get().map_or(&[], |v| v.as_slice())
52}
53
54#[tokio::main]
55async fn main() -> Result<()> {
56    let args = cli::Args::parse();
57
58    if let Some(hostname) = args.hostname {
59        let _ = HOSTNAME.set(hostname);
60    }
61    if let Some(ssh_prefix) = args.ssh_prefix {
62        let _ = SSH_PREFIX.set(ssh_prefix);
63    }
64    if let Some(base_url) = args.base_url {
65        let _ = BASE_URL.set(base_url);
66    }
67
68    let favicon_data = match args.favicon {
69        Some(path) => std::fs::read(&path).unwrap_or_default(),
70        None => include_bytes!("../static/favicon.svg").to_vec(),
71    };
72    let _ = FAVICON.set(favicon_data);
73
74    // Concatenate base styles, markdown styles, and theme variables into a
75    // single stylesheet served at /style.css.
76    let theme_vars = match args.theme_file {
77        Some(path) => std::fs::read_to_string(path)?,
78        None => args.theme.css().to_owned(),
79    };
80    let css = format!("{theme_vars}\n{BASE_CSS}\n{MARKDOWN_CSS}");
81
82    let state = routes::AppState {
83        root: args.root,
84        css,
85    };
86
87    let router = routes::router(state);
88    let addr = format!("{}:{}", args.bind, args.port);
89    let socket = TcpListener::bind(&addr).await?;
90
91    axum::serve(socket, router).await?;
92
93    Ok(())
94}