1pub mod auth;
11mod routes;
12mod state;
13
14use std::net::SocketAddr;
15use std::sync::Arc;
16
17use anyhow::Result;
18use axum::routing::{get, post};
19use tower_http::cors::CorsLayer;
20
21pub use auth::{AuthConfig, AuthState};
22pub use state::ServerState;
23
24pub async fn run(
33 config_toml: String,
34 secrets: std::collections::HashMap<String, String>,
35 build_info: trustee_core::types::BuildInfo,
36 addr: SocketAddr,
37) -> Result<()> {
38 let auth_state = AuthConfig::from_toml(&config_toml).map(|cfg| {
40 let is_dev = cfg.dev_config.local_dev_mode;
41 tracing::info!(
42 "Auth enabled: {} mode, issuer={}",
43 if is_dev { "development" } else { "production" },
44 cfg.issuer_url
45 );
46 Arc::new(AuthState::new(cfg))
47 });
48
49 let (mut session, workflow_rx) = trustee_core::session::Session::new();
51 session.config_toml = Some(config_toml);
52 session.secrets = Some(secrets);
53 session.build_info = Some(build_info);
54 session.parse_auto_handoff_config();
55
56 let (ws_tx, _ws_rx) = tokio::sync::broadcast::channel::<String>(256);
58
59 let state = ServerState::new(session, ws_tx, auth_state);
61
62 state.clone().spawn_drain_task(workflow_rx);
64
65 let app = axum::Router::new()
73 .route("/api/v1/health", get(routes::health))
75 .nest("/auth", auth::auth_routes())
76 .route("/api/v1/session", get(routes::get_session))
78 .route("/api/v1/session/command", post(routes::post_command))
79 .route("/api/v1/session/cancel", post(routes::post_cancel))
80 .route("/api/v1/session/handoff", post(routes::post_handoff))
81 .route("/api/v1/session/stream", get(routes::ws_handler))
82 .route("/", get(routes::serve_index))
84 .route("/{file}", get(routes::serve_static))
85 .layer(CorsLayer::permissive())
86 .layer(axum::extract::DefaultBodyLimit::max(10 * 1024 * 1024))
87 .with_state(state);
88
89 let listener = tokio::net::TcpListener::bind(addr).await?;
91 tracing::info!("Trustee API listening on http://{}", addr);
92 axum::serve(listener, app).await?;
93
94 Ok(())
95}