Skip to main content

rush_sync_server/server/
shared.rs

1use crate::core::config::Config;
2use crate::proxy::ProxyManager;
3use crate::server::persistence::ServerRegistry;
4use crate::server::types::{ServerContext, ServerStatus};
5use crate::server::utils::port::is_port_available;
6use std::sync::{Arc, OnceLock};
7
8static SHARED_CONTEXT: OnceLock<ServerContext> = OnceLock::new();
9static PERSISTENT_REGISTRY: OnceLock<ServerRegistry> = OnceLock::new();
10static PROXY_MANAGER: OnceLock<Arc<ProxyManager>> = OnceLock::new();
11
12pub fn get_shared_context() -> &'static ServerContext {
13    SHARED_CONTEXT.get_or_init(ServerContext::default)
14}
15
16pub fn get_persistent_registry() -> &'static ServerRegistry {
17    PERSISTENT_REGISTRY.get_or_init(|| match ServerRegistry::new() {
18        Ok(registry) => registry,
19        Err(e) => {
20            log::error!(
21                "Failed to initialize server registry: {}, using fallback",
22                e
23            );
24            ServerRegistry::with_fallback()
25        }
26    })
27}
28
29pub fn get_proxy_manager() -> &'static Arc<ProxyManager> {
30    PROXY_MANAGER.get_or_init(|| {
31        // Load config and create proxy manager
32        let config = tokio::task::block_in_place(|| {
33            tokio::runtime::Handle::current().block_on(async {
34                crate::core::config::Config::load()
35                    .await
36                    .unwrap_or_default()
37            })
38        });
39
40        Arc::new(ProxyManager::new(config.proxy))
41    })
42}
43
44// Start the proxy system
45async fn start_proxy_system(config: &Config) -> crate::core::error::Result<()> {
46    if !config.proxy.enabled {
47        log::info!("Proxy system disabled in config");
48        return Ok(());
49    }
50
51    let proxy_manager = get_proxy_manager();
52
53    // Start proxy server (HTTP + HTTPS)
54    Arc::clone(proxy_manager).start_proxy_server().await?;
55
56    log::info!("Proxy system started:");
57    log::info!(
58        "  HTTP:  http://{{name}}.{}:{}",
59        config.server.production_domain,
60        config.proxy.port
61    );
62
63    let https_port = config.proxy.port + config.proxy.https_port_offset;
64    log::info!(
65        "  HTTPS: https://{{name}}.{}:{}",
66        config.server.production_domain,
67        https_port
68    );
69
70    Ok(())
71}
72
73pub async fn initialize_server_system() -> crate::core::error::Result<()> {
74    let config = Config::load().await?;
75
76    crate::server::handlers::web::set_global_config(config.clone());
77
78    let registry = get_persistent_registry();
79    let context = get_shared_context();
80
81    let mut persistent_servers = registry.load_servers().await?;
82    let mut corrected_servers = 0;
83
84    for (_server_id, persistent_info) in persistent_servers.iter_mut() {
85        if persistent_info.status == ServerStatus::Running {
86            if !is_port_available(persistent_info.port, &config.server.bind_address) {
87                log::warn!(
88                    "Server {} claims to be running on port {}, but port is occupied",
89                    persistent_info.name,
90                    persistent_info.port
91                );
92                persistent_info.status = ServerStatus::Failed;
93                corrected_servers += 1;
94            } else {
95                log::info!(
96                    "Server {} was running but is no longer active, correcting status",
97                    persistent_info.name
98                );
99                persistent_info.status = ServerStatus::Stopped;
100                corrected_servers += 1;
101            }
102        }
103    }
104
105    if corrected_servers > 0 {
106        registry.save_servers(&persistent_servers).await?;
107        log::info!(
108            "Corrected {} server statuses after program restart",
109            corrected_servers
110        );
111    }
112
113    {
114        let mut servers = context.servers.write().map_err(|e| {
115            crate::core::error::AppError::Validation(format!("servers lock poisoned: {}", e))
116        })?;
117        servers.clear();
118        for (id, persistent_info) in persistent_servers.iter() {
119            let server_info = crate::server::types::ServerInfo::from(persistent_info.clone());
120            servers.insert(id.clone(), server_info);
121        }
122    }
123
124    log::info!(
125        "Server system initialized with {} persistent servers",
126        persistent_servers.len()
127    );
128    log::info!(
129        "Server Config: Port Range {}-{}, Max Concurrent: {}, Workers: {}",
130        config.server.port_range_start,
131        config.server.port_range_end,
132        config.server.max_concurrent,
133        config.server.workers
134    );
135    log::info!(
136        "Logging Config: Max Size {}MB, Archives: {}, Compression: {}, Request Logging: {}",
137        config.logging.max_file_size_mb,
138        config.logging.max_archive_files,
139        config.logging.compress_archives,
140        config.logging.log_requests
141    );
142
143    let auto_start_servers = registry.get_auto_start_servers(&persistent_servers);
144    if !auto_start_servers.is_empty() {
145        log::info!(
146            "Found {} servers marked for auto-start",
147            auto_start_servers.len()
148        );
149
150        if auto_start_servers.len() > config.server.max_concurrent {
151            log::warn!(
152                "Auto-start servers ({}) exceed max_concurrent ({}), some will be skipped",
153                auto_start_servers.len(),
154                config.server.max_concurrent
155            );
156        }
157    }
158
159    // Initialize proxy manager
160    if config.proxy.enabled {
161        // 1. Start proxy system (HTTP + HTTPS)
162        if let Err(e) = start_proxy_system(&config).await {
163            log::error!("Failed to start proxy system: {}", e);
164        } else {
165            // 2. Start HTTP redirect (port 80, serves ACME challenges + redirects to HTTPS)
166            if let Err(e) = start_http_redirect_server(&config).await {
167                log::warn!("Failed to start HTTP redirect: {}", e);
168            }
169
170            // Note: Proxy routes for auto-started servers are registered later
171            // by create_web_server() in auto_start_servers(), not here.
172        }
173
174        log::info!(
175            "  DNS: Point *.{} to this server",
176            config.server.production_domain
177        );
178
179        // 4. Start ACME background provisioning + auto hot-reload
180        // ACME runs AFTER proxy is ready (5s delay), provisions cert, then hot-reloads proxy TLS.
181        // No manual restart needed — new connections automatically use the LE certificate.
182        if config.server.use_lets_encrypt && config.server.production_domain != "localhost" {
183            let cert_dir = crate::core::helpers::get_base_dir()
184                .map(|b| b.join(&config.server.cert_dir))
185                .unwrap_or_else(|_| std::path::PathBuf::from(&config.server.cert_dir));
186
187            // Collect subdomains only from actually registered servers and proxy routes.
188            // Every SAN must have a valid DNS record pointing to this server,
189            // otherwise Let's Encrypt HTTP-01 validation fails for the entire certificate.
190            // "blog" and "myapp" are built-in proxy routes (served directly, not via add_route).
191            let mut subdomains: Vec<String> = vec!["blog".to_string(), "myapp".to_string()];
192            for (_id, persistent_info) in persistent_servers.iter() {
193                if !subdomains.contains(&persistent_info.name) {
194                    subdomains.push(persistent_info.name.clone());
195                }
196            }
197            let proxy_manager = get_proxy_manager();
198            let routes = proxy_manager.get_routes().await;
199            for route in &routes {
200                if !subdomains.contains(&route.subdomain) {
201                    subdomains.push(route.subdomain.clone());
202                }
203            }
204
205            crate::server::acme::start_acme_background(
206                config.server.production_domain.clone(),
207                cert_dir,
208                config.server.acme_email.clone(),
209                false,
210                subdomains,
211            );
212            log::info!(
213                "ACME: Background provisioning + auto hot-reload started for {}",
214                config.server.production_domain
215            );
216        }
217    } else {
218        log::info!("Reverse Proxy disabled in configuration");
219    }
220
221    Ok(())
222}
223
224pub async fn persist_server_update(server_id: &str, status: crate::server::types::ServerStatus) {
225    let registry = get_persistent_registry();
226    if let Err(e) = registry.update_server_status(server_id, status).await {
227        log::error!("Failed to persist server status update: {}", e);
228    }
229}
230
231pub async fn shutdown_all_servers_on_exit() -> crate::core::error::Result<()> {
232    let config = Config::load().await.unwrap_or_default();
233    let registry = get_persistent_registry();
234    let context = get_shared_context();
235
236    let server_handles: Vec<_> = {
237        let mut handles = context.handles.write().map_err(|e| {
238            crate::core::error::AppError::Validation(format!("handles lock poisoned: {}", e))
239        })?;
240        handles.drain().collect()
241    };
242
243    log::info!("Shutting down {} active servers...", server_handles.len());
244
245    let shutdown_timeout = std::time::Duration::from_secs(config.server.shutdown_timeout);
246
247    for (server_id, handle) in server_handles {
248        log::info!("Stopping server {}", server_id);
249
250        if tokio::time::timeout(shutdown_timeout, handle.stop(true))
251            .await
252            .is_err()
253        {
254            log::warn!("Server {} shutdown timeout, forcing stop", server_id);
255            handle.stop(false).await;
256        }
257
258        // Persist stopped status
259        let _ = registry
260            .update_server_status(&server_id, ServerStatus::Stopped)
261            .await;
262    }
263
264    // Save analytics data before exit
265    crate::server::analytics::save_analytics_on_shutdown();
266
267    log::info!("Server system shutdown complete");
268    Ok(())
269}
270
271pub async fn validate_server_creation(
272    name: &str,
273    port: Option<u16>,
274) -> crate::core::error::Result<()> {
275    let config = Config::load().await?;
276    let context = get_shared_context();
277    let servers = context.servers.read().map_err(|e| {
278        crate::core::error::AppError::Validation(format!("servers lock poisoned: {}", e))
279    })?;
280
281    if servers.len() >= config.server.max_concurrent {
282        return Err(crate::core::error::AppError::Validation(format!(
283            "Server limit reached: {}/{}. Use 'cleanup' command to remove stopped servers.",
284            servers.len(),
285            config.server.max_concurrent
286        )));
287    }
288
289    if let Some(port) = port {
290        if port < config.server.port_range_start || port > config.server.port_range_end {
291            return Err(crate::core::error::AppError::Validation(format!(
292                "Port {} outside configured range {}-{}",
293                port, config.server.port_range_start, config.server.port_range_end
294            )));
295        }
296    }
297
298    if servers.values().any(|s| s.name == name) {
299        return Err(crate::core::error::AppError::Validation(format!(
300            "Server name '{}' already exists",
301            name
302        )));
303    }
304
305    Ok(())
306}
307
308pub async fn get_server_system_stats() -> serde_json::Value {
309    let config = Config::load().await.unwrap_or_default();
310    let context = get_shared_context();
311    let servers = match context.servers.read() {
312        Ok(s) => s,
313        Err(_) => return serde_json::json!({"error": "lock poisoned"}),
314    };
315
316    let running_count = servers
317        .values()
318        .filter(|s| s.status == ServerStatus::Running)
319        .count();
320    let stopped_count = servers
321        .values()
322        .filter(|s| s.status == ServerStatus::Stopped)
323        .count();
324    let failed_count = servers
325        .values()
326        .filter(|s| s.status == ServerStatus::Failed)
327        .count();
328
329    serde_json::json!({
330        "total_servers": servers.len(),
331        "running": running_count,
332        "stopped": stopped_count,
333        "failed": failed_count,
334        "max_concurrent": config.server.max_concurrent,
335        "utilization_percent": (servers.len() as f64 / config.server.max_concurrent as f64 * 100.0),
336        "port_range": format!("{}-{}", config.server.port_range_start, config.server.port_range_end),
337        "available_ports": config.server.port_range_end - config.server.port_range_start + 1,
338        "proxy": {
339            "enabled": config.proxy.enabled,
340            "http_port": config.proxy.port,
341            "https_port": config.proxy.port + config.proxy.https_port_offset,
342            "redirect_port": if is_port_available(80, "0.0.0.0") { None } else { Some(80) }
343        },
344        "config": {
345            "workers_per_server": config.server.workers,
346            "shutdown_timeout_sec": config.server.shutdown_timeout,
347            "startup_delay_ms": config.server.startup_delay_ms,
348            "logging": {
349                "max_file_size_mb": config.logging.max_file_size_mb,
350                "max_archives": config.logging.max_archive_files,
351                "compression": config.logging.compress_archives,
352                "request_logging": config.logging.log_requests,
353                "security_alerts": config.logging.log_security_alerts,
354                "performance_monitoring": config.logging.log_performance
355            }
356        }
357    })
358}
359
360pub async fn auto_start_servers() -> crate::core::error::Result<Vec<String>> {
361    let config = Config::load().await?;
362    let registry = get_persistent_registry();
363    let ctx = get_shared_context();
364    let auto_start_list = {
365        let servers = registry.load_servers().await?;
366        registry.get_auto_start_servers(&servers)
367    };
368
369    if auto_start_list.is_empty() {
370        return Ok(vec![]);
371    }
372
373    let max_to_start = config.server.max_concurrent.min(auto_start_list.len());
374    let mut started_servers = Vec::new();
375
376    for server in auto_start_list.iter().take(max_to_start) {
377        log::info!(
378            "Auto-starting server: {} on port {}",
379            server.name,
380            server.port
381        );
382
383        // Check port availability
384        if !is_port_available(server.port, &config.server.bind_address) {
385            log::warn!(
386                "Port {} not available for server '{}', skipping",
387                server.port,
388                server.name
389            );
390            continue;
391        }
392
393        let server_info = crate::server::types::ServerInfo::from(server.clone());
394
395        // Actually start the web server (bind port, serve HTTP)
396        match crate::server::handlers::web::create_web_server(ctx, server_info.clone(), &config) {
397            Ok(handle) => {
398                // Store handle
399                if let Ok(mut handles) = ctx.handles.write() {
400                    handles.insert(server_info.id.clone(), handle);
401                }
402
403                // Update status in memory
404                if let Ok(mut servers) = ctx.servers.write() {
405                    if let Some(s) = servers.get_mut(&server_info.id) {
406                        s.status = ServerStatus::Running;
407                    }
408                }
409
410                // Persist status
411                let server_id = server_info.id.clone();
412                tokio::spawn(async move {
413                    persist_server_update(&server_id, ServerStatus::Running).await;
414                });
415
416                started_servers.push(format!("{}:{}", server_info.name, server_info.port));
417                log::info!(
418                    "Server '{}' started on http://{}:{}",
419                    server_info.name,
420                    config.server.bind_address,
421                    server_info.port
422                );
423            }
424            Err(e) => {
425                log::error!(
426                    "Failed to auto-start server '{}': {}",
427                    server.name,
428                    e
429                );
430
431                // Mark as failed
432                let server_id = server_info.id.clone();
433                tokio::spawn(async move {
434                    persist_server_update(&server_id, ServerStatus::Failed).await;
435                });
436            }
437        }
438    }
439
440    if auto_start_list.len() > max_to_start {
441        log::warn!(
442            "Skipped {} auto-start servers due to max_concurrent limit of {}",
443            auto_start_list.len() - max_to_start,
444            config.server.max_concurrent
445        );
446    }
447
448    Ok(started_servers)
449}
450
451async fn start_http_redirect_server(config: &Config) -> crate::core::error::Result<()> {
452    let redirect_port = 80;
453    // In Docker, host port 443 maps to container port 3443.
454    // The redirect must use the EXTERNAL port that clients see (443), not the internal one (3443).
455    // EXTERNAL_HTTPS_PORT env var overrides the computed internal port.
456    let target_https_port = std::env::var("EXTERNAL_HTTPS_PORT")
457        .ok()
458        .and_then(|v| v.parse::<u16>().ok())
459        .unwrap_or_else(|| config.proxy.port + config.proxy.https_port_offset);
460
461    if !crate::server::utils::port::is_port_available(redirect_port, "0.0.0.0") {
462        log::warn!(
463            "Port {} already in use - HTTP redirect disabled",
464            redirect_port
465        );
466        return Ok(());
467    }
468
469    log::info!(
470        "Starting HTTP->HTTPS redirect server on port {}",
471        redirect_port
472    );
473
474    // Use std::thread::spawn to avoid Send requirements on the future
475    std::thread::spawn(move || {
476        // Single-threaded tokio runtime for the redirect server
477        let rt = tokio::runtime::Builder::new_current_thread()
478            .enable_all()
479            .build()
480            .expect("Failed to build single-thread runtime for redirect server");
481
482        rt.block_on(async move {
483            let redirect_server =
484                crate::server::redirect::HttpRedirectServer::new(redirect_port, target_https_port);
485
486            if let Err(e) = redirect_server.run().await {
487                log::error!("HTTP redirect server error: {}", e);
488            }
489        });
490    });
491
492    // Brief wait for startup
493    tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
494    log::info!(
495        "HTTP redirect active: Port {} → HTTPS Port {}",
496        redirect_port,
497        target_https_port
498    );
499
500    Ok(())
501}