use std::fs; use std::path::PathBuf; use clap::Parser; use serde::{Deserialize, Serialize}; use tracing::{info, warn}; /// 命令行参数 #[derive(Parser, Debug)] #[command(name = "rcoder")] #[command(about = "RCoder - Rust-based AI Agent Framework")] #[command(version)] pub struct CliArgs { /// 主服务端口 #[arg(short = 'p', long)] pub port: Option, /// 项目工作目录 #[arg(short = 'd', long, default_value = "./project_workspace")] pub projects_dir: Option, /// 启用反向代理 #[arg(short, long)] pub enable_proxy: bool, /// 代理服务端口 #[arg(long = "proxy-port")] pub proxy_port: Option, /// 默认后端服务端口 #[arg(long = "backend-port")] pub default_backend_port: Option, } // 从 shared_types 导入 API Key 鉴权配置 pub use shared_types::ApiKeyAuthConfig; /// 应用程序配置 #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AppConfig { /// 默认使用的 Agent ID #[serde(default = "default_agent_id", alias = "default_agent")] pub default_agent_id: String, /// 项目工作目录 pub projects_dir: PathBuf, /// 主服务端口 pub port: u16, /// 反向代理配置 pub proxy_config: Option, /// Docker 配置 pub docker_config: Option, /// 容器清理配置 #[serde(default)] pub cleanup_config: CleanupConfigSettings, /// API Key 鉴权配置 #[serde(default)] pub api_key_auth: ApiKeyAuthConfig, } fn default_agent_id() -> String { "claude-code-acp-ts".to_string() } /// 生成随机 API Key /// 使用 UUID v4 生成随机密钥,格式:sk-{uuid} fn generate_random_api_key() -> String { use uuid::Uuid; let uuid = Uuid::new_v4(); format!("sk-{}", uuid.simple()) } /// 健康检查配置 #[derive(Debug, Clone, Serialize, Deserialize)] pub struct HealthCheckConfig { /// 是否启用健康检查 pub enabled: bool, /// 检查间隔(秒) pub interval_seconds: u64, /// 超时时间(秒) pub timeout_seconds: u64, /// 健康阈值 pub healthy_threshold: u32, /// 不健康阈值 pub unhealthy_threshold: u32, } /// 反向代理配置 #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ProxyConfig { /// 代理服务监听端口 pub listen_port: u16, /// 默认后端服务端口 pub default_backend_port: u16, /// 后端服务主机地址 pub backend_host: String, /// 端口参数名称 pub port_param: String, /// 健康检查配置 pub health_check: HealthCheckConfig, } /// 日志清理配置 #[derive(Debug, Clone, Serialize, Deserialize)] pub struct LogCleanupConfig { /// 日志目录路径 #[serde(default = "default_log_dir")] pub log_dir: String, /// 日志保留天数,默认10天 #[serde(default = "default_log_retention_days")] pub log_retention_days: u64, } fn default_log_dir() -> String { "/app/logs/container".to_string() } fn default_log_retention_days() -> u64 { 10 } impl Default for LogCleanupConfig { fn default() -> Self { Self { log_dir: default_log_dir(), log_retention_days: default_log_retention_days(), } } } /// 容器清理配置(配置文件格式,使用秒作为单位) #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CleanupConfigSettings { /// 是否启用容器清理功能(默认禁用,更安全) #[serde(default = "default_cleanup_enabled")] pub enabled: bool, /// 闲置超时时间(秒),默认600秒(10分钟) #[serde(default = "default_idle_timeout_seconds")] pub idle_timeout_seconds: u64, /// 清理检查间隔(秒),默认300秒(5分钟) #[serde(default = "default_cleanup_interval_seconds")] pub cleanup_interval_seconds: u64, /// Docker容器停止超时时间(秒),默认30秒 #[serde(default = "default_docker_stop_timeout_seconds")] pub docker_stop_timeout_seconds: u64, /// 容器最小保护时间(秒),默认300秒(5分钟) #[serde(default = "default_container_protection_seconds")] pub container_protection_seconds: u64, /// 日志清理配置 #[serde(default)] pub log_cleanup: LogCleanupConfig, } fn default_cleanup_enabled() -> bool { true // 默认启用容器清理功能 } fn default_idle_timeout_seconds() -> u64 { 600 // 10分钟 } fn default_cleanup_interval_seconds() -> u64 { 300 // 5分钟 } fn default_docker_stop_timeout_seconds() -> u64 { 30 } fn default_container_protection_seconds() -> u64 { 300 // 5分钟 } impl Default for CleanupConfigSettings { fn default() -> Self { Self { enabled: default_cleanup_enabled(), idle_timeout_seconds: default_idle_timeout_seconds(), cleanup_interval_seconds: default_cleanup_interval_seconds(), docker_stop_timeout_seconds: default_docker_stop_timeout_seconds(), container_protection_seconds: default_container_protection_seconds(), log_cleanup: LogCleanupConfig::default(), } } } /// Docker 配置 #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(default)] pub struct DockerConfig { /// 多镜像配置 pub multi_image_config: Option, /// 网络模式 pub network_mode: Option, /// 工作目录 pub work_dir: Option, /// 自动清理 pub auto_cleanup: Option, /// 容器存活时间(秒) pub container_ttl_seconds: Option, /// 网络基础名称(不含 project name 前缀) /// Docker Compose 会自动添加 project name 前缀,实际网络名称为 {project_name}_{network_base_name} /// 例如: network_base_name="agent-network" 时,实际网络为 "rcoder_agent-network" pub network_base_name: Option, /// 🔧 Docker API 调用超时时间(秒) pub api_timeout_seconds: Option, /// 🔧 快速操作超时时间(秒) pub api_timeout_quick_seconds: Option, /// 🔧 状态缓存 TTL(秒) pub cache_status_ttl_seconds: Option, /// 🔧 网络缓存 TTL(秒) pub cache_network_ttl_seconds: Option, /// 🔧 缓存最大容量 pub cache_max_capacity: Option, } pub const CONFIG_FILE: &str = "config.yml"; impl Default for AppConfig { fn default() -> Self { Self { default_agent_id: default_agent_id(), projects_dir: PathBuf::from("./project_workspace"), port: 8087, proxy_config: Some(ProxyConfig::default()), docker_config: Some(DockerConfig::default()), cleanup_config: CleanupConfigSettings::default(), api_key_auth: ApiKeyAuthConfig { enabled: false, api_key: generate_random_api_key(), }, } } } impl Default for ProxyConfig { fn default() -> Self { Self { listen_port: 8088, default_backend_port: 8086, backend_host: "127.0.0.1".to_string(), port_param: "port".to_string(), health_check: HealthCheckConfig::default(), } } } impl Default for HealthCheckConfig { fn default() -> Self { Self { enabled: true, interval_seconds: 5, timeout_seconds: 1, healthy_threshold: 2, unhealthy_threshold: 3, } } } impl Default for DockerConfig { fn default() -> Self { Self { multi_image_config: Some(shared_types::create_default_multi_image_config()), network_mode: Some("bridge".to_string()), work_dir: Some("/app".to_string()), auto_cleanup: Some(true), container_ttl_seconds: Some(3600), network_base_name: Some("agent-network".to_string()), // 🔧 新增字段默认值 api_timeout_seconds: Some(10), api_timeout_quick_seconds: Some(5), cache_status_ttl_seconds: Some(10), cache_network_ttl_seconds: Some(15), cache_max_capacity: Some(10000), } } } impl DockerConfig { /// 获取多镜像配置,如果没有配置多镜像配置,会从传统配置自动转换 pub fn get_multi_image_config(&self) -> shared_types::MultiImageConfig { if let Some(ref multi_config) = self.multi_image_config { multi_config.clone() } else { // 从传统配置创建多镜像配置 self.create_legacy_multi_config() } } /// 从传统配置创建多镜像配置 fn create_legacy_multi_config(&self) -> shared_types::MultiImageConfig { info!("Config created from legacy config"); // 创建基于传统配置的多镜像配置 let mut services = std::collections::HashMap::new(); // 为 RCoder 服务使用默认配置 let rcoder_service = { info!("Using default config"); shared_types::service_config::default_rcoder_service_config() }; services.insert("rcoder".to_string(), rcoder_service); // 为 AgentRunner 服务使用默认配置 services.insert( "agent-runner".to_string(), shared_types::service_config::default_agent_runner_service_config(), ); shared_types::MultiImageConfig { services, global_defaults: shared_types::GlobalImageDefaults { image: None, arm64_image: None, amd64_image: None, default_image: None, registry_prefix: None, }, selection_strategy: shared_types::ImageSelectionStrategy::ServiceOnly, cache_config: shared_types::ImageCacheConfig { enabled: true, ttl_seconds: 3600, max_entries: 100, }, } } /// 验证多镜像配置 pub fn validate_multi_image_config(&self) -> Result<(), String> { let multi_config = self.get_multi_image_config(); match multi_config.validate() { Ok(()) => Ok(()), Err(e) => Err(e.to_string()), } } /// 检查是否使用多镜像配置 pub fn is_using_multi_image_config(&self) -> bool { self.multi_image_config.is_some() } /// 应用环境变量覆盖 pub fn apply_env_overrides(&mut self) -> anyhow::Result<()> { // 应用网络模式 if let Ok(val) = std::env::var("RCODER_NETWORK_MODE") { info!("RCODER_NETWORK_MODE overridden"); self.network_mode = Some(val); } // 应用网络基础名称 if let Ok(val) = std::env::var("RCODER_NETWORK_BASE_NAME") { info!("RCODER_NETWORK_BASE_NAME: {}", val); self.network_base_name = Some(val); } // 应用工作目录 if let Ok(val) = std::env::var("RCODER_WORK_DIR") { info!("RCODER_WORK_DIR overridden"); self.work_dir = Some(val); } // 应用自动清理 if let Ok(val) = std::env::var("RCODER_AUTO_CLEANUP") { info!("RCODER_AUTO_CLEANUP overridden"); self.auto_cleanup = Some(val.parse().unwrap_or(true)); } // 应用容器存活时间 if let Ok(val) = std::env::var("RCODER_CONTAINER_TTL") { info!("RCODER_CONTAINER_TTL overridden"); match val.parse() { Ok(seconds) => self.container_ttl_seconds = Some(seconds), Err(e) => { tracing::warn!( "⚠️ [CONFIG] Failed to parse RCODER_CONTAINER_TTL '{}': {}, using default", val, e ); } } } // 🔧 应用 API 超时配置 if let Ok(val) = std::env::var("RCODER_API_TIMEOUT_SECONDS") { info!("RCODER_API_TIMEOUT_SECONDS overridden"); match val.parse() { Ok(seconds) => self.api_timeout_seconds = Some(seconds), Err(e) => { tracing::warn!( "⚠️ [CONFIG] Failed to parse RCODER_API_TIMEOUT_SECONDS '{}': {}, using default", val, e ); } } } // 🔧 应用快速操作超时配置 if let Ok(val) = std::env::var("RCODER_API_TIMEOUT_QUICK_SECONDS") { info!("RCODER_API_TIMEOUT_QUICK_SECONDS overridden"); match val.parse() { Ok(seconds) => self.api_timeout_quick_seconds = Some(seconds), Err(e) => { tracing::warn!( "⚠️ [CONFIG] Failed to parse RCODER_API_TIMEOUT_QUICK_SECONDS '{}': {}, using default", val, e ); } } } // 🔧 应用状态缓存 TTL 配置 if let Ok(val) = std::env::var("RCODER_CACHE_STATUS_TTL_SECONDS") { info!("RCODER_CACHE_STATUS_TTL_SECONDS overridden"); match val.parse() { Ok(seconds) => self.cache_status_ttl_seconds = Some(seconds), Err(e) => { tracing::warn!( "⚠️ [CONFIG] Failed to parse RCODER_CACHE_STATUS_TTL_SECONDS '{}': {}, using default", val, e ); } } } // 🔧 应用网络缓存 TTL 配置 if let Ok(val) = std::env::var("RCODER_CACHE_NETWORK_TTL_SECONDS") { info!("RCODER_CACHE_NETWORK_TTL_SECONDS overridden"); match val.parse() { Ok(seconds) => self.cache_network_ttl_seconds = Some(seconds), Err(e) => { tracing::warn!( "⚠️ [CONFIG] Failed to parse RCODER_CACHE_NETWORK_TTL_SECONDS '{}': {}, using default", val, e ); } } } // 🔧 应用缓存最大容量配置 if let Ok(val) = std::env::var("RCODER_CACHE_MAX_CAPACITY") { info!("RCODER_CACHE_MAX_CAPACITY overridden"); match val.parse() { Ok(capacity) => self.cache_max_capacity = Some(capacity), Err(e) => { tracing::warn!( "⚠️ [CONFIG] Failed to parse RCODER_CACHE_MAX_CAPACITY '{}': {}, using default", val, e ); } } } Ok(()) } /// Get configuration summary pub fn get_summary(&self) -> String { format!( "Docker config: network_mode={}, network_base_name={}, work_dir={}, auto_cleanup={}, container_ttl={}, api_timeout={}s, quick_timeout={}s, status_cache={}s, network_cache={}s, cache_max_capacity={}", self.network_mode.as_deref().unwrap_or("default"), self.network_base_name.as_deref().unwrap_or("agent-network"), self.work_dir.as_deref().unwrap_or("/app"), self.auto_cleanup.unwrap_or(true), self.container_ttl_seconds.unwrap_or(3600), self.api_timeout_seconds.unwrap_or(10), self.api_timeout_quick_seconds.unwrap_or(5), self.cache_status_ttl_seconds.unwrap_or(10), self.cache_network_ttl_seconds.unwrap_or(15), self.cache_max_capacity.unwrap_or(10000) ) } } /// 加载配置(命令行参数 + 配置文件 + 环境变量) pub fn load_config_with_args(cli_args: CliArgs) -> anyhow::Result { let mut config = if std::path::Path::new(CONFIG_FILE).exists() { // 尝试从文件加载配置 match load_config_from_file() { Ok(file_config) => { info!("Config file already loaded: {}", CONFIG_FILE); file_config } Err(e) => { warn!("Failed to load config file, using default config: {}", e); AppConfig::default() } } } else { info!( "configfilenot found, createddefaultconfigfile: {}", CONFIG_FILE ); let default_config = AppConfig::default(); create_default_config_file(&default_config)?; default_config }; // 命令行参数覆盖配置文件 if let Some(port) = cli_args.port { config.port = port; } if let Some(projects_dir) = cli_args.projects_dir { config.projects_dir = PathBuf::from(projects_dir); } // 环境变量覆盖所有配置 if let Ok(port) = std::env::var("RCODER_PORT") { if let Ok(port) = port.parse::() { config.port = port; } else { warn!(" parse RCODER_PORT failed: {}", port); } } if let Ok(projects_dir) = std::env::var("RCODER_PROJECTS_DIR") { config.projects_dir = PathBuf::from(projects_dir); } // 如果启用了代理,配置代理相关参数 if cli_args.enable_proxy { let mut proxy_config = ProxyConfig::default(); if let Some(proxy_port) = cli_args.proxy_port { proxy_config.listen_port = proxy_port; } if let Some(default_backend_port) = cli_args.default_backend_port { proxy_config.default_backend_port = default_backend_port; } config.proxy_config = Some(proxy_config); } // 应用 Docker 配置的环境变量覆盖 if let Some(docker_config) = &mut config.docker_config { docker_config.apply_env_overrides()?; } // 应用 API Key 配置的环境变量覆盖 if let Ok(val) = std::env::var("RCODER_API_KEY_ENABLED") { if let Ok(enabled) = val.parse::() { config.api_key_auth.enabled = enabled; info!(" RCODER_API_KEY_ENABLED: {}", enabled); } else { warn!(" parse RCODER_API_KEY_ENABLED failed: {}", val); } } if let Ok(val) = std::env::var("RCODER_API_KEY") { config.api_key_auth.api_key = val.clone(); info!(" RCODER_API_KEY configured"); } // 验证 API Key 配置 if config.api_key_auth.enabled && config.api_key_auth.api_key.trim().is_empty() { return Err(anyhow::anyhow!( "API Key authentication is enabled but API Key is empty, please check config file or environment variables" )); } // 配置验证 if let Some(docker_config) = &config.docker_config { if let Err(e) = docker_config.validate_multi_image_config() { return Err(anyhow::anyhow!("Docker configuration validation failed: {}", e)); } } info!( "Final config: port={}, projects_dir={:?}, default_agent_id={}, proxy_enabled={}", config.port, config.projects_dir, config.default_agent_id, config.proxy_config.is_some() ); Ok(config) } /// 加载配置(保留旧接口以保持兼容性) pub fn load_config() -> anyhow::Result { let cli_args = CliArgs { port: None, projects_dir: None, enable_proxy: false, proxy_port: None, default_backend_port: None, }; load_config_with_args(cli_args) } /// 从文件加载配置 fn load_config_from_file() -> anyhow::Result { let config_content = fs::read_to_string(CONFIG_FILE).map_err(|e| anyhow::anyhow!("Failed to read config file: {}", e))?; tracing::debug!("config file content: {}", config_content); let config: AppConfig = serde_yaml::from_str(&config_content) .map_err(|e| anyhow::anyhow!("Failed to parse config file: {}", e))?; // 调试:打印解析后的多镜像配置 if let Some(ref docker_config) = config.docker_config { tracing::info!("[CONFIG] docker_config is Some, checking multi_image_config"); if let Some(ref multi_config) = docker_config.multi_image_config { tracing::info!("[CONFIG] multi_image_config is Some, services count: {}", multi_config.services.len()); for (service_key, service_config) in &multi_config.services { tracing::info!("[CONFIG] Service '{}': arm64_image={:?}, amd64_image={:?}", service_key, service_config.arm64_image, service_config.amd64_image); tracing::debug!( " Service '{}' mount config (total {} mounts):", service_key, service_config.mounts.len() ); for (i, mount) in service_config.mounts.iter().enumerate() { tracing::debug!( " [{}]: {} -> {} ({})", i, mount.container_path, mount.host_path, mount.mount_type ); } } } } Ok(config) } /// 从配置文件中仅加载 API Key 配置(用于热更新) /// /// 此函数由 config_watcher 模块调用,用于配置热重载。 /// 编译器可能误报为未使用,因为是跨模块调用。 #[allow(dead_code)] pub fn load_api_key_config_from_file( config_path: &std::path::Path, ) -> anyhow::Result { let config_content = fs::read_to_string(config_path).map_err(|e| anyhow::anyhow!("Failed to read config file: {}", e))?; let config: AppConfig = serde_yaml::from_str(&config_content) .map_err(|e| anyhow::anyhow!("Failed to parse config file: {}", e))?; Ok(config.api_key_auth) } /// 创建默认配置文件 fn create_default_config_file(_config: &AppConfig) -> anyhow::Result<()> { // 检查配置文件是否已存在 if std::path::Path::new(CONFIG_FILE).exists() { return Ok(()); } // 创建配置文件目录(如果不存在) if let Some(parent) = std::path::Path::new(CONFIG_FILE).parent() { std::fs::create_dir_all(parent).map_err(|e| anyhow::anyhow!("Failed to create config directory: {}", e))?; } // 使用嵌入式配置文件 let default_config = include_str!("rcoder_default.yml"); // 🆕 生成随机 API Key 并替换模板占位符 let generated_api_key = generate_random_api_key(); let config_content = default_config.replace("{{GENERATED_API_KEY}}", &generated_api_key); fs::write(CONFIG_FILE, config_content) .map_err(|e| anyhow::anyhow!("Failed to write default config file: {}", e))?; info!("Created default config file: {}", CONFIG_FILE); info!("🔑 Loaded API Key (not set)"); Ok(()) }