haematite_cli/door/
cli.rs1use std::io::Read;
4use std::net::TcpListener;
5use std::path::{Path, PathBuf};
6use std::process::ExitCode;
7use std::sync::Arc;
8use std::time::Instant;
9
10use super::config::Config;
11use super::listen::serve;
12use super::lys::LysSignIn;
13use super::signin::Door;
14
15const CONFIG_BYTES: u64 = 1_048_576;
16const USAGE: &str = "usage: haematite door [--check] --config <absolute door.json>";
17
18pub fn dispatch(args: impl Iterator<Item = String>) -> ExitCode {
20 let started = Instant::now();
21 let (path, checking) = match arguments(args) {
22 Ok(arguments) => arguments,
23 Err(message) => {
24 eprintln!("{message}");
25 return ExitCode::FAILURE;
26 }
27 };
28 match run_config(&path, checking, started) {
29 Ok(()) => ExitCode::SUCCESS,
30 Err(message) => {
31 eprintln!("{message}");
32 if checking {
33 ExitCode::from(78)
34 } else {
35 ExitCode::FAILURE
36 }
37 }
38 }
39}
40
41pub fn run(args: impl Iterator<Item = String>) -> Result<(), String> {
47 let started = Instant::now();
48 let (path, checking) = arguments(args)?;
49 run_config(&path, checking, started)
50}
51
52fn run_config(path: &Path, checking: bool, started: Instant) -> Result<(), String> {
53 let config = load(path, checking)?;
54 if checking {
55 super::check::configuration(&config)?;
56 println!(
57 "door_check_ok: {} children, {} stores",
58 config.children.len(),
59 config.stores.len()
60 );
61 return Ok(());
62 }
63 let issuer = LysSignIn::connect(&config.lys)?;
64 let door = Arc::new(Door::new(issuer));
65 let assistant = super::assistant_socket::Listener::start(&door, &config)
66 .map_err(|error| format!("{}: {}", error.code, error.message))?;
67 let children = super::children::Children::start(&config.children)?;
68 let site = Arc::new(config.site());
69 let limits = config.limits();
70 let listener = TcpListener::bind(config.listen)
71 .map_err(|error| format!("the door could not listen on {}: {error}", config.listen))?;
72 eprintln!(
73 "haematite door: listening on {} for {} elapsed_ms={}",
74 config.listen,
75 config.origin,
76 started.elapsed().as_millis()
77 );
78 serve(&listener, &door, &site, &limits);
79 drop(assistant);
80 drop(children);
81 Ok(())
82}
83
84fn arguments(mut args: impl Iterator<Item = String>) -> Result<(PathBuf, bool), String> {
85 let mut checking = false;
86 let mut path = None;
87 while let Some(arg) = args.next() {
88 match arg.as_str() {
89 "--check" if !checking => checking = true,
90 "--config" if path.is_none() => path = Some(PathBuf::from(args.next().ok_or(USAGE)?)),
91 _ => return Err(USAGE.to_owned()),
92 }
93 }
94 let path = path.ok_or(USAGE)?;
95 if !path.is_absolute() {
96 return Err(USAGE.to_owned());
97 }
98 Ok((path, checking))
99}
100
101fn load(path: &Path, checking: bool) -> Result<Config, String> {
102 let file = super::config_upgrade::open_source(path)?;
103 let mut bytes = Vec::new();
104 file.take(CONFIG_BYTES + 1)
105 .read_to_end(&mut bytes)
106 .map_err(|error| format!("the door configuration could not be read: {error}"))?;
107 if u64::try_from(bytes.len()).unwrap_or(u64::MAX) > CONFIG_BYTES {
108 return Err("the door configuration exceeds its byte limit".to_owned());
109 }
110 let plan = super::config_upgrade::plan(path, &bytes)?;
111 if checking {
112 super::config_upgrade::check_apply(path, &plan)?;
113 } else {
114 super::config_upgrade::apply(path, &bytes, &plan)?;
115 }
116 let config = Config::parse(&plan.bytes)?;
117 if checking && !plan.names.is_empty() {
118 println!(
119 "door_config_upgrade_needed: give_up_after=3 for {}",
120 plan.names.join(", ")
121 );
122 }
123 Ok(config)
124}