mod agent; use std::net::SocketAddr; use agent::AgentService; use anyhow::Error; use axum::{ extract::State, http::StatusCode, response::{IntoResponse, Response}, routing::{get, post}, Json, Router, }; use churn_domain::AgentEnrollReq; use clap::{Parser, Subcommand}; use serde_json::json; #[derive(Parser)] #[command(author, version, about, long_about = None, subcommand_required = true)] struct Command { #[command(subcommand)] command: Option, } #[derive(Subcommand)] enum Commands { Daemon { #[arg(env = "CHURN_ADDR", long)] host: SocketAddr, }, Connect { /// agent name is the hostname which other agents or servers can resolve and connect via. It should be unique #[arg(env = "CHURN_AGENT_NAME", long)] agent_name: String, #[arg(env = "CHURN_ADDR", long)] host: SocketAddr, #[arg(env = "CHURN_TOKEN", long)] token: String, }, } #[derive(Clone)] #[derive(Default)] struct AppState { agent: AgentService, } #[tokio::main] async fn main() -> anyhow::Result<()> { dotenv::dotenv().ok(); tracing_subscriber::fmt::init(); let cli = Command::parse(); handle_command(cli).await?; Ok(()) } async fn handle_command(cmd: Command) -> anyhow::Result<()> { match cmd.command { Some(Commands::Daemon { host }) => { tracing::info!("Starting churn server"); let app = Router::new() .route("/enroll", post(enroll)) .route("/ping", get(ping)) .with_state(AppState::default()); tracing::info!("churn server listening on {}", host); axum::Server::bind(&host) .serve(app.into_make_service()) .await .unwrap(); Ok(()) } Some(Commands::Connect { host: _, token: _, agent_name: _, }) => todo!(), None => todo!(), } } enum AppError { Internal(Error), } impl IntoResponse for AppError { fn into_response(self) -> Response { let (status, error_message) = match self { AppError::Internal(e) => { tracing::error!("failed with error: {}", e); ( StatusCode::INTERNAL_SERVER_ERROR, "failed with internal error", ) } }; let body = Json(json!({ "error": error_message, })); (status, body).into_response() } } async fn ping() -> impl IntoResponse { "pong!" } async fn enroll( State(state): State, Json(req): Json, ) -> Result<(), AppError> { state .agent .enroll(&req.agent_name, &req.server, &req.lease) .await .map_err(AppError::Internal)?; Ok(()) }