main.rs
1 use std::num::NonZeroUsize; 2 use std::{collections::HashMap, process}; 3 4 use axum_listener::DualAddr; 5 use radicle::prelude::RepoId; 6 use radicle::version::Version; 7 use cc_httpd as httpd; 8 9 pub const VERSION: Version = Version { 10 name: "radicle-httpd", 11 commit: env!("GIT_HEAD"), 12 version: env!("RADICLE_VERSION"), 13 timestamp: env!("SOURCE_DATE_EPOCH"), 14 }; 15 16 pub const HELP_MSG: &str = r#" 17 Usage 18 19 radicle-httpd [<option>...] 20 21 Options 22 23 --listen <address> Address to listen on: TCP address (e.g., 127.0.0.1:8080) 24 or Unix socket path (e.g., /tmp/radicle.sock) 25 (default: 0.0.0.0:8080) 26 --alias, -a <alias> <rid> Provide alias and RID pairs to shorten git clone commands for repositories, 27 e.g. heartwood and rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5 to produce https://seed.radicle.xyz/heartwood.git 28 --cache <number> Max amount of items in cache for /tree endpoints (default: 100) 29 --version, -v Print program version 30 --help, -h Print help 31 "#; 32 33 #[tokio::main] 34 async fn main() -> anyhow::Result<()> { 35 // SAFETY: The logger is only initialized once. 36 httpd::logger::init().unwrap(); 37 tracing::info!("starting http daemon.."); 38 tracing::info!("version {} ({})", env!("RADICLE_VERSION"), env!("GIT_HEAD")); 39 40 let options = parse_options()?; 41 42 match httpd::run(options).await { 43 Ok(()) => {} 44 Err(err) => { 45 tracing::error!("Fatal: {:#}", err); 46 process::exit(1); 47 } 48 } 49 Ok(()) 50 } 51 52 /// Parse command-line arguments into HTTP options. 53 fn parse_options() -> Result<httpd::Options, lexopt::Error> { 54 use lexopt::prelude::*; 55 56 let mut parser = lexopt::Parser::from_env(); 57 let mut listen = None; 58 let mut aliases = HashMap::new(); 59 let mut cache = Some(httpd::DEFAULT_CACHE_SIZE); 60 61 while let Some(arg) = parser.next()? { 62 match arg { 63 Long("listen") => { 64 let addr: DualAddr = parser.value()?.parse()?; 65 66 #[cfg(unix)] 67 // Get socket path and remove it if existing 68 if let DualAddr::Uds(socket_path) = &addr { 69 if let Some(path) = socket_path.as_pathname() { 70 if path.exists() { 71 tracing::info!("Removing existing socket path at {}", path.display()); 72 if let Err(e) = std::fs::remove_file(path) { 73 tracing::error!("{e}"); 74 } 75 } 76 } else { 77 tracing::error!("Provided socket address isn't a valid path."); 78 process::exit(0); 79 } 80 } 81 82 listen = Some(addr); 83 } 84 Long("alias") | Short('a') => { 85 let alias: String = parser.value()?.parse()?; 86 let id: RepoId = parser.value()?.parse()?; 87 88 aliases.insert(alias, id); 89 } 90 Long("version") | Short('v') => { 91 if let Err(e) = VERSION.write(std::io::stdout()) { 92 tracing::error!("{e}"); 93 process::exit(1); 94 }; 95 process::exit(0); 96 } 97 Long("cache") => { 98 let size = parser.value()?.parse()?; 99 cache = NonZeroUsize::new(size); 100 } 101 Long("help") | Short('h') => { 102 println!("{HELP_MSG}"); 103 process::exit(0); 104 } 105 _ => return Err(arg.unexpected()), 106 } 107 } 108 Ok(httpd::Options { 109 aliases, 110 listen: listen.unwrap_or_else(|| DualAddr::Tcp(([0, 0, 0, 0], 8080).into())), 111 cache, 112 }) 113 }