136 lines
3.7 KiB
Rust
136 lines
3.7 KiB
Rust
pub mod admin;
|
|
pub mod auth;
|
|
pub mod bootstrap_assets;
|
|
pub mod cloud_init;
|
|
pub mod cluster;
|
|
pub mod config;
|
|
pub mod local_storage;
|
|
pub mod phone_home;
|
|
pub mod state;
|
|
pub mod storage;
|
|
pub mod tls;
|
|
pub mod validation;
|
|
|
|
use axum::{
|
|
routing::{get, post},
|
|
Router,
|
|
};
|
|
use std::sync::Arc;
|
|
use tracing::info;
|
|
|
|
use crate::{config::Config, state::AppState};
|
|
|
|
/// Build the Axum router with all API routes
|
|
pub fn build_router(state: Arc<AppState>) -> Router {
|
|
Router::new()
|
|
// Health check
|
|
.route("/health", get(health_check))
|
|
// Phone Home API (node registration)
|
|
.route("/api/v1/phone-home", post(phone_home::phone_home))
|
|
.route(
|
|
"/api/v1/cloud-init/:machine_id/meta-data",
|
|
get(cloud_init::meta_data),
|
|
)
|
|
.route(
|
|
"/api/v1/cloud-init/:machine_id/user-data",
|
|
get(cloud_init::user_data),
|
|
)
|
|
.route(
|
|
"/api/v1/bootstrap/flake-bundle",
|
|
get(bootstrap_assets::flake_bundle),
|
|
)
|
|
// Admin API (node management)
|
|
.route("/api/v1/admin/nodes", post(admin::pre_register))
|
|
.route("/api/v1/admin/nodes", get(admin::list_nodes))
|
|
.with_state(state)
|
|
}
|
|
|
|
/// Health check endpoint
|
|
async fn health_check() -> &'static str {
|
|
"OK"
|
|
}
|
|
|
|
/// Run the Deployer server
|
|
pub async fn run(config: Config) -> anyhow::Result<()> {
|
|
let bind_addr = config.bind_addr;
|
|
|
|
// Create application state
|
|
let mut state = AppState::with_config(config);
|
|
|
|
if state.config.allow_unauthenticated {
|
|
tracing::warn!("Deployer running with allow_unauthenticated=true (unsafe)");
|
|
} else if state.config.bootstrap_token.is_none() {
|
|
tracing::warn!("Deployer requires bootstrap_token but none is configured");
|
|
}
|
|
|
|
if state.config.admin_token.is_none() {
|
|
if state.config.allow_admin_fallback {
|
|
tracing::warn!("admin_token not set; admin API will fall back to bootstrap_token");
|
|
} else {
|
|
tracing::warn!(
|
|
"admin_token not set; admin API disabled unless allow_admin_fallback=true"
|
|
);
|
|
}
|
|
} else if state.config.admin_token == state.config.bootstrap_token {
|
|
tracing::warn!(
|
|
"DEPLOYER_ADMIN_TOKEN matches bootstrap token; consider separating privileges"
|
|
);
|
|
}
|
|
|
|
if state.config.cluster_id.is_none() {
|
|
tracing::warn!(
|
|
"cluster_id not set; cluster node state won't be written to photoncloud/clusters"
|
|
);
|
|
}
|
|
|
|
// Initialize ChainFire storage
|
|
if let Err(e) = state.init_storage().await {
|
|
tracing::warn!(error = %e, "ChainFire storage initialization failed");
|
|
}
|
|
|
|
if state.config.require_chainfire && !state.has_storage() {
|
|
return Err(anyhow::anyhow!(
|
|
"ChainFire storage is required but unavailable. Configure chainfire.endpoints or disable require_chainfire for dev mode."
|
|
));
|
|
}
|
|
|
|
let state = Arc::new(state);
|
|
|
|
// Build router
|
|
let app = build_router(state);
|
|
|
|
// Create TCP listener
|
|
let listener = tokio::net::TcpListener::bind(bind_addr).await?;
|
|
|
|
info!("Deployer server listening on {}", bind_addr);
|
|
|
|
// Run server
|
|
axum::serve(listener, app).await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use axum::http::StatusCode;
|
|
use tower::ServiceExt;
|
|
|
|
#[tokio::test]
|
|
async fn test_health_check() {
|
|
let state = Arc::new(AppState::new());
|
|
let app = build_router(state);
|
|
|
|
let response = app
|
|
.oneshot(
|
|
axum::http::Request::builder()
|
|
.uri("/health")
|
|
.body(axum::body::Body::empty())
|
|
.unwrap(),
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
assert_eq!(response.status(), StatusCode::OK);
|
|
}
|
|
}
|