//! SimAdmin - Debian SIM Management Service //! //! A backend service for managing Debian-based modem and SIM devices. //! Built with Rust + Axum + zbus. //! use anyhow::Result; use axum::{ extract::DefaultBodyLimit, http::{StatusCode, Uri}, middleware, response::{IntoResponse, Response}, routing::{delete, get, post}, Router, }; use clap::{Args as ClapArgs, Parser, Subcommand}; use socket2::{Domain, Protocol, Socket, Type}; use std::net::{IpAddr, Ipv6Addr, SocketAddr}; use std::path::PathBuf; use std::sync::atomic::AtomicBool; use std::sync::Arc; use tower_http::cors::{Any, CorsLayer}; use tracing::{info, warn}; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter}; use zbus::Connection; mod auth; mod automation; mod cell_lock_store; mod config; mod db; mod device_network; mod device_status; mod esim; mod handlers; mod iptables; mod models; mod modem_manager; mod notification; mod notification_queue; mod ota; mod serial; mod sms_listener; mod state; mod system_event; mod system_event_monitor; mod utils; mod verification_code; use config::{get_default_config_path, ConfigManager}; use db::Database; use device_network::DdnsManager; use esim::EsimSupervisor; use handlers::*; use modem_manager::{ensure_nm_modem_profile, init_data_connection}; use notification::NotificationSender; use notification_queue::*; use state::AppState; use system_event::{ codes as system_event_codes, severity as system_event_severity, status as system_event_status, SystemEventEmitter, }; /// 获取二进制文件同级目录下的 www 目录路径 fn get_www_dir() -> PathBuf { // 获取当前可执行文件的路径 let exe_path = std::env::current_exe().expect("Failed to get executable path"); // 获取可执行文件所在目录 let exe_dir = exe_path .parent() .expect("Failed to get executable directory"); // 拼接 www 目录 exe_dir.join("www") } fn get_data_db_path() -> PathBuf { std::env::current_exe() .expect("Failed to get executable path") .parent() .expect("Failed to get executable directory") .join("data.db") } /// SPA fallback handler - 对于所有前端路由返回 index.html async fn spa_fallback(uri: Uri) -> Response { let path = uri.path(); // 如果是 API 路由,返回 404(不应该走到这里,但作为保险) if path.starts_with("/api/") { return (StatusCode::NOT_FOUND, "API endpoint not found").into_response(); } // 获取 www 目录的绝对路径 let www_dir = get_www_dir(); // 构建请求文件的完整路径 let requested_path = if path == "/" { "/index.html" } else { path }; let file_path = www_dir.join(requested_path.trim_start_matches('/')); // 如果文件存在,返回文件内容 if let Ok(content) = tokio::fs::read(&file_path).await { // 根据文件扩展名设置正确的 Content-Type let content_type = match file_path.extension().and_then(|ext| ext.to_str()) { Some("html") => "text/html; charset=utf-8", Some("css") => "text/css; charset=utf-8", Some("js") => "application/javascript; charset=utf-8", Some("json") => "application/json", Some("png") => "image/png", Some("jpg") | Some("jpeg") => "image/jpeg", Some("gif") => "image/gif", Some("svg") => "image/svg+xml", Some("ico") => "image/x-icon", _ => "application/octet-stream", }; return ( StatusCode::OK, [(axum::http::header::CONTENT_TYPE, content_type)], content, ) .into_response(); } // 如果文件不存在,返回 index.html(SPA 路由) let index_path = www_dir.join("index.html"); match tokio::fs::read(&index_path).await { Ok(content) => ( StatusCode::OK, [(axum::http::header::CONTENT_TYPE, "text/html; charset=utf-8")], content, ) .into_response(), Err(_) => ( StatusCode::NOT_FOUND, format!( "index.html not found at {:?}. Please build the frontend first.", index_path ), ) .into_response(), } } /// 确保 ModemManager 开启 debug 提权模式,但为了防止日志爆炸, /// 启动后立刻通过 ExecStartPost 将日志级别降回 INFO。 /// 这完美绕过了 Modem.Command 的 Unauthorized 限制,同时保持系统纯净。 fn ensure_modemmanager_debug_override() { let override_dir = "/etc/systemd/system/ModemManager.service.d"; let override_file = "/etc/systemd/system/ModemManager.service.d/99-simadmin-debug.conf"; let desired_content = "\ # SimAdmin: enable ModemManager debug mode so that Modem.Command D-Bus # interface is available for AT+CRSM (SIM file read) and AT+CSCA? (SMSC query). # We immediately set logging level back to INFO via ExecStartPost to prevent log spam. [Service] ExecStart= ExecStart=/usr/sbin/ModemManager --debug ExecStartPost=-/usr/bin/busctl call org.freedesktop.ModemManager1 /org/freedesktop/ModemManager1 org.freedesktop.ModemManager1 SetLogging s \"INFO\" "; let needs_update = match std::fs::read_to_string(override_file) { Ok(content) => content != desired_content, Err(_) => true, }; if needs_update { tracing::info!("Applying ModemManager debug override (with silent logging)..."); let _ = std::fs::create_dir_all(override_dir); if let Err(e) = std::fs::write(override_file, desired_content) { tracing::warn!("Failed to write MM debug override: {}", e); return; } // Reload systemd & restart ModemManager silently let _ = std::process::Command::new("systemctl") .arg("daemon-reload") .output(); let _ = std::process::Command::new("systemctl") .args(["restart", "ModemManager.service"]) .output(); tracing::info!("ModemManager debug override applied and service restarted."); } } /// 解压 ZIP 文件到指定目录(供安装脚本使用,无需依赖 unzip / python3) fn run_extract_zip(archive: &str, target: &str) -> Result<()> { use std::io; let file = std::fs::File::open(archive) .map_err(|e| anyhow::anyhow!("Failed to open {}: {}", archive, e))?; let mut zip = zip::ZipArchive::new(file) .map_err(|e| anyhow::anyhow!("Invalid zip archive {}: {}", archive, e))?; let target_dir = std::path::Path::new(target); std::fs::create_dir_all(target_dir) .map_err(|e| anyhow::anyhow!("Failed to create target dir {}: {}", target, e))?; for i in 0..zip.len() { let mut entry = zip.by_index(i)?; let Some(path) = entry.enclosed_name().map(|p| target_dir.join(p)) else { continue; }; if entry.is_dir() { std::fs::create_dir_all(&path)?; continue; } if let Some(parent) = path.parent() { std::fs::create_dir_all(parent)?; } let mut outfile = std::fs::File::create(&path)?; io::copy(&mut entry, &mut outfile)?; // 保留可执行权限 #[cfg(unix)] { use std::os::unix::fs::PermissionsExt; if let Some(mode) = entry.unix_mode() { std::fs::set_permissions(&path, std::fs::Permissions::from_mode(mode))?; } } } println!("Extracted {} entries to {}", zip.len(), target); Ok(()) } #[derive(Parser, Debug)] #[command(name = "simadmin")] #[command(author, version, about, long_about = None)] struct Cli { #[command(subcommand)] command: Option, #[command(flatten)] serve: ServeArgs, } #[derive(Subcommand, Debug)] enum CliCommand { /// 启动 Web 管理服务 Serve(ServeArgs), /// 管理 Web 登录认证(用于 SSH 本机恢复) Auth { #[command(subcommand)] command: AuthCommand, }, /// 解压 ZIP 文件到指定目录(供安装脚本调用) ExtractZip { /// ZIP 文件路径 archive: String, /// 解压目标目录 target: String, }, } #[derive(Subcommand, Debug)] enum AuthCommand { /// 交互式重置管理员密码,并清空所有 Web 会话 ResetPassword, /// 清除管理员密码,让 Web UI 下次进入首次设置 Clear, } #[derive(ClapArgs, Debug, Clone)] struct ServeArgs { /// 监听端口 (默认: 3000) #[arg(short, long, default_value = "3000", env = "PORT")] port: u16, /// 监听地址 (默认: ::,双栈监听 IPv4/IPv6) #[arg(short = 'H', long, default_value = "::", env = "HOST")] host: String, } #[tokio::main] async fn main() -> Result<()> { // 初始化 tracing 日志框架 // 通过 RUST_LOG 环境变量控制日志级别,默认为 info tracing_subscriber::registry() .with(EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"))) .with(tracing_subscriber::fmt::layer().with_target(false)) .init(); // 解析命令行参数 let cli = Cli::parse(); // 处理非服务子命令 if let Some(CliCommand::ExtractZip { archive, target }) = &cli.command { return run_extract_zip(archive, target); } if let Some(CliCommand::Auth { command }) = &cli.command { let db = Database::new(get_data_db_path())?; let config_manager = ConfigManager::new(get_default_config_path()); let security = config_manager.get_security(); return match command { AuthCommand::ResetPassword => auth::reset_admin_password_interactive(&db, &security), AuthCommand::Clear => auth::clear_admin_auth(&db), }; } let args = match cli.command { Some(CliCommand::Serve(args)) => args, None => cli.serve, _ => unreachable!(), }; let bind_addr = display_bind_addr(&args.host, args.port); // 确保 ModemManager 已提权以支持 AT 指令读取短信中心 ensure_modemmanager_debug_override(); // Connect to system D-Bus let dbus_conn = Arc::new(Connection::system().await?); // 创建 SMS 数据库(存储在可执行文件同级目录) let db_path = get_data_db_path(); let app_db = Arc::new(Database::new(db_path)?); // 初始化配置管理器 let config_path = get_default_config_path(); info!(path = ?config_path, "Loading config"); let config_manager = Arc::new(ConfigManager::new(config_path)); let data_user_disabled = Arc::new(AtomicBool::new(!config_manager.get_data_enabled())); let airplane_mode_requested = Arc::new(AtomicBool::new(false)); let cell_monitoring_active = Arc::new(AtomicBool::new(false)); let esim_supervisor = Arc::new(EsimSupervisor::new(Arc::clone(&config_manager))); let nm_result = ensure_nm_modem_profile().await; tracing::info!(result = %nm_result, "NetworkManager modem profile setup completed"); // 初始化通知发送器 let notification_sender = Arc::new(NotificationSender::new( Arc::clone(&config_manager), Arc::clone(&dbus_conn), Arc::clone(&app_db), )); let system_event_emitter = Arc::new(SystemEventEmitter::new(Arc::clone(¬ification_sender))); let (sms_resync, sms_resync_rx) = sms_listener::sms_resync_channel(); let ddns_manager = Arc::new(DdnsManager::new()); { let notification_queue_worker = Arc::clone(¬ification_sender); tokio::spawn(async move { notification_queue_worker.run_queue_worker().await; }); } system_event_monitor::spawn_system_event_monitor( Arc::clone(&system_event_emitter), Arc::clone(&dbus_conn), ); device_status::spawn_device_status_scheduler( Arc::clone(&config_manager), Arc::clone(¬ification_sender), Arc::clone(&app_db), Arc::clone(&dbus_conn), Arc::clone(&ddns_manager), ); { let ddns_manager_clone = Arc::clone(&ddns_manager); let config_clone = Arc::clone(&config_manager); let notification_clone = Arc::clone(¬ification_sender); tokio::spawn(async move { tokio::time::sleep(tokio::time::Duration::from_secs(10)).await; loop { let config = config_clone.get_ddns_config(); let interval = config.interval_seconds.max(60); if config.enabled { if let Err(err) = ddns_manager_clone .sync_now(Arc::clone(&config_clone), Arc::clone(¬ification_clone)) .await { tracing::warn!(error = %err, "DDNS background sync failed"); } } tokio::time::sleep(tokio::time::Duration::from_secs(interval)).await; } }); } { let config_clone = Arc::clone(&config_manager); let notification_clone = Arc::clone(¬ification_sender); tokio::spawn(async move { loop { tokio::time::sleep(crate::ota::duration_until_next_update_check()).await; let config = config_clone.get_version_update_notifications(); if config.enabled { if let Err(err) = crate::ota::check_and_notify_version_update( Arc::clone(&config_clone), Arc::clone(¬ification_clone), ) .await { tracing::warn!(error = %err, "Version update notification check failed"); } } } }); } // 启动 SMS 监听线程 { let conn_clone = Connection::system().await?; let db_clone = Arc::clone(&app_db); let notification_clone = Arc::clone(¬ification_sender); let resync_rx = sms_resync_rx; tokio::spawn(async move { let _ = sms_listener::start_sms_listener( conn_clone, db_clone, notification_clone, resync_rx, ) .await; }); } // 电话监听暂不启用 // 自动初始化数据连接 { let conn_clone = Arc::clone(&dbus_conn); let user_off = Arc::clone(&data_user_disabled); let cfg = Arc::clone(&config_manager); tokio::spawn(async move { // 等待 2 秒让 modem 完全初始化 tokio::time::sleep(tokio::time::Duration::from_secs(2)).await; let allow_roaming = cfg.get_roaming_allowed(); let apn_config = cfg.get_apn_config(); let result = init_data_connection( conn_clone.as_ref(), user_off.as_ref(), allow_roaming, Some(apn_config), ) .await; tracing::info!(result = %result, "Auto-connect completed"); }); } // 启动数据连接 Watchdog(每 15 秒检查一次) { let conn_clone = Arc::clone(&dbus_conn); let user_off = Arc::clone(&data_user_disabled); let airplane_requested = Arc::clone(&airplane_mode_requested); let cfg = Arc::clone(&config_manager); let system_events = Arc::clone(&system_event_emitter); tokio::spawn(async move { // 初始延迟 5 秒,等待系统稳定 tokio::time::sleep(tokio::time::Duration::from_secs(5)).await; tracing::info!(interval = 15, "Watchdog started"); modem_manager::data_connection_watchdog( conn_clone, 15, user_off, airplane_requested, cfg, system_events, ) .await; }); } system_event_emitter .emit_code( system_event_codes::SYSTEM_SERVICE_STARTED, system_event_severity::INFO, system_event_status::SUCCEEDED, "simadmin", "SimAdmin 服务启动完成", ) .await; // CORS 配置:允许前端开发服务器跨域访问 let cors = CorsLayer::new() .allow_origin(Any) .allow_methods(Any) .allow_headers(Any); // 创建统一的应用状态 spawn_system_stats_sampler(Arc::clone(&dbus_conn)); let app_state = AppState::new( dbus_conn, app_db, config_manager, notification_sender, system_event_emitter, ddns_manager, Arc::clone(&esim_supervisor), sms_resync, data_user_disabled, airplane_mode_requested, cell_monitoring_active, ); // 启动自动化中心后台调度引擎 automation::spawn_automation_scheduler(app_state.clone()); // Build protected routes - 使用统一的 AppState let protected_routes = Router::new() // ========== 设备信息接口 ========== .route("/api/device", get(get_device_info).options(options_handler)) // ========== SIM 卡接口 ========== .route("/api/sim", get(get_sim_info).options(options_handler)) .route( "/api/sim/details/refresh", post(refresh_sim_details_handler).options(options_handler), ) .route( "/api/sim/cache", post(update_sim_cache_handler).options(options_handler), ) // ========== 网络接口 ========== .route( "/api/network", get(get_network_info).options(options_handler), ) .route("/api/cells", get(get_cells).options(options_handler)) .route( "/api/cell-monitor/start", post(start_cell_monitor_handler).options(options_handler), ) .route( "/api/cell-monitor/stop", post(stop_cell_monitor_handler).options(options_handler), ) .route( "/api/radio-mode", get(get_radio_mode_handler) .post(set_radio_mode_handler) .options(options_handler), ) .route( "/api/band-lock", get(get_band_lock_handler) .post(set_band_lock_handler) .options(options_handler), ) .route( "/api/network/interfaces", get(get_network_interfaces_info).options(options_handler), ) .route( "/api/network/connection-addresses", get(get_network_connection_addresses).options(options_handler), ) .route( "/api/device-network/ddns/config", get(get_device_ddns_config_handler) .post(set_device_ddns_config_handler) .options(options_handler), ) .route( "/api/device-network/ddns/status", get(get_device_ddns_status_handler).options(options_handler), ) .route( "/api/device-network/ddns/sync", post(sync_device_ddns_handler).options(options_handler), ) .route( "/api/device-network/ddns/logs", get(get_device_ddns_logs_handler).options(options_handler), ) .route( "/api/device-network/ddns/logs/clear", post(clear_device_ddns_logs_handler).options(options_handler), ) .route( "/api/device-network/wlan/status", get(get_device_wlan_status_handler).options(options_handler), ) .route( "/api/device-network/wlan/enabled", post(set_device_wlan_enabled_handler).options(options_handler), ) .route( "/api/device-network/wlan/scan", post(scan_device_wlan_handler).options(options_handler), ) .route( "/api/device-network/wlan/profiles", get(get_device_wlan_profiles_handler).options(options_handler), ) .route( "/api/device-network/wlan/forget", post(forget_device_wlan_handler).options(options_handler), ) .route( "/api/device-network/wlan/connect", post(connect_device_wlan_handler).options(options_handler), ) .route( "/api/device-network/wlan/disconnect", post(disconnect_device_wlan_handler).options(options_handler), ) .route( "/api/device-network/wlan/profile", post(save_device_wlan_profile_handler).options(options_handler), ) .route( "/api/network/signal-strength", get(get_signal_strength_handler).options(options_handler), ) .route( "/api/location/cell-info", get(get_cell_location_handler).options(options_handler), ) .route( "/api/network/operators", get(get_network_operators).options(options_handler), ) .route( "/api/network/operators/scan", get(scan_network_operators).options(options_handler), ) .route( "/api/network/register-manual", post(register_network_manual).options(options_handler), ) .route( "/api/network/register-auto", post(register_network_auto).options(options_handler), ) .route( "/api/apn", get(get_apn_list_handler) .post(set_apn_handler) .options(options_handler), ) .route( "/api/cell-lock", get(get_cell_lock_status_handler) .post(set_cell_lock_handler) .options(options_handler), ) .route( "/api/cell-lock/unlock-all", post(unlock_all_cells_handler).options(options_handler), ) // ========== 数据连接接口 ========== .route( "/api/data", get(get_data_status) .post(set_data_status) .options(options_handler), ) .route( "/api/roaming", get(get_roaming_status_handler) .post(set_roaming_status_handler) .options(options_handler), ) .route( "/api/airplane-mode", get(get_airplane_mode_handler) .post(set_airplane_mode_handler) .options(options_handler), ) .route( "/api/baseband/restart", post(restart_baseband_handler).options(options_handler), ) .route( "/api/baseband/restart/status", get(get_baseband_restart_status_handler).options(options_handler), ) // ========== 工作模式 / eSIM 管理 ========== .route( "/api/work-mode", get(get_work_mode_handler) .post(set_work_mode_handler) .options(options_handler), ) .route( "/api/esim/config", get(get_esim_config_handler) .post(set_esim_config_handler) .options(options_handler), ) .route( "/api/esim/lpac/status", get(get_esim_lpac_status_handler).options(options_handler), ) .route( "/api/esim/lpac/repair", post(repair_esim_lpac_handler).options(options_handler), ) .route( "/api/esim/euicc", get(get_esim_euicc_handler).options(options_handler), ) .route( "/api/esim/profiles", get(get_esim_profiles_handler) .post(download_esim_profile_handler) .options(options_handler), ) .route( "/api/esim/profiles/{iccid}/enable", post(enable_esim_profile_handler).options(options_handler), ) .route( "/api/esim/profiles/{iccid}/rename", post(rename_esim_profile_handler).options(options_handler), ) .route( "/api/esim/profiles/{iccid}", delete(delete_esim_profile_handler).options(options_handler), ) // ========== 电话功能接口 ========== .route( "/api/calls", get(get_calls_handler).options(options_handler), ) .route( "/api/call/dial", post(dial_call_handler).options(options_handler), ) .route( "/api/call/hangup", post(hangup_call_handler).options(options_handler), ) .route( "/api/call/hangup-all", post(hangup_all_calls_handler).options(options_handler), ) .route( "/api/call/answer", post(answer_call_handler).options(options_handler), ) .route( "/api/call/volume", get(get_call_volume_handler) .post(set_call_volume_handler) .options(options_handler), ) .route( "/api/call/forwarding", get(get_call_forwarding_handler) .post(set_call_forwarding_handler) .options(options_handler), ) .route( "/api/call/settings", get(get_call_settings_handler) .post(set_call_settings_handler) .options(options_handler), ) .route( "/api/call/history", get(get_call_history_handler).options(options_handler), ) .route( "/api/call/history/{id}", axum::routing::delete(delete_call_history_handler).options(options_handler), ) .route( "/api/call/history/clear", post(clear_call_history_handler).options(options_handler), ) .route( "/api/ims/status", get(get_ims_status_handler).options(options_handler), ) .route( "/api/voicemail/status", get(get_voicemail_status_handler).options(options_handler), ) // ========== 短信功能接口 ========== .route( "/api/sms/send", post(send_sms_handler).options(options_handler), ) .route( "/api/sms/list", get(get_sms_list_handler).options(options_handler), ) .route( "/api/sms/conversation", get(get_sms_conversation_handler).options(options_handler), ) .route( "/api/sms/stats", get(get_sms_stats_handler).options(options_handler), ) .route( "/api/sms/batch-delete", post(delete_sms_batch_handler).options(options_handler), ) .route( "/api/sms/conversation/{phone_number}", axum::routing::delete(delete_sms_conversation_handler).options(options_handler), ) .route( "/api/sms/message/{id}", axum::routing::delete(delete_sms_message_handler).options(options_handler), ) .route( "/api/sms/clear", post(clear_sms_handler).options(options_handler), ) // ========== 系统接口 ========== .route("/api/stats", get(get_system_stats).options(options_handler)) .route("/api/stats/cpu", get(get_cpu_info).options(options_handler)) .route( "/api/connectivity", get(get_connectivity_check).options(options_handler), ) .route( "/api/system/reboot", post(system_reboot).options(options_handler), ) .route( "/api/service/restart", post(restart_service_handler).options(options_handler), ) // ========== 通知配置接口 ========== .route( "/api/notifications/config", get(get_notification_config_handler) .post(set_notification_config_handler) .options(options_handler), ) .route( "/api/notifications/test/{channel}", post(test_notification_channel_handler).options(options_handler), ) // ========== OTA 更新接口 ========== .route( "/api/notifications/logs", get(get_notification_logs_handler).options(options_handler), ) .route( "/api/notifications/logs/clear", post(clear_notification_logs_handler).options(options_handler), ) .route( "/api/notifications/queue", get(get_notification_queue_handler).options(options_handler), ) .route( "/api/notifications/queue/retry-all", post(retry_all_notification_queue_handler).options(options_handler), ) .route( "/api/notifications/queue/clear", post(clear_notification_queue_handler).options(options_handler), ) .route( "/api/notifications/queue/{id}", delete(delete_notification_queue_item_handler).options(options_handler), ) .route( "/api/notifications/queue/{id}/retry", post(retry_notification_queue_item_handler).options(options_handler), ) // ========== 自动化中心接口 ========== .route( "/api/automation/config", get(get_automation_config_handler) .post(set_automation_config_handler) .options(options_handler), ) .route( "/api/automation/logs", get(get_automation_logs_handler).options(options_handler), ) .route( "/api/automation/logs/clear", post(clear_automation_logs_handler).options(options_handler), ) .route( "/api/automation/test/{task_id}", post(test_automation_task_handler).options(options_handler), ) .route( "/api/ota/status", get(get_ota_status_handler).options(options_handler), ) .route( "/api/ota/upload", post(upload_ota_handler) .options(options_handler) .layer(DefaultBodyLimit::max(50 * 1024 * 1024)), ) .route( "/api/ota/latest-release", post(get_latest_ota_release_handler).options(options_handler), ) .route( "/api/ota/online-prepare", post(prepare_online_ota_handler).options(options_handler), ) .route( "/api/ota/apply", post(apply_ota_handler).options(options_handler), ) .route( "/api/ota/cancel", post(cancel_ota_handler).options(options_handler), ) .route( "/api/auth/password", post(auth::change_password).options(options_handler), ) .route( "/api/auth/settings", get(auth::get_settings) .post(auth::set_settings) .options(options_handler), ) .route_layer(middleware::from_fn_with_state( app_state.clone(), auth::auth_middleware, )); let app = Router::new() .route("/api/health", get(health_check).options(options_handler)) .route( "/api/auth/status", get(auth::status).options(options_handler), ) .route( "/api/auth/setup", post(auth::setup).options(options_handler), ) .route( "/api/auth/login", post(auth::login).options(options_handler), ) .route( "/api/auth/logout", post(auth::logout).options(options_handler), ) .merge(protected_routes) .with_state(app_state) .layer(cors) .fallback(spa_fallback); // Start server - 显示版权信息 info!( version = env!("APP_VERSION"), branch = env!("GIT_BRANCH"), commit = env!("GIT_COMMIT"), "SimAdmin - Debian SIM Management Service" ); info!("Copyright © 2026 GitHub 3899 - SimAdmin"); // 绑定端口,如果被占用则轮询等待(最多 30 秒) let listener = bind_with_retry(&args.host, args.port, 30).await?; info!(addr = %bind_addr, "Server listening"); // 使用优雅关闭 axum::serve(listener, app) .with_graceful_shutdown(shutdown_signal()) .await?; Ok(()) } /// 绑定端口,如果被占用则轮询等待 fn display_bind_addr(host: &str, port: u16) -> String { let host = host.trim_matches(|c| c == '[' || c == ']'); if host.contains(':') { format!("[{host}]:{port}") } else { format!("{host}:{port}") } } async fn bind_listener(host: &str, port: u16) -> std::io::Result { let normalized_host = host.trim_matches(|c| c == '[' || c == ']'); if normalized_host == "::" { let socket = Socket::new(Domain::IPV6, Type::STREAM, Some(Protocol::TCP))?; socket.set_reuse_address(true)?; socket.set_only_v6(false)?; socket.bind(&SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), port).into())?; socket.listen(1024)?; socket.set_nonblocking(true)?; let listener: std::net::TcpListener = socket.into(); return tokio::net::TcpListener::from_std(listener); } tokio::net::TcpListener::bind((normalized_host, port)).await } async fn bind_with_retry( host: &str, port: u16, max_retries: u32, ) -> Result { use std::time::Duration; let addr = display_bind_addr(host, port); for i in 0..max_retries { match bind_listener(host, port).await { Ok(listener) => return Ok(listener), Err(e) => { if i == 0 { warn!(addr = %addr, "Port busy, waiting for release..."); } if i + 1 < max_retries { tokio::time::sleep(Duration::from_secs(1)).await; } else { return Err(anyhow::anyhow!("Failed to bind to {}: {}", addr, e)); } } } } unreachable!() } /// 监听 Ctrl+C 和 SIGTERM 信号,用于优雅关闭 async fn shutdown_signal() { use tokio::signal; let ctrl_c = async { signal::ctrl_c() .await .expect("Failed to install Ctrl+C signal handler"); }; #[cfg(unix)] let terminate = async { signal::unix::signal(signal::unix::SignalKind::terminate()) .expect("Failed to install SIGTERM signal handler") .recv() .await; }; #[cfg(not(unix))] let terminate = std::future::pending::<()>(); tokio::select! { _ = ctrl_c => {}, _ = terminate => {}, } }