pub mod cache; pub mod component; pub mod installer; pub mod os; pub mod setup; pub mod alm_setup; pub use cache::{CacheResult, DownloadCache}; pub use installer::PackageManager; pub mod cli; pub mod facade; use serde::{Serialize, Deserialize}; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum InstallMode { Local, Container, } #[derive(Debug, Clone, PartialEq, Eq)] pub enum OsType { Linux, MacOS, Windows, } #[derive(Debug)] pub struct ComponentInfo { pub name: &'static str, pub termination_command: &'static str, } pub fn get_all_components() -> Vec { vec![ ComponentInfo { name: "tables", termination_command: "postgres", }, ComponentInfo { name: "cache", termination_command: "redis-server", }, ComponentInfo { name: "drive", termination_command: "minio", }, ComponentInfo { name: "llm", termination_command: "llama-server", }, ] } pub use alm_setup::setup_alm; /// Initialize Directory (Zitadel) with default admin user and OAuth application /// This should be called after Zitadel has started and is responding #[cfg(feature = "directory")] pub async fn setup_directory() -> anyhow::Result { use std::path::PathBuf; use std::collections::HashMap; let stack_path = std::env::var("BOTSERVER_STACK_PATH") .unwrap_or_else(|_| "./botserver-stack".to_string()); let base_url = "http://localhost:8300".to_string(); let config_path = PathBuf::from(&stack_path).join("conf/system/directory_config.json"); // Check if config already exists in Vault first if let Ok(secrets_manager) = crate::core::secrets::SecretsManager::from_env() { if secrets_manager.is_enabled() { if let Ok(secrets) = secrets_manager.get_secret(crate::core::secrets::SecretPaths::DIRECTORY).await { if let (Some(client_id), Some(client_secret)) = (secrets.get("client_id"), secrets.get("client_secret")) { // Validate that credentials are real, not placeholders let is_valid = !client_id.is_empty() && !client_secret.is_empty() && client_secret != "..." && client_id.contains('@') // OAuth client IDs contain @ && client_secret.len() > 10; // Real secrets are longer than placeholders if is_valid { log::info!("Directory already configured with OAuth client in Vault"); // Reconstruct config from Vault let config = crate::core::package_manager::setup::DirectoryConfig { base_url: base_url.clone(), issuer_url: secrets.get("issuer_url").cloned().unwrap_or_else(|| base_url.clone()), issuer: secrets.get("issuer").cloned().unwrap_or_else(|| base_url.clone()), client_id: client_id.clone(), client_secret: client_secret.clone(), redirect_uri: secrets.get("redirect_uri").cloned().unwrap_or_else(|| "http://localhost:3000/auth/callback".to_string()), project_id: secrets.get("project_id").cloned().unwrap_or_default(), api_url: secrets.get("api_url").cloned().unwrap_or_else(|| base_url.clone()), service_account_key: secrets.get("service_account_key").cloned(), }; return Ok(config); } } } } } // Check if config already exists with valid OAuth client in file if config_path.exists() { if let Ok(content) = std::fs::read_to_string(&config_path) { if let Ok(config) = serde_json::from_str::(&content) { // Validate that credentials are real, not placeholders let is_valid = !config.client_id.is_empty() && !config.client_secret.is_empty() && config.client_secret != "..." && config.client_id.contains('@') && config.client_secret.len() > 10; if is_valid { log::info!("Directory already configured with OAuth client"); return Ok(config); } } } } // Initialize directory with default credentials let mut directory_setup = crate::core::package_manager::setup::DirectorySetup::new(base_url.clone(), config_path.clone()); let config = directory_setup.initialize().await .map_err(|e| anyhow::anyhow!("Failed to initialize directory: {}", e))?; // Store credentials in Vault if let Ok(secrets_manager) = crate::core::secrets::SecretsManager::from_env() { if secrets_manager.is_enabled() { let mut secrets = HashMap::new(); secrets.insert("url".to_string(), config.base_url.clone()); secrets.insert("issuer_url".to_string(), config.issuer_url.clone()); secrets.insert("issuer".to_string(), config.issuer.clone()); secrets.insert("client_id".to_string(), config.client_id.clone()); secrets.insert("client_secret".to_string(), config.client_secret.clone()); secrets.insert("redirect_uri".to_string(), config.redirect_uri.clone()); secrets.insert("project_id".to_string(), config.project_id.clone()); secrets.insert("api_url".to_string(), config.api_url.clone()); if let Some(key) = &config.service_account_key { secrets.insert("service_account_key".to_string(), key.clone()); } match secrets_manager.put_secret(crate::core::secrets::SecretPaths::DIRECTORY, secrets).await { Ok(_) => log::info!("Directory credentials stored in Vault"), Err(e) => log::warn!("Failed to store directory credentials in Vault: {}", e), } } } Ok(config) }