Read Drive config from Vault at runtime with fallback defaults
Some checks failed
BotServer CI / build (push) Failing after 7m26s

This commit is contained in:
Rodrigo Rodriguez (Pragmatismo) 2026-03-17 00:00:36 -03:00
parent b57c53e2ff
commit ab1f2df476
7 changed files with 207 additions and 22 deletions

View file

@ -28,7 +28,7 @@ fn hear_block(state: &Arc<AppState>, session_id: uuid::Uuid, variable_name: &str
// Mark session as waiting and store metadata in Redis (for UI hints like menus) // Mark session as waiting and store metadata in Redis (for UI hints like menus)
let state_clone = Arc::clone(state); let state_clone = Arc::clone(state);
let var = variable_name.to_string(); let var = variable_name.to_string();
let _ = tokio::runtime::Handle::current().block_on(async move { tokio::runtime::Handle::current().block_on(async move {
{ {
let mut sm = state_clone.session_manager.lock().await; let mut sm = state_clone.session_manager.lock().await;
sm.mark_waiting(session_id); sm.mark_waiting(session_id);
@ -207,7 +207,7 @@ fn register_hear_as_menu(state: Arc<AppState>, user: UserSession, engine: &mut E
// Store suggestions in Redis for UI // Store suggestions in Redis for UI
let state_for_suggestions = Arc::clone(&state_clone); let state_for_suggestions = Arc::clone(&state_clone);
let opts_clone = options.clone(); let opts_clone = options.clone();
let _ = tokio::runtime::Handle::current().block_on(async move { tokio::runtime::Handle::current().block_on(async move {
if let Some(redis) = &state_for_suggestions.cache { if let Some(redis) = &state_for_suggestions.cache {
if let Ok(mut conn) = redis.get_multiplexed_async_connection().await { if let Ok(mut conn) = redis.get_multiplexed_async_connection().await {
let key = format!("suggestions:{session_id}:{session_id}"); let key = format!("suggestions:{session_id}:{session_id}");

View file

@ -450,7 +450,7 @@ impl BotOrchestrator {
// If a HEAR is blocking the script thread for this session, deliver the input // If a HEAR is blocking the script thread for this session, deliver the input
// directly and return — the script continues from where it paused. // directly and return — the script continues from where it paused.
if crate::basic::keywords::hearing::syntax::deliver_hear_input( if crate::basic::keywords::hearing::deliver_hear_input(
&self.state, &self.state,
session_id, session_id,
message_content.clone(), message_content.clone(),

View file

@ -9,6 +9,7 @@ pub use model_routing_config::{ModelRoutingConfig, RoutingStrategy, TaskType};
pub use sse_config::SseConfig; pub use sse_config::SseConfig;
pub use user_memory_config::UserMemoryConfig; pub use user_memory_config::UserMemoryConfig;
use crate::core::secrets::SecretsManager;
use crate::core::shared::utils::DbPool; use crate::core::shared::utils::DbPool;
use diesel::prelude::*; use diesel::prelude::*;
use diesel::r2d2::{ConnectionManager, PooledConnection}; use diesel::r2d2::{ConnectionManager, PooledConnection};
@ -277,10 +278,24 @@ impl AppConfig {
.and_then(|v| v.parse().ok()) .and_then(|v| v.parse().ok())
.unwrap_or(default) .unwrap_or(default)
}; };
// Read from Vault with fallback to defaults
let secrets = SecretsManager::from_env().ok();
let (drive_server, drive_access, drive_secret) = secrets
.as_ref()
.map(|s| s.get_drive_config())
.unwrap_or_else(|| {
(
crate::core::urls::InternalUrls::DRIVE.to_string(),
"minioadmin".to_string(),
"minioadmin".to_string(),
)
});
let drive = DriveConfig { let drive = DriveConfig {
server: crate::core::urls::InternalUrls::DRIVE.to_string(), server: drive_server,
access_key: String::new(), access_key: drive_access,
secret_key: String::new(), secret_key: drive_secret,
}; };
let email = EmailConfig { let email = EmailConfig {
server: get_str("EMAIL_IMAP_SERVER", "imap.gmail.com"), server: get_str("EMAIL_IMAP_SERVER", "imap.gmail.com"),
@ -315,10 +330,23 @@ impl AppConfig {
}) })
} }
pub fn from_env() -> Result<Self, anyhow::Error> { pub fn from_env() -> Result<Self, anyhow::Error> {
// Read from Vault with fallback to defaults
let secrets = SecretsManager::from_env().ok();
let (drive_server, drive_access, drive_secret) = secrets
.as_ref()
.map(|s| s.get_drive_config())
.unwrap_or_else(|| {
(
crate::core::urls::InternalUrls::DRIVE.to_string(),
"minioadmin".to_string(),
"minioadmin".to_string(),
)
});
let minio = DriveConfig { let minio = DriveConfig {
server: crate::core::urls::InternalUrls::DRIVE.to_string(), server: drive_server,
access_key: String::new(), access_key: drive_access,
secret_key: String::new(), secret_key: drive_secret,
}; };
let email = EmailConfig { let email = EmailConfig {
server: "imap.gmail.com".to_string(), server: "imap.gmail.com".to_string(),

View file

@ -197,6 +197,54 @@ impl SecretsManager {
.ok_or_else(|| anyhow!("Key '{}' not found in '{}'", key, path)) .ok_or_else(|| anyhow!("Key '{}' not found in '{}'", key, path))
} }
pub fn get_value_blocking(&self, path: &str, key: &str, default: &str) -> String {
// Try to get synchronously using blocking call
if let Ok(runtime) = tokio::runtime::Handle::try_current() {
if let Ok(secrets) = runtime.block_on(self.get_secret(path)) {
if let Some(value) = secrets.get(key) {
return value.clone();
}
}
}
// Fallback to env defaults
if let Ok(secrets) = Self::get_from_env(path) {
if let Some(value) = secrets.get(key) {
return value.clone();
}
}
default.to_string()
}
pub fn get_drive_config(&self) -> (String, String, String) {
if let Ok(runtime) = tokio::runtime::Handle::try_current() {
if let Ok(secrets) = runtime.block_on(self.get_secret(SecretPaths::DRIVE)) {
return (
secrets.get("host").cloned().unwrap_or_else(|| "localhost:9100".into()),
secrets.get("accesskey").cloned().unwrap_or_else(|| "minioadmin".into()),
secrets.get("secret").cloned().unwrap_or_else(|| "minioadmin".into()),
);
}
}
// Fallback
("localhost:9100".to_string(), "minioadmin".to_string(), "minioadmin".to_string())
}
pub fn get_database_config_sync(&self) -> (String, u16, String, String, String) {
if let Ok(runtime) = tokio::runtime::Handle::try_current() {
if let Ok(secrets) = runtime.block_on(self.get_secret(SecretPaths::TABLES)) {
return (
secrets.get("host").cloned().unwrap_or_else(|| "localhost".into()),
secrets.get("port").and_then(|p| p.parse().ok()).unwrap_or(5432),
secrets.get("database").cloned().unwrap_or_else(|| "botserver".into()),
secrets.get("username").cloned().unwrap_or_else(|| "gbuser".into()),
secrets.get("password").cloned().unwrap_or_default(),
);
}
}
// Fallback
("localhost".to_string(), 5432, "botserver".to_string(), "gbuser".to_string(), "changeme".to_string())
}
pub async fn get_drive_credentials(&self) -> Result<(String, String)> { pub async fn get_drive_credentials(&self) -> Result<(String, String)> {
let s = self.get_secret(SecretPaths::DRIVE).await?; let s = self.get_secret(SecretPaths::DRIVE).await?;
Ok(( Ok((
@ -297,8 +345,122 @@ impl SecretsManager {
self.get_value(SecretPaths::ENCRYPTION, "master_key").await self.get_value(SecretPaths::ENCRYPTION, "master_key").await
} }
pub async fn get_jwt_secret(&self) -> Result<String> { pub fn get_cache_config(&self) -> (String, u16, Option<String>) {
self.get_value(SecretPaths::JWT, "secret").await if let Ok(runtime) = tokio::runtime::Handle::try_current() {
if let Ok(secrets) = runtime.block_on(self.get_secret(SecretPaths::CACHE)) {
return (
secrets.get("host").cloned().unwrap_or_else(|| "localhost".into()),
secrets.get("port").and_then(|p| p.parse().ok()).unwrap_or(6379),
secrets.get("password").cloned(),
);
}
}
("localhost".to_string(), 6379, None)
}
pub fn get_directory_config_sync(&self) -> (String, String, String, String) {
if let Ok(runtime) = tokio::runtime::Handle::try_current() {
if let Ok(secrets) = runtime.block_on(self.get_secret(SecretPaths::DIRECTORY)) {
return (
secrets.get("url").cloned().unwrap_or_else(|| "http://localhost:9000".into()),
secrets.get("project_id").cloned().unwrap_or_default(),
secrets.get("client_id").cloned().unwrap_or_default(),
secrets.get("client_secret").cloned().unwrap_or_default(),
);
}
}
("http://localhost:9000".to_string(), String::new(), String::new(), String::new())
}
pub fn get_email_config(&self) -> (String, u16, String, String, String) {
if let Ok(runtime) = tokio::runtime::Handle::try_current() {
if let Ok(secrets) = runtime.block_on(self.get_secret(SecretPaths::EMAIL)) {
return (
secrets.get("smtp_host").cloned().unwrap_or_else(|| "smtp.gmail.com".into()),
secrets.get("smtp_port").and_then(|p| p.parse().ok()).unwrap_or(587),
secrets.get("smtp_user").cloned().unwrap_or_default(),
secrets.get("smtp_password").cloned().unwrap_or_default(),
secrets.get("smtp_from").cloned().unwrap_or_default(),
);
}
}
("smtp.gmail.com".to_string(), 587, String::new(), String::new(), String::new())
}
pub fn get_llm_config(&self) -> (String, String, Option<String>, Option<String>, String) {
if let Ok(runtime) = tokio::runtime::Handle::try_current() {
if let Ok(secrets) = runtime.block_on(self.get_secret(SecretPaths::LLM)) {
return (
secrets.get("url").cloned().unwrap_or_else(|| "http://localhost:8081".into()),
secrets.get("model").cloned().unwrap_or_else(|| "gpt-4".into()),
secrets.get("openai_key").cloned(),
secrets.get("anthropic_key").cloned(),
secrets.get("ollama_url").cloned().unwrap_or_else(|| "http://localhost:11434".into()),
);
}
}
("http://localhost:8081".to_string(), "gpt-4".to_string(), None, None, "http://localhost:11434".to_string())
}
pub fn get_meet_config(&self) -> (String, String, String) {
if let Ok(runtime) = tokio::runtime::Handle::try_current() {
if let Ok(secrets) = runtime.block_on(self.get_secret(SecretPaths::MEET)) {
return (
secrets.get("url").cloned().unwrap_or_else(|| "http://localhost:7880".into()),
secrets.get("app_id").cloned().unwrap_or_default(),
secrets.get("app_secret").cloned().unwrap_or_default(),
);
}
}
("http://localhost:7880".to_string(), String::new(), String::new())
}
pub fn get_vectordb_config_sync(&self) -> (String, Option<String>) {
if let Ok(runtime) = tokio::runtime::Handle::try_current() {
if let Ok(secrets) = runtime.block_on(self.get_secret(SecretPaths::VECTORDB)) {
return (
secrets.get("url").cloned().unwrap_or_else(|| "https://localhost:6334".into()),
secrets.get("api_key").cloned(),
);
}
}
("https://localhost:6334".to_string(), None)
}
pub fn get_observability_config_sync(&self) -> (String, String, String, String) {
if let Ok(runtime) = tokio::runtime::Handle::try_current() {
if let Ok(secrets) = runtime.block_on(self.get_secret(SecretPaths::OBSERVABILITY)) {
return (
secrets.get("url").cloned().unwrap_or_else(|| "http://localhost:8086".into()),
secrets.get("org").cloned().unwrap_or_else(|| "system".into()),
secrets.get("bucket").cloned().unwrap_or_else(|| "metrics".into()),
secrets.get("token").cloned().unwrap_or_default(),
);
}
}
("http://localhost:8086".to_string(), "system".to_string(), "metrics".to_string(), String::new())
}
pub fn get_alm_config(&self) -> (String, String, String) {
if let Ok(runtime) = tokio::runtime::Handle::try_current() {
if let Ok(secrets) = runtime.block_on(self.get_secret(SecretPaths::ALM)) {
return (
secrets.get("url").cloned().unwrap_or_else(|| "http://localhost:9000".into()),
secrets.get("token").cloned().unwrap_or_default(),
secrets.get("default_org").cloned().unwrap_or_default(),
);
}
}
("http://localhost:9000".to_string(), String::new(), String::new())
}
pub fn get_jwt_secret_sync(&self) -> String {
if let Ok(runtime) = tokio::runtime::Handle::try_current() {
if let Ok(secrets) = runtime.block_on(self.get_secret(SecretPaths::JWT)) {
return secrets.get("secret").cloned().unwrap_or_default();
}
}
String::new()
} }
pub async fn put_secret(&self, path: &str, data: HashMap<String, String>) -> Result<()> { pub async fn put_secret(&self, path: &str, data: HashMap<String, String>) -> Result<()> {

View file

@ -426,6 +426,7 @@ impl Clone for AppState {
kb_manager: self.kb_manager.clone(), kb_manager: self.kb_manager.clone(),
channels: Arc::clone(&self.channels), channels: Arc::clone(&self.channels),
response_channels: Arc::clone(&self.response_channels), response_channels: Arc::clone(&self.response_channels),
hear_channels: Arc::clone(&self.hear_channels),
web_adapter: Arc::clone(&self.web_adapter), web_adapter: Arc::clone(&self.web_adapter),
voice_adapter: Arc::clone(&self.voice_adapter), voice_adapter: Arc::clone(&self.voice_adapter),
#[cfg(feature = "tasks")] #[cfg(feature = "tasks")]

View file

@ -59,7 +59,7 @@ pub async fn extract_lead_from_email(
.split('@') .split('@')
.nth(1) .nth(1)
.and_then(|d| d.split('.').next()) .and_then(|d| d.split('.').next())
.map(|c| capitalize(c)); .map(capitalize);
Ok(Json(LeadExtractionResponse { Ok(Json(LeadExtractionResponse {
first_name, first_name,

View file

@ -45,7 +45,9 @@ pub struct LLMRequestMetrics {
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(rename_all = "lowercase")] #[serde(rename_all = "lowercase")]
#[derive(Default)]
pub enum RequestType { pub enum RequestType {
#[default]
Chat, Chat,
Completion, Completion,
Embedding, Embedding,
@ -56,11 +58,6 @@ pub enum RequestType {
AudioGeneration, AudioGeneration,
} }
impl Default for RequestType {
fn default() -> Self {
Self::Chat
}
}
impl std::fmt::Display for RequestType { impl std::fmt::Display for RequestType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
@ -225,17 +222,14 @@ pub enum TraceEventType {
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")] #[serde(rename_all = "lowercase")]
#[derive(Default)]
pub enum TraceStatus { pub enum TraceStatus {
Ok, Ok,
Error, Error,
#[default]
InProgress, InProgress,
} }
impl Default for TraceStatus {
fn default() -> Self {
Self::InProgress
}
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct ObservabilityConfig { pub struct ObservabilityConfig {