M3a review finding 7, for the other three roles. loopd keeps its config path as a path; bxctl and inferproxy take text arguments and answer one that is not UTF-8 with their usage. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
127 lines
4.1 KiB
Rust
127 lines
4.1 KiB
Rust
//! `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::ffi::OsString> = std::env::args_os().skip(1).collect();
|
|
let words: Vec<Option<&str>> = 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 <path>");
|
|
eprintln!("usage: loopd serve --config <path>");
|
|
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<dyn ToolPort> = 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
|
|
}
|