mod handlers;
mod models;
use actix_web::{web, App, HttpServer};
use models::{RouteConditions, RouteEntry, RouteTarget};
use reqwest::Client;
use std::collections::HashMap;
use std::env;
use std::sync::atomic::AtomicUsize;
use std::sync::Arc;
use tokio::sync::RwLock;
use tracing::info;
#[derive(Clone)]
pub struct RouteRing {
pub targets: Vec<RouteTarget>,
pub conditions: Option<RouteConditions>,
pub cursor: Arc<AtomicUsize>,
}
impl RouteRing {
pub fn new(entry: RouteEntry) -> Self {
Self {
targets: entry.targets,
conditions: entry.conditions,
cursor: Arc::new(AtomicUsize::new(0)),
}
}
}
#[derive(Clone)]
pub struct AppState {
pub routes: Arc<RwLock<HashMap<String, RouteRing>>>,
pub client: Client,
}
impl AppState {
pub fn new() -> Self {
AppState {
routes: Arc::new(RwLock::new(HashMap::new())),
client: Client::new(),
}
}
}
impl Default for AppState {
fn default() -> Self {
Self::new()
}
}
pub async fn start_api_server() -> std::io::Result<()> {
let bind_host = env::var("XBP_API_BIND").unwrap_or_else(|_| "127.0.0.1".to_string());
let port = env::var("PORT_XBP_API")
.unwrap_or_else(|_| "8080".to_string())
.parse::<u16>()
.unwrap_or(8080);
info!("Starting XBP API server on {}:{}", bind_host, port);
let state = web::Data::new(AppState::new());
HttpServer::new(move || {
App::new()
.app_data(state.clone())
.route("/ports", web::get().to(handlers::list_ports))
.route("/ports/{port}", web::get().to(handlers::get_port))
.route("/ports/{port}/kill", web::post().to(handlers::kill_port))
.route(
"/network/floating-ips",
web::get().to(handlers::list_network_floating_ips),
)
.route(
"/network/floating-ips",
web::post().to(handlers::add_network_floating_ip),
)
.route(
"/network/configs",
web::get().to(handlers::list_network_configs),
)
.route("/systemctl", web::get().to(handlers::list_systemctl))
.route(
"/systemctl/{service}",
web::get().to(handlers::get_systemctl_service),
)
.route(
"/systemctl/{service}/{action}",
web::post().to(handlers::systemctl_action),
)
.route("/pm2", web::get().to(handlers::list_pm2))
.route("/pm2/{name}", web::delete().to(handlers::delete_pm2))
.route("/pm2/{name}/start", web::post().to(handlers::start_pm2))
.route("/pm2/{name}/stop", web::post().to(handlers::stop_pm2))
.route("/pm2/{name}/restart", web::post().to(handlers::restart_pm2))
.route("/services", web::get().to(handlers::list_services))
.route(
"/services/{name}/{command}",
web::post().to(handlers::run_service_command),
)
.route("/config", web::get().to(handlers::get_config))
.route("/logs", web::get().to(handlers::get_logs))
.route(
"/install/{package}",
web::post().to(handlers::install_package),
)
.route("/setup", web::post().to(handlers::setup))
.route("/redeploy", web::post().to(handlers::redeploy))
.route(
"/redeploy/{service_name}",
web::post().to(handlers::redeploy_service),
)
.route(
"/binary/download",
web::post().to(handlers::download_and_run_binary),
)
.route(
"/openapi/download",
web::get().to(handlers::download_openapi),
)
.route("/health", web::get().to(handlers::health))
.route("/commands/exec", web::post().to(handlers::exec_command))
.route("/routes", web::get().to(handlers::list_routes))
.route("/routes", web::post().to(handlers::create_route))
.route("/routes/{domain}", web::delete().to(handlers::delete_route))
.route("/proxy/{domain}/{tail:.*}", web::to(handlers::proxy_route))
.route("/metrics", web::get().to(handlers::metrics))
})
.bind((bind_host, port))?
.run()
.await
}