| 1 | //! `snowbound-site`: see `crates/relay/README.md`. |
| 2 | |
| 3 | use relay::site::{self, Site}; |
| 4 | use std::{net::TcpListener, path::PathBuf, process::ExitCode}; |
| 5 | |
| 6 | const USAGE: &str = "\ |
| 7 | usage: snowbound-site [OPTION VALUE]... |
| 8 | |
| 9 | Each option may also come from the environment as SNOWBOUND_SITE_<OPTION>, upper case with |
| 10 | underscores: SNOWBOUND_SITE_ROOT=/srv/snowbound/web. Flags win. |
| 11 | |
| 12 | --listen ADDRESS where to listen (127.0.0.1:23593) |
| 13 | --root FOLDER the web build's folder (web) |
| 14 | --web URL where Open in Web goes, the code appended, as |
| 15 | https://snowbound.paperclover.net/?join= (none: no Open in Web) |
| 16 | --crashes FOLDER where crash reports are kept (crashes, beside the root) |
| 17 | --trust-forwarded true|false |
| 18 | count senders by X-Forwarded-For, behind a proxy (false) |
| 19 | "; |
| 20 | |
| 21 | fn main() -> ExitCode { |
| 22 | let mut listen = String::from("127.0.0.1:23593"); |
| 23 | let mut site = Site { |
| 24 | root: PathBuf::from("web"), |
| 25 | web: None, |
| 26 | crashes: PathBuf::new(), |
| 27 | trust_forwarded: false, |
| 28 | }; |
| 29 | let mut crashes = None; |
| 30 | let from_environment = std::env::vars().filter_map(|(key, value)| { |
| 31 | let option = key.strip_prefix("SNOWBOUND_SITE_")?; |
| 32 | Some((option.to_lowercase().replace('_', "-"), value)) |
| 33 | }); |
| 34 | let mut arguments = std::env::args().skip(1); |
| 35 | let mut from_flags = Vec::new(); |
| 36 | while let Some(flag) = arguments.next() { |
| 37 | if flag == "--help" { |
| 38 | print!("{USAGE}"); |
| 39 | return ExitCode::SUCCESS; |
| 40 | } |
| 41 | let (Some(option), Some(value)) = (flag.strip_prefix("--"), arguments.next()) else { |
| 42 | eprint!("{USAGE}"); |
| 43 | return ExitCode::from(2); |
| 44 | }; |
| 45 | from_flags.push((option.to_owned(), value)); |
| 46 | } |
| 47 | for (option, value) in from_environment.chain(from_flags) { |
| 48 | match option.as_str() { |
| 49 | "listen" => listen = value, |
| 50 | "root" => site.root = PathBuf::from(value), |
| 51 | "web" => site.web = Some(value).filter(|web| !web.is_empty()), |
| 52 | "crashes" => crashes = Some(PathBuf::from(value)), |
| 53 | "trust-forwarded" => match value.parse() { |
| 54 | Ok(trust) => site.trust_forwarded = trust, |
| 55 | Err(_) => { |
| 56 | eprintln!("--trust-forwarded {value}: true or false\n\n{USAGE}"); |
| 57 | return ExitCode::from(2); |
| 58 | } |
| 59 | }, |
| 60 | _ => { |
| 61 | eprintln!("--{option}: no such option\n\n{USAGE}"); |
| 62 | return ExitCode::from(2); |
| 63 | } |
| 64 | } |
| 65 | } |
| 66 | site.crashes = crashes.unwrap_or_else(|| site.root.with_file_name("crashes")); |
| 67 | let listener = match TcpListener::bind(&listen) { |
| 68 | Ok(listener) => listener, |
| 69 | Err(error) => { |
| 70 | eprintln!("Cannot listen on {listen}: {error}"); |
| 71 | return ExitCode::FAILURE; |
| 72 | } |
| 73 | }; |
| 74 | if let Ok(address) = listener.local_addr() { |
| 75 | println!( |
| 76 | "snowbound-site {} listening on {address}, serving {}", |
| 77 | env!("CARGO_PKG_VERSION"), |
| 78 | site.root.display() |
| 79 | ); |
| 80 | } |
| 81 | match site::serve(listener, site) { |
| 82 | Ok(()) => ExitCode::SUCCESS, |
| 83 | Err(error) => { |
| 84 | eprintln!("{error}"); |
| 85 | ExitCode::FAILURE |
| 86 | } |
| 87 | } |
| 88 | } |