Files
qiming/qiming-rcoder/crates/rcoder/src/config.rs
2026-06-01 13:54:52 +08:00

679 lines
22 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<u16>,
/// 项目工作目录
#[arg(short = 'd', long, default_value = "./project_workspace")]
pub projects_dir: Option<String>,
/// 启用反向代理
#[arg(short, long)]
pub enable_proxy: bool,
/// 代理服务端口
#[arg(long = "proxy-port")]
pub proxy_port: Option<u16>,
/// 默认后端服务端口
#[arg(long = "backend-port")]
pub default_backend_port: Option<u16>,
}
// 从 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<ProxyConfig>,
/// Docker 配置
pub docker_config: Option<DockerConfig>,
/// 容器清理配置
#[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<shared_types::MultiImageConfig>,
/// 网络模式
pub network_mode: Option<String>,
/// 工作目录
pub work_dir: Option<String>,
/// 自动清理
pub auto_cleanup: Option<bool>,
/// 容器存活时间(秒)
pub container_ttl_seconds: Option<u64>,
/// 网络基础名称(不含 project name 前缀)
/// Docker Compose 会自动添加 project name 前缀,实际网络名称为 {project_name}_{network_base_name}
/// 例如: network_base_name="agent-network" 时,实际网络为 "rcoder_agent-network"
pub network_base_name: Option<String>,
/// 🔧 Docker API 调用超时时间(秒)
pub api_timeout_seconds: Option<u64>,
/// 🔧 快速操作超时时间(秒)
pub api_timeout_quick_seconds: Option<u64>,
/// 🔧 状态缓存 TTL
pub cache_status_ttl_seconds: Option<u64>,
/// 🔧 网络缓存 TTL
pub cache_network_ttl_seconds: Option<u64>,
/// 🔧 缓存最大容量
pub cache_max_capacity: Option<u64>,
}
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<AppConfig> {
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::<u16>() {
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::<bool>() {
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<AppConfig> {
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<AppConfig> {
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<ApiKeyAuthConfig> {
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(())
}