//! `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 loopd::channel::{self, Context}; use loopd::config::Config; use loopd::llama::Client; use loopd::selftest::{SelfTestError, run}; use loopd::tools::{FakeTools, Registry}; fn main() -> ExitCode { let args: Vec = std::env::args().skip(1).collect(); let args: Vec<&str> = args.iter().map(String::as_str).collect(); match args.as_slice() { ["selftest", "--config", path] => run_selftest(path), ["serve", "--config", path] => run_serve(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}"); Err(e) } } } fn run_selftest(path: &str) -> ExitCode { let cfg = match Config::load(Path::new(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: &str) -> ExitCode { let cfg = match Config::load(Path::new(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 ctx = Arc::new(Context::new( cfg, client, Box::new(FakeTools::new()), Registry::m2b(), )); if let Err(e) = channel::serve(listener, ctx) { eprintln!("loopd: the channel server stopped: {e}"); return ExitCode::from(1); } ExitCode::SUCCESS }