//! `loopd`: the agent loop daemon. It has two commands: `selftest`, which runs the startup checks //! before serving anyone, and `serve`, which runs those checks and then runs the channel server. use std::os::unix::fs::PermissionsExt; use std::os::unix::net::UnixListener; use std::path::Path; use std::process::ExitCode; use std::sync::Arc; use std::time::Duration; use loopd::broker_port::{BrokerPort, NoBroker, not_configured_line}; use loopd::channel::{self, Context}; use loopd::config::Config; use loopd::llama::Client; use loopd::selftest::{SelfTestError, run}; use loopd::tools::{Registry, ToolPort}; fn main() -> ExitCode { // `args_os`: the config path need not be UTF-8, and `args` would panic on one that is not. let args: Vec = std::env::args_os().skip(1).collect(); let words: Vec> = args.iter().map(|a| a.to_str()).collect(); match (words.as_slice(), args.get(2)) { ([Some("selftest"), Some("--config"), _], Some(path)) => run_selftest(Path::new(path)), ([Some("serve"), Some("--config"), _], Some(path)) => run_serve(Path::new(path)), _ => { eprintln!("usage: loopd selftest --config "); eprintln!("usage: loopd serve --config "); ExitCode::from(2) } } } /// The startup checks, with the lines `loopd` prints. Shared by `selftest` and `serve`. fn run_selftest_check(client: &Client) -> Result<(), SelfTestError> { let mut on_step = |step: &str| { eprintln!("selftest: {step}"); }; match run(client, &mut on_step) { Ok(()) => { eprintln!("selftest: ok"); Ok(()) } Err(e) => { eprintln!("selftest: FAILED: {e}; see docs/runbook.md#loopd-selftest-failed"); Err(e) } } } fn run_selftest(path: &Path) -> ExitCode { let cfg = match Config::load(path) { Ok(cfg) => cfg, Err(e) => { eprintln!("loopd: {e}"); return ExitCode::from(1); } }; let client = Client::new(cfg); match run_selftest_check(&client) { Ok(()) => ExitCode::SUCCESS, Err(_) => ExitCode::from(1), } } fn run_serve(path: &Path) -> ExitCode { let cfg = match Config::load(path) { Ok(cfg) => cfg, Err(e) => { eprintln!("loopd: {e}"); return ExitCode::from(1); } }; let socket = cfg.channel_socket(); if socket.exists() && let Err(e) = std::fs::remove_file(&socket) { eprintln!( "loopd: cannot remove the old socket at {}: {e}", socket.display() ); return ExitCode::from(1); } let client = Client::new(cfg.clone()); if run_selftest_check(&client).is_err() { return ExitCode::from(1); } if let Some(parent) = socket.parent() && let Err(e) = std::fs::create_dir_all(parent) { eprintln!("loopd: cannot create the socket directory: {e}"); return ExitCode::from(1); } let listener = match UnixListener::bind(&socket) { Ok(listener) => listener, Err(e) => { eprintln!("loopd: cannot bind the socket at {}: {e}", socket.display()); return ExitCode::from(1); } }; if let Err(e) = std::fs::set_permissions(&socket, std::fs::Permissions::from_mode(0o600)) { eprintln!("loopd: cannot set the mode of {}: {e}", socket.display()); return ExitCode::from(1); } eprintln!("loopd: serving on {}", socket.display()); let port: Box = match &cfg.broker.socket { Some(socket) => Box::new(BrokerPort::new( socket.clone(), Duration::from_millis(cfg.broker.timeout_ms), )), None => { eprintln!("{}", not_configured_line()); Box::new(NoBroker) } }; let ctx = Arc::new(Context::new(cfg, client, port, Registry::m3a())); if let Err(e) = channel::serve(listener, ctx) { eprintln!("loopd: the channel server stopped: {e}"); return ExitCode::from(1); } ExitCode::SUCCESS }