From 4b44602d391a45599019f7ef8326d09491e36ed7 Mon Sep 17 00:00:00 2001 From: Rodrigo Rodriguez Date: Sat, 14 Feb 2026 09:54:14 +0000 Subject: [PATCH] Update bootstrap and core modules --- src/core/bootstrap/bootstrap_manager.rs | 36 +++++++++++++ src/core/bot/mod.rs | 69 ++++++++++++++++++++----- src/core/secrets/mod.rs | 67 +++++++++++++++++++++++- src/core/shared/utils.rs | 8 ++- src/main_module/bootstrap.rs | 3 ++ 5 files changed, 163 insertions(+), 20 deletions(-) diff --git a/src/core/bootstrap/bootstrap_manager.rs b/src/core/bootstrap/bootstrap_manager.rs index 0440b60c8..4c3ed9a21 100644 --- a/src/core/bootstrap/bootstrap_manager.rs +++ b/src/core/bootstrap/bootstrap_manager.rs @@ -153,6 +153,42 @@ impl BootstrapManager { info!("Starting bootstrap process..."); // Kill any existing processes self.kill_stack_processes().await?; + + // Install all required components + self.install_all().await?; + + Ok(()) + } + + /// Install all required components + pub async fn install_all(&mut self) -> anyhow::Result<()> { + let pm = PackageManager::new(self.install_mode.clone(), self.tenant.clone())?; + + // Install vault first (required for secrets management) + if !pm.is_installed("vault") { + info!("Installing Vault..."); + match pm.install("vault").await { + Ok(Some(_)) => info!("Vault installed successfully"), + Ok(None) => warn!("Vault installation returned no result"), + Err(e) => warn!("Failed to install Vault: {}", e), + } + } else { + info!("Vault already installed"); + } + + // Install other core components (names must match 3rdparty.toml) + let core_components = ["tables", "cache", "drive", "llm"]; + for component in core_components { + if !pm.is_installed(component) { + info!("Installing {}...", component); + match pm.install(component).await { + Ok(Some(_)) => info!("{} installed successfully", component), + Ok(None) => warn!("{} installation returned no result", component), + Err(e) => warn!("Failed to install {}: {}", component, e), + } + } + } + Ok(()) } diff --git a/src/core/bot/mod.rs b/src/core/bot/mod.rs index d1e1a09c6..4a18a8142 100644 --- a/src/core/bot/mod.rs +++ b/src/core/bot/mod.rs @@ -114,6 +114,11 @@ pub struct BotConfigQuery { #[derive(Debug, Serialize)] pub struct BotConfigResponse { pub public: bool, + pub theme_color1: Option, + pub theme_color2: Option, + pub theme_title: Option, + pub theme_logo: Option, + pub theme_logo_text: Option, } /// Get bot configuration endpoint @@ -132,40 +137,78 @@ pub async fn get_bot_config( } }; - // Query bot_configuration table for this bot's public setting + // Query bot_configuration table for this bot's configuration use crate::core::shared::models::schema::bot_configuration::dsl::*; let mut is_public = false; + let mut theme_color1: Option = None; + let mut theme_color2: Option = None; + let mut theme_title: Option = None; + let mut theme_logo: Option = None; + let mut theme_logo_text: Option = None; + // Query all config values (no prefix filter - will match in code) match bot_configuration .select((config_key, config_value)) - .filter(config_key.like(format!("{}%", bot_name))) - .filter(config_key.like("%public%")) .load::<(String, String)>(&mut conn) { Ok(configs) => { + info!("Config query returned {} entries for bot '{}'", configs.len(), bot_name); for (key, value) in configs { - let key: String = key; - let value: String = value; - // Check if this is the public setting + // Try to strip bot_name prefix, use original if no prefix let clean_key = key.strip_prefix(&format!("{}.", bot_name)) .or_else(|| key.strip_prefix(&format!("{}_", bot_name))) .unwrap_or(&key); - if clean_key.eq_ignore_ascii_case("public") { - is_public = value.eq_ignore_ascii_case("true") || value == "1"; - break; + // Check if key is for this bot (either prefixed or not) + let key_for_bot = clean_key == key || key.starts_with(&format!("{}.", bot_name)) || key.starts_with(&format!("{}_", bot_name)); + + info!("Key '{}' -> clean_key '{}' -> key_for_bot: {}", key, clean_key, key_for_bot); + + if !key_for_bot { + info!("Skipping key '{}' - not for bot '{}'", key, bot_name); + continue; + } + + match clean_key.to_lowercase().as_str() { + "public" => { + is_public = value.eq_ignore_ascii_case("true") || value == "1"; + } + "theme-color1" => { + theme_color1 = Some(value); + } + "theme-color2" => { + theme_color2 = Some(value); + } + "theme-title" => { + theme_title = Some(value); + } + "theme-logo" => { + theme_logo = Some(value); + } + "theme-logo-text" => { + theme_logo_text = Some(value); + } + _ => {} } } - info!("Retrieved public status for bot '{}': {}", bot_name, is_public); + info!("Retrieved config for bot '{}': public={}, theme_color1={:?}, theme_color2={:?}, theme_title={:?}", + bot_name, is_public, theme_color1, theme_color2, theme_title); } Err(e) => { - warn!("Failed to load public status for bot '{}': {}", bot_name, e); - // Return default (not public) + warn!("Failed to load config for bot '{}': {}", bot_name, e); + // Return defaults (not public, no theme) } } - let config_response = BotConfigResponse { public: is_public }; + let config_response = BotConfigResponse { + public: is_public, + theme_color1, + theme_color2, + theme_title, + theme_logo, + theme_logo_text, + }; Ok(Json(config_response)) } diff --git a/src/core/secrets/mod.rs b/src/core/secrets/mod.rs index 94380caa2..a185a1b11 100644 --- a/src/core/secrets/mod.rs +++ b/src/core/secrets/mod.rs @@ -326,8 +326,71 @@ impl SecretsManager { self.cache.write().await.remove(path); } - fn get_from_env(_path: &str) -> Result> { - Err(anyhow!("Vault not configured. All secrets must be stored in Vault. Set VAULT_ADDR and VAULT_TOKEN in .env")) + fn get_from_env(path: &str) -> Result> { + let mut secrets = HashMap::new(); + + match path { + SecretPaths::TABLES => { + secrets.insert("host".into(), "localhost".into()); + secrets.insert("port".into(), "5432".into()); + secrets.insert("database".into(), "botserver".into()); + secrets.insert("username".into(), "gbuser".into()); + secrets.insert("password".into(), "changeme".into()); + } + SecretPaths::DIRECTORY => { + secrets.insert("url".into(), "http://localhost:8300".into()); + secrets.insert("project_id".into(), String::new()); + secrets.insert("client_id".into(), String::new()); + secrets.insert("client_secret".into(), String::new()); + } + SecretPaths::DRIVE => { + secrets.insert("accesskey".into(), String::new()); + secrets.insert("secret".into(), String::new()); + } + SecretPaths::CACHE => { + secrets.insert("password".into(), String::new()); + } + SecretPaths::EMAIL => { + secrets.insert("smtp_host".into(), String::new()); + secrets.insert("smtp_port".into(), "587".into()); + secrets.insert("username".into(), String::new()); + secrets.insert("password".into(), String::new()); + secrets.insert("from_address".into(), String::new()); + } + SecretPaths::LLM => { + secrets.insert("openai_key".into(), String::new()); + secrets.insert("anthropic_key".into(), String::new()); + secrets.insert("ollama_url".into(), "http://localhost:11434".into()); + } + SecretPaths::ENCRYPTION => { + secrets.insert("master_key".into(), String::new()); + } + SecretPaths::MEET => { + secrets.insert("jitsi_url".into(), "https://meet.jit.si".into()); + secrets.insert("app_id".into(), String::new()); + secrets.insert("app_secret".into(), String::new()); + } + SecretPaths::VECTORDB => { + secrets.insert("url".into(), "http://localhost:6333".into()); + secrets.insert("api_key".into(), String::new()); + } + SecretPaths::OBSERVABILITY => { + secrets.insert("url".into(), "http://localhost:8086".into()); + secrets.insert("org".into(), "system".into()); + secrets.insert("bucket".into(), "metrics".into()); + secrets.insert("token".into(), String::new()); + } + SecretPaths::ALM => { + secrets.insert("url".into(), "http://localhost:8080".into()); + secrets.insert("username".into(), String::new()); + secrets.insert("password".into(), String::new()); + } + _ => { + log::debug!("No default values for secret path: {}", path); + } + } + + Ok(secrets) } } diff --git a/src/core/shared/utils.rs b/src/core/shared/utils.rs index 1d9a430cf..5af883372 100644 --- a/src/core/shared/utils.rs +++ b/src/core/shared/utils.rs @@ -42,13 +42,11 @@ pub async fn init_secrets_manager() -> Result<()> { pub async fn get_database_url() -> Result { let guard = SECRETS_MANAGER.read().await; if let Some(ref manager) = *guard { - if manager.is_enabled() { - return manager.get_database_url().await; - } + return manager.get_database_url().await; } Err(anyhow::anyhow!( - "Vault not configured. Set VAULT_ADDR and VAULT_TOKEN in .env" + "Secrets manager not initialized" )) } @@ -68,7 +66,7 @@ pub fn get_database_url_sync() -> Result { } Err(anyhow::anyhow!( - "Vault not configured. Set VAULT_ADDR and VAULT_TOKEN in .env" + "Secrets manager not initialized" )) } diff --git a/src/main_module/bootstrap.rs b/src/main_module/bootstrap.rs index 33af84dbb..561bb7d9f 100644 --- a/src/main_module/bootstrap.rs +++ b/src/main_module/bootstrap.rs @@ -219,6 +219,9 @@ pub async fn init_database( trace!("Creating database pool again..."); progress_tx.send(BootstrapProgress::ConnectingDatabase).ok(); + // Ensure secrets manager is initialized before creating database connection + crate::core::shared::utils::init_secrets_manager().await; + let pool = match create_conn() { Ok(pool) => { trace!("Running database migrations...");