/ src / main.rs
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  }