Files
boxmaker/crates/brokerd/src/main.rs
T
kyle 3ecaef3c8b Add brokerd serve: startup, both sockets, and the expiry thread
Implemented-By: OpenCode session (model recorded in docs/implementer-log.md)
2026-09-20 17:24:57 -07:00

121 lines
3.5 KiB
Rust

//! `brokerd`: the broker role. `serve` takes the audit lock, binds both sockets, and serves.
use std::path::PathBuf;
use std::process::ExitCode;
use std::sync::Arc;
use brokerd::audit::{AuditError, RECOVERED_NOTICE};
use brokerd::config::Config;
use brokerd::runner::Refusing;
use brokerd::serve::{self, ServeError};
const USAGE: &str = "usage: brokerd serve --config <path> [--accept-break]";
fn main() -> ExitCode {
// The first argument must be `serve`.
let mut args = std::env::args().skip(1);
match args.next() {
Some(command) if command == "serve" => {}
_ => return usage(),
}
// `--config <path>` exactly once and `--accept-break` at most once, in any order.
let (config_path, accept_break) = match parse_serve(args) {
Ok(pair) => pair,
Err(()) => return usage(),
};
// Read the config before touching the filesystem: a bad config must make nothing.
let cfg = match Config::load(&config_path) {
Ok(cfg) => cfg,
Err(e) => {
eprintln!("brokerd: {e}");
return ExitCode::from(1);
}
};
// Keep the socket paths for the serving notice, before `cfg` is moved into `start`.
let broker_path = cfg.broker_socket();
let admin_path = cfg.admin_socket();
let started = match serve::start(
cfg,
accept_break,
Box::new(Refusing),
Arc::new(|line: &str| eprintln!("{line}")),
) {
Ok(started) => started,
Err(ServeError::Audit(e @ AuditError::NothingToAccept)) => {
eprintln!("brokerd: {e}");
return ExitCode::from(2);
}
Err(e) => {
eprintln!("brokerd: {e}");
return ExitCode::from(1);
}
};
if started.recovered {
eprintln!("{RECOVERED_NOTICE}");
}
if let Some(failure) = &started.accepted {
eprintln!(
"audit: accepted the break at {}:{}: {}",
failure.file, failure.line, failure.what
);
}
eprintln!(
"brokerd: serving tools on {} and approvals on {}",
broker_path.display(),
admin_path.display()
);
match started.run() {
Ok(()) => ExitCode::SUCCESS,
Err(e) => {
eprintln!("brokerd: {e}");
ExitCode::from(1)
}
}
}
/// The argument list was not `serve --config <path> [--accept-break]`.
fn usage() -> ExitCode {
eprintln!("{USAGE}");
ExitCode::from(2)
}
/// The `--config` and `--accept-break` flags after `serve`, or `Err(())` for a bad list.
fn parse_serve<I: Iterator<Item = String>>(mut args: I) -> Result<(PathBuf, bool), ()> {
let mut config: Option<String> = None;
let mut accept_break = false;
loop {
match args.next() {
None => {
return match config {
Some(path) => Ok((PathBuf::from(path), accept_break)),
None => Err(()),
};
}
Some(arg) => match arg.as_str() {
"--config" => match args.next() {
Some(path) => {
if config.is_some() {
return Err(());
}
config = Some(path);
}
None => return Err(()),
},
"--accept-break" => {
if accept_break {
return Err(());
}
accept_break = true;
}
_ => return Err(()),
},
}
}
}