use std::path::Path; use anyhow::{Context, Result}; use hive_sh4re::{HostRequest, HostResponse}; use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader}; use tokio::net::{UnixListener, UnixStream}; use crate::lifecycle; pub async fn serve(socket: &Path, agent_flake: &str) -> Result<()> { if let Some(parent) = socket.parent() { std::fs::create_dir_all(parent) .with_context(|| format!("create socket parent {}", parent.display()))?; } if socket.exists() { std::fs::remove_file(socket).context("remove stale socket")?; } let listener = UnixListener::bind(socket) .with_context(|| format!("bind admin socket {}", socket.display()))?; tracing::info!(socket = %socket.display(), %agent_flake, "hive-c0re listening"); loop { let (stream, _) = listener.accept().await.context("accept connection")?; let agent_flake = agent_flake.to_owned(); tokio::spawn(async move { if let Err(e) = handle(stream, &agent_flake).await { tracing::warn!(error = ?e, "connection failed"); } }); } } async fn handle(stream: UnixStream, agent_flake: &str) -> Result<()> { let (read, mut write) = stream.into_split(); let mut reader = BufReader::new(read); let mut line = String::new(); loop { line.clear(); let n = reader.read_line(&mut line).await?; if n == 0 { return Ok(()); } let resp = match serde_json::from_str::(line.trim()) { Ok(req) => dispatch(&req, agent_flake).await, Err(e) => HostResponse::error(format!("parse error: {e}")), }; let mut payload = serde_json::to_string(&resp)?; payload.push('\n'); write.write_all(payload.as_bytes()).await?; write.flush().await?; } } async fn dispatch(req: &HostRequest, agent_flake: &str) -> HostResponse { let result: anyhow::Result = async { Ok(match req { HostRequest::Spawn { name } => { tracing::info!(%name, "spawn"); lifecycle::spawn(name, agent_flake).await?; HostResponse::success() } HostRequest::Kill { name } => { tracing::info!(%name, "kill"); lifecycle::kill(name).await?; HostResponse::success() } HostRequest::Rebuild { name } => { tracing::info!(%name, "rebuild"); lifecycle::rebuild(name, agent_flake).await?; HostResponse::success() } HostRequest::List => HostResponse::list(lifecycle::list().await?), }) } .await; match result { Ok(r) => r, Err(e) => HostResponse::error(format!("{e:#}")), } }