//! `ops-agent` — the on-host executor. //! //! Reads its config (its own grant + the caller identities it trusts), binds //! the configured tailnet socket, and serves `/run` `/health` `/pull`. On //! macOS it is installed as an Aqua LaunchAgent so build+sign run in the GUI //! security session (see `_private/docs/ops-core/executor.md`). No root needed: //! `launchctl bootstrap gui/$(id -u) `. //! //! Usage: `ops-agent --config /path/to/config.toml` use anyhow::{Context, Result}; use ops_exec::agent::{AgentConfig, AgentState, router}; use std::net::SocketAddr; #[tokio::main] async fn main() -> Result<()> { tracing_subscriber::fmt().with_env_filter(env_filter()).init(); let config_path = parse_config_arg().context( "usage: ops-agent --config ", )?; let raw = std::fs::read_to_string(&config_path) .with_context(|| format!("reading config {config_path}"))?; let config: AgentConfig = toml::from_str(&raw).context("parsing config toml")?; let listen = config.listen; tracing::info!(%listen, allow = config.allow.len(), "ops-agent starting"); let state = AgentState::new(config); let app = router(state); let listener = tokio::net::TcpListener::bind(listen) .await .with_context(|| format!("binding {listen}"))?; axum::serve( listener, app.into_make_service_with_connect_info::(), ) .await .context("serving")?; Ok(()) } fn env_filter() -> tracing_subscriber::EnvFilter { tracing_subscriber::EnvFilter::try_from_default_env() .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")) } /// Minimal `--config ` parser (no clap dependency for one flag). fn parse_config_arg() -> Option { let mut args = std::env::args().skip(1); while let Some(a) = args.next() { match a.as_str() { "--config" | "-c" => return args.next(), other if other.starts_with("--config=") => { return Some(other.trim_start_matches("--config=").to_string()); } _ => {} } } None }