1mod handlers;
2mod models;
3
4use actix_web::{web, App, HttpServer};
5use models::{RouteConditions, RouteEntry, RouteTarget};
6use reqwest::Client;
7use std::collections::HashMap;
8use std::env;
9use std::sync::atomic::AtomicUsize;
10use std::sync::Arc;
11use tokio::sync::RwLock;
12use tracing::info;
13
14#[derive(Clone)]
15pub struct RouteRing {
16 pub targets: Vec<RouteTarget>,
17 pub conditions: Option<RouteConditions>,
18 pub cursor: Arc<AtomicUsize>,
19}
20
21impl RouteRing {
22 pub fn new(entry: RouteEntry) -> Self {
23 Self {
24 targets: entry.targets,
25 conditions: entry.conditions,
26 cursor: Arc::new(AtomicUsize::new(0)),
27 }
28 }
29}
30
31#[derive(Clone)]
32pub struct AppState {
33 pub routes: Arc<RwLock<HashMap<String, RouteRing>>>,
34 pub client: Client,
35}
36
37impl AppState {
38 pub fn new() -> Self {
39 AppState {
40 routes: Arc::new(RwLock::new(HashMap::new())),
41 client: Client::new(),
42 }
43 }
44}
45
46impl Default for AppState {
47 fn default() -> Self {
48 Self::new()
49 }
50}
51
52pub async fn start_api_server() -> std::io::Result<()> {
53 let bind_host = env::var("XBP_API_BIND").unwrap_or_else(|_| "127.0.0.1".to_string());
54 let port = env::var("PORT_XBP_API")
55 .unwrap_or_else(|_| "8080".to_string())
56 .parse::<u16>()
57 .unwrap_or(8080);
58
59 info!("Starting XBP API server on {}:{}", bind_host, port);
60
61 let state = web::Data::new(AppState::new());
62
63 HttpServer::new(move || {
64 App::new()
65 .app_data(state.clone())
66 .route("/ports", web::get().to(handlers::list_ports))
67 .route("/ports/{port}", web::get().to(handlers::get_port))
68 .route("/ports/{port}/kill", web::post().to(handlers::kill_port))
69 .route(
70 "/network/floating-ips",
71 web::get().to(handlers::list_network_floating_ips),
72 )
73 .route(
74 "/network/floating-ips",
75 web::post().to(handlers::add_network_floating_ip),
76 )
77 .route(
78 "/network/configs",
79 web::get().to(handlers::list_network_configs),
80 )
81 .route("/systemctl", web::get().to(handlers::list_systemctl))
82 .route(
83 "/systemctl/{service}",
84 web::get().to(handlers::get_systemctl_service),
85 )
86 .route(
87 "/systemctl/{service}/{action}",
88 web::post().to(handlers::systemctl_action),
89 )
90 .route("/pm2", web::get().to(handlers::list_pm2))
91 .route("/pm2/{name}", web::delete().to(handlers::delete_pm2))
92 .route("/pm2/{name}/start", web::post().to(handlers::start_pm2))
93 .route("/pm2/{name}/stop", web::post().to(handlers::stop_pm2))
94 .route("/pm2/{name}/restart", web::post().to(handlers::restart_pm2))
95 .route("/services", web::get().to(handlers::list_services))
96 .route(
97 "/services/{name}/{command}",
98 web::post().to(handlers::run_service_command),
99 )
100 .route("/config", web::get().to(handlers::get_config))
101 .route("/logs", web::get().to(handlers::get_logs))
102 .route(
103 "/install/{package}",
104 web::post().to(handlers::install_package),
105 )
106 .route("/setup", web::post().to(handlers::setup))
107 .route("/redeploy", web::post().to(handlers::redeploy))
108 .route(
109 "/redeploy/{service_name}",
110 web::post().to(handlers::redeploy_service),
111 )
112 .route(
113 "/binary/download",
114 web::post().to(handlers::download_and_run_binary),
115 )
116 .route(
117 "/openapi/download",
118 web::get().to(handlers::download_openapi),
119 )
120 .route("/health", web::get().to(handlers::health))
121 .route("/commands/exec", web::post().to(handlers::exec_command))
122 .route("/routes", web::get().to(handlers::list_routes))
123 .route("/routes", web::post().to(handlers::create_route))
124 .route("/routes/{domain}", web::delete().to(handlers::delete_route))
125 .route("/proxy/{domain}/{tail:.*}", web::to(handlers::proxy_route))
126 .route("/metrics", web::get().to(handlers::metrics))
127 })
128 .bind((bind_host, port))?
129 .run()
130 .await
131}