use color_eyre::eyre::Result;
use console::style;
use std::path::PathBuf;
use std::net::SocketAddr;
use std::convert::Infallible;
use std::sync::Arc;
use std::time::Duration;
use tokio::signal;
use tokio::sync::oneshot;
use tracing::{info, warn, error};
use hyper::Server;
use hyper::service::{make_service_fn, service_fn};
use crate::commands::config::{get_config_path, load_or_create_config, ConfigSettings as FortressConfig};
#[derive(Clone)]
struct FortressServer {
_config: FortressConfig,
start_time: chrono::DateTime<chrono::Utc>,
}
impl FortressServer {
fn new(config: FortressConfig) -> Self {
Self {
_config: config,
start_time: chrono::Utc::now(),
}
}
async fn health_check(&self) -> Result<serde_json::Value> {
let uptime = chrono::Utc::now() - self.start_time;
Ok(serde_json::json!({
"status": "healthy",
"uptime_seconds": uptime.num_seconds(),
"timestamp": chrono::Utc::now().to_rfc3339()
}))
}
async fn get_status(&self) -> Result<serde_json::Value> {
let uptime = chrono::Utc::now() - self.start_time;
Ok(serde_json::json!({
"server": {
"status": "running",
"uptime_seconds": uptime.num_seconds(),
"version": env!("CARGO_PKG_VERSION"),
"start_time": self.start_time.to_rfc3339()
},
"database": {
"connected": true,
"connections": 1,
"max_connections": 100,
"type": "fortress"
},
"performance": {
"requests_per_second": 0.0,
"average_response_time_ms": 0,
"memory_usage_mb": 50
}
}))
}
async fn get_metrics(&self) -> Result<serde_json::Value> {
let uptime = chrono::Utc::now() - self.start_time;
Ok(serde_json::json!({
"metrics": {
"requests": 0,
"errors": 0,
"uptime_seconds": uptime.num_seconds()
},
"system": {
"http_requests_total": 0,
"http_request_duration_seconds": 0.0,
"active_connections": 0,
"database_operations_total": 0,
"encryption_operations_total": 0,
"memory_usage_bytes": 52428800, "cpu_usage_percent": 0.0,
"disk_usage_bytes": 104857600 }
}))
}
}
pub async fn handle_start(
data_dir: Option<String>,
port: u16,
host: String,
) -> Result<()> {
println!("{}", style("Starting Fortress Server").bold().cyan());
println!();
let db_path = PathBuf::from(data_dir.unwrap_or_else(|| "./fortress".to_string()));
if !db_path.exists() {
return Err(color_eyre::eyre::eyre!(
"Database directory not found: {}. Use 'fortress create' first.",
db_path.display()
));
}
let config_path = get_config_path()?;
let config_settings = load_or_create_config(&config_path).await?;
let mut config_settings = config_settings;
if port != 8080 {
config_settings.server.port = port;
}
if host != "127.0.0.1" {
config_settings.server.host = host;
}
validate_server_config(&config_settings)?;
let addr_str = format!("{}:{}", config_settings.server.host, config_settings.server.port);
let addr: SocketAddr = addr_str.parse()
.map_err(|e| color_eyre::eyre::eyre!("Invalid address {}: {}", addr_str, e))?;
info!("Starting Fortress server on {}", addr);
info!("Data directory: {}", db_path.display());
info!("Worker threads: {}", config_settings.server.workers);
let (shutdown_tx, shutdown_rx) = oneshot::channel::<()>();
let fortress_config = create_fortress_config(&config_settings, &db_path)?;
let fortress_server = Arc::new(FortressServer::new(fortress_config));
let server_handle = start_fortress_server(
addr,
fortress_server.clone(),
config_settings.server.workers,
shutdown_rx
).await?;
println!("✓ Fortress Server started successfully");
println!("Server URL: http://{}", addr);
println!("Data directory: {}", style(db_path.display()).bold());
println!("Worker threads: {}", style(config_settings.server.workers).bold());
println!("Health check: http://{}/health", addr);
println!();
println!("Press Ctrl+C to stop the server");
match signal::ctrl_c().await {
Ok(()) => {
println!("\n{}", style("Shutting down Fortress Server...").bold().yellow());
let _ = shutdown_tx.send(());
match tokio::time::timeout(Duration::from_secs(30), server_handle).await {
Ok(Ok(())) => {
println!("✓ Fortress Server stopped gracefully");
}
Ok(Err(e)) => {
warn!("Server shutdown with error: {}", e);
}
Err(_) => {
warn!("Server shutdown timeout - forcing exit");
}
}
}
Err(e) => {
error!("Failed to listen for shutdown signal: {}", e);
return Err(color_eyre::eyre::eyre!("Shutdown signal error: {}", e));
}
}
Ok(())
}
fn validate_server_config(config: &FortressConfig) -> Result<()> {
if config.server.host.is_empty() {
return Err(color_eyre::eyre::eyre!("Server host cannot be empty"));
}
if config.server.port == 0 {
return Err(color_eyre::eyre::eyre!("Server port must be between 1 and 65535"));
}
if config.server.workers == 0 || config.server.workers > 1024 {
return Err(color_eyre::eyre::eyre!("Worker count must be between 1 and 1024"));
}
Ok(())
}
fn create_fortress_config(config_settings: &FortressConfig, _db_path: &PathBuf) -> Result<FortressConfig> {
Ok(config_settings.clone())
}
async fn start_fortress_server(
addr: SocketAddr,
fortress_server: Arc<FortressServer>,
worker_threads: usize,
shutdown_rx: oneshot::Receiver<()>,
) -> Result<tokio::task::JoinHandle<()>> {
let service = make_service_fn(move |_conn| {
let fortress_server = fortress_server.clone();
async move {
Ok::<_, Infallible>(service_fn(move |req| {
let fortress_server = fortress_server.clone();
async move {
Ok::<_, Infallible>(handle_request(req, fortress_server).await)
}
}))
}
});
let server = Server::bind(&addr).serve(service);
let graceful = server.with_graceful_shutdown(async {
shutdown_rx.await.ok();
});
let _runtime = tokio::runtime::Builder::new_multi_thread()
.worker_threads(worker_threads)
.thread_name("fortress-worker")
.enable_all()
.build()
.map_err(|e| color_eyre::eyre::eyre!("Failed to create runtime: {}", e))?;
let handle = tokio::spawn(async move {
if let Err(e) = graceful.await {
error!("Server error: {}", e);
}
});
Ok(handle)
}
async fn handle_request(
req: hyper::Request<hyper::Body>,
fortress_server: Arc<FortressServer>,
) -> hyper::Response<hyper::Body> {
let path = req.uri().path();
let method = req.method();
info!("{} {}", method, path);
match (method.as_str(), path) {
("GET", "/health") => {
handle_health_check(fortress_server).await.unwrap_or_else(|_| {
hyper::Response::builder()
.status(500)
.body(hyper::Body::from("Internal Server Error"))
.unwrap()
})
}
("GET", "/status") => {
handle_status_check(fortress_server).await.unwrap_or_else(|_| {
hyper::Response::builder()
.status(500)
.body(hyper::Body::from("Internal Server Error"))
.unwrap()
})
}
("GET", "/metrics") => {
handle_metrics(fortress_server).await.unwrap_or_else(|_| {
hyper::Response::builder()
.status(500)
.body(hyper::Body::from("Internal Server Error"))
.unwrap()
})
}
_ => {
hyper::Response::builder()
.status(404)
.body(hyper::Body::from("Not Found"))
.unwrap()
}
}
}
async fn handle_health_check(fortress_server: Arc<FortressServer>) -> Result<hyper::Response<hyper::Body>, hyper::Error> {
let health_result = fortress_server.health_check().await.unwrap_or_else(|_| {
serde_json::json!({"status": "unhealthy"})
});
let is_healthy = health_result.get("status")
.and_then(|v| v.as_bool())
.unwrap_or(false);
let status = if is_healthy { "healthy" } else { "unhealthy" };
let body = serde_json::json!({
"status": status,
"timestamp": chrono::Utc::now().to_rfc3339(),
"version": env!("CARGO_PKG_VERSION")
});
Ok(hyper::Response::builder()
.status(hyper::StatusCode::OK)
.header("content-type", "application/json")
.body(hyper::Body::from(body.to_string()))
.unwrap())
}
async fn handle_status_check(fortress_server: Arc<FortressServer>) -> Result<hyper::Response<hyper::Body>, hyper::Error> {
let status_result = fortress_server.get_status().await.unwrap_or_else(|_| {
serde_json::json!({"status": "unknown"})
});
let body = serde_json::json!({
"server": status_result,
"database": {
"connected": true,
"connections": 1,
"max_connections": 100
},
"performance": {
"requests_per_second": 0.0,
"average_response_time_ms": 0
}
});
Ok(hyper::Response::builder()
.status(hyper::StatusCode::OK)
.header("content-type", "application/json")
.body(hyper::Body::from(body.to_string()))
.unwrap())
}
async fn handle_metrics(fortress_server: Arc<FortressServer>) -> Result<hyper::Response<hyper::Body>, hyper::Error> {
let metrics_result = fortress_server.get_metrics().await.unwrap_or_else(|_| {
serde_json::json!({"requests": 0})
});
let body = serde_json::json!({
"metrics": metrics_result,
"extra": {
"http_requests_total": 0,
"http_request_duration_seconds": 0.0,
"active_connections": 0,
"database_operations_total": 0,
"encryption_operations_total": 0,
"memory_usage_bytes": 0,
"cpu_usage_percent": 0.0
}
});
Ok(hyper::Response::builder()
.status(hyper::StatusCode::OK)
.header("content-type", "application/json")
.body(hyper::Body::from(body.to_string()))
.unwrap())
}