679 lines
22 KiB
Rust
679 lines
22 KiB
Rust
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(())
|
||
}
|