Add the loopd serve command

Implemented-By: OpenCode session (model recorded in docs/implementer-log.md)
This commit is contained in:
2026-09-18 19:50:32 -07:00
parent 8bc835f623
commit eb8fc5f920
3 changed files with 211 additions and 17 deletions
+88 -17
View File
@@ -1,25 +1,49 @@
//! `loopd`: the agent loop daemon. Its one command today is `selftest`, which runs the startup
//! checks before `loopd` serves anyone.
//! `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::run;
use loopd::selftest::{SelfTestError, run};
use loopd::tools::{FakeTools, Registry};
fn main() -> ExitCode {
let args: Vec<String> = 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 <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}");
Err(e)
}
}
}
fn run_selftest(path: &str) -> ExitCode {
let cfg = match Config::load(Path::new(path)) {
Ok(cfg) => cfg,
@@ -29,19 +53,66 @@ fn run_selftest(path: &str) -> ExitCode {
}
};
let mut on_step = |step: &str| {
eprintln!("selftest: {step}");
};
let result = run(&Client::new(cfg), &mut on_step);
match result {
Ok(()) => {
eprintln!("selftest: ok");
ExitCode::SUCCESS
}
Err(e) => {
eprintln!("selftest: FAILED: {e}");
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
}