Update bootstrap and core modules
All checks were successful
BotServer CI / build (push) Successful in 10m27s

This commit is contained in:
Rodrigo Rodriguez 2026-02-14 09:54:14 +00:00
parent 17cb4ef147
commit 4b44602d39
5 changed files with 163 additions and 20 deletions

View file

@ -153,6 +153,42 @@ impl BootstrapManager {
info!("Starting bootstrap process..."); info!("Starting bootstrap process...");
// Kill any existing processes // Kill any existing processes
self.kill_stack_processes().await?; 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(()) Ok(())
} }

View file

@ -114,6 +114,11 @@ pub struct BotConfigQuery {
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
pub struct BotConfigResponse { pub struct BotConfigResponse {
pub public: bool, pub public: bool,
pub theme_color1: Option<String>,
pub theme_color2: Option<String>,
pub theme_title: Option<String>,
pub theme_logo: Option<String>,
pub theme_logo_text: Option<String>,
} }
/// Get bot configuration endpoint /// 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::*; use crate::core::shared::models::schema::bot_configuration::dsl::*;
let mut is_public = false; let mut is_public = false;
let mut theme_color1: Option<String> = None;
let mut theme_color2: Option<String> = None;
let mut theme_title: Option<String> = None;
let mut theme_logo: Option<String> = None;
let mut theme_logo_text: Option<String> = None;
// Query all config values (no prefix filter - will match in code)
match bot_configuration match bot_configuration
.select((config_key, config_value)) .select((config_key, config_value))
.filter(config_key.like(format!("{}%", bot_name)))
.filter(config_key.like("%public%"))
.load::<(String, String)>(&mut conn) .load::<(String, String)>(&mut conn)
{ {
Ok(configs) => { Ok(configs) => {
info!("Config query returned {} entries for bot '{}'", configs.len(), bot_name);
for (key, value) in configs { for (key, value) in configs {
let key: String = key; // Try to strip bot_name prefix, use original if no prefix
let value: String = value;
// Check if this is the public setting
let clean_key = key.strip_prefix(&format!("{}.", bot_name)) let clean_key = key.strip_prefix(&format!("{}.", bot_name))
.or_else(|| key.strip_prefix(&format!("{}_", bot_name))) .or_else(|| key.strip_prefix(&format!("{}_", bot_name)))
.unwrap_or(&key); .unwrap_or(&key);
if clean_key.eq_ignore_ascii_case("public") { // Check if key is for this bot (either prefixed or not)
is_public = value.eq_ignore_ascii_case("true") || value == "1"; let key_for_bot = clean_key == key || key.starts_with(&format!("{}.", bot_name)) || key.starts_with(&format!("{}_", bot_name));
break;
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) => { Err(e) => {
warn!("Failed to load public status for bot '{}': {}", bot_name, e); warn!("Failed to load config for bot '{}': {}", bot_name, e);
// Return default (not public) // 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)) Ok(Json(config_response))
} }

View file

@ -326,8 +326,71 @@ impl SecretsManager {
self.cache.write().await.remove(path); self.cache.write().await.remove(path);
} }
fn get_from_env(_path: &str) -> Result<HashMap<String, String>> { fn get_from_env(path: &str) -> Result<HashMap<String, String>> {
Err(anyhow!("Vault not configured. All secrets must be stored in Vault. Set VAULT_ADDR and VAULT_TOKEN in .env")) 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)
} }
} }

View file

@ -42,13 +42,11 @@ pub async fn init_secrets_manager() -> Result<()> {
pub async fn get_database_url() -> Result<String> { pub async fn get_database_url() -> Result<String> {
let guard = SECRETS_MANAGER.read().await; let guard = SECRETS_MANAGER.read().await;
if let Some(ref manager) = *guard { 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!( 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<String> {
} }
Err(anyhow::anyhow!( Err(anyhow::anyhow!(
"Vault not configured. Set VAULT_ADDR and VAULT_TOKEN in .env" "Secrets manager not initialized"
)) ))
} }

View file

@ -219,6 +219,9 @@ pub async fn init_database(
trace!("Creating database pool again..."); trace!("Creating database pool again...");
progress_tx.send(BootstrapProgress::ConnectingDatabase).ok(); 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() { let pool = match create_conn() {
Ok(pool) => { Ok(pool) => {
trace!("Running database migrations..."); trace!("Running database migrations...");