Update botserver: Multiple improvements across core modules
All checks were successful
BotServer CI/CD / build (push) Successful in 10m41s

This commit is contained in:
Rodrigo Rodriguez (Pragmatismo) 2026-04-11 07:33:32 -03:00
parent 4cc1e3aa4b
commit 5576378b3f
44 changed files with 320 additions and 334 deletions

View file

@ -78,15 +78,12 @@ jobs:
ssh $SSH_ARGS system "find /opt/gbo/data -maxdepth 2 ! -path '*/botserver' ! -path '*/gb-ws' -print0 2>/dev/null | xargs -0 rm -rf || true"
# Clean old log files
ssh $SSH_ARGS system "find /tmp -name '*.log' -type f -mtime +7 -print0 2>/dev/null | xargs -0 rm -f || true"
# Show disk space
ssh $SSH_ARGS system "df -h /opt/gbo/data"
echo "=== Workspace cleanup complete ==="
- name: Build BotServer
working-directory: /opt/gbo/data/botserver
run: |
sccache --start-server 2>/dev/null || true
cargo build -p botserver --features chat -j 8 2>&1 | tee /tmp/build.log
cargo build -p botserver -j 8 2>&1 | tee /tmp/build.log
sccache --show-stats
ls -lh target/debug/botserver

View file

@ -17,7 +17,7 @@ use crate::core::shared::schema::{okr_checkins, okr_key_results, okr_objectives,
use crate::core::shared::state::AppState;
fn get_bot_context() -> (Uuid, Uuid) {
let sm = crate::core::secrets::SecretsManager::from_env().ok();
let sm = crate::core::secrets::SecretsManager::get().ok().map(|sm| sm.clone());
let (org_id, bot_id) = if let Some(sm) = sm {
let sm_owned = sm.clone();
let (tx, rx) = std::sync::mpsc::channel();

View file

@ -56,7 +56,7 @@ impl AgentExecutor {
}
});
self.broadcast_browser_ready("http://localhost:8000", 8000);
self.broadcast_browser_ready("", 8000);
self.broadcast_step("Agent Ready", 2, 10);
Ok(())

View file

@ -634,19 +634,23 @@ impl ReflectionEngine {
.load(&mut conn)
.unwrap_or_default();
let mut llm_url = "http://localhost:8081".to_string();
let mut llm_model = "default".to_string();
let mut llm_key = "none".to_string();
let mut llm_url: Option<String> = None;
let mut llm_model: Option<String> = None;
let mut llm_key: Option<String> = None;
for config in configs {
match config.config_key.as_str() {
"llm-url" => llm_url = config.config_value,
"llm-model" => llm_model = config.config_value,
"llm-key" => llm_key = config.config_value,
"llm-url" => llm_url = Some(config.config_value),
"llm-model" => llm_model = Some(config.config_value),
"llm-key" => llm_key = Some(config.config_value),
_ => {}
}
}
let llm_url = llm_url.ok_or_else(|| "LLM URL not configured".to_string())?;
let llm_model = llm_model.ok_or_else(|| "LLM model not configured".to_string())?;
let llm_key = llm_key.ok_or_else(|| "LLM key not configured".to_string())?;
Ok((llm_url, llm_model, llm_key))
}

View file

@ -184,9 +184,9 @@ async fn translate_text(
target_lang: &str,
) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
let llm_url = if let Some(sm) = crate::core::shared::utils::get_secrets_manager().await {
sm.get_value("gbo/llm", "url").await.unwrap_or_else(|_| "http://localhost:8081".to_string())
sm.get_value("gbo/llm", "url").await.unwrap_or_else(|_| "".to_string())
} else {
"http://localhost:8081".to_string()
"".to_string()
};
let prompt = format!(
"Translate to {}. Return ONLY the translation:\n\n{}",
@ -212,7 +212,7 @@ async fn translate_text(
async fn perform_ocr(image_path: &str) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
let botmodels_url =
std::env::var("BOTMODELS_URL").unwrap_or_else(|_| "http://localhost:8001".to_string());
std::env::var("BOTMODELS_URL").unwrap_or_else(|_| "".to_string());
let client = reqwest::Client::new();
let image_data = if image_path.starts_with("http") {
client.get(image_path).send().await?.bytes().await?.to_vec()
@ -237,9 +237,9 @@ async fn analyze_sentiment(
text: &str,
) -> Result<Dynamic, Box<dyn std::error::Error + Send + Sync>> {
let llm_url = if let Some(sm) = crate::core::shared::utils::get_secrets_manager().await {
sm.get_value("gbo/llm", "url").await.unwrap_or_else(|_| "http://localhost:8081".to_string())
sm.get_value("gbo/llm", "url").await.unwrap_or_else(|_| "".to_string())
} else {
"http://localhost:8081".to_string()
"".to_string()
};
let prompt = format!(
r#"Analyze sentiment. Return JSON only:
@ -346,9 +346,9 @@ async fn classify_text(
categories: &[String],
) -> Result<Dynamic, Box<dyn std::error::Error + Send + Sync>> {
let llm_url = if let Some(sm) = crate::core::shared::utils::get_secrets_manager().await {
sm.get_value("gbo/llm", "url").await.unwrap_or_else(|_| "http://localhost:8081".to_string())
sm.get_value("gbo/llm", "url").await.unwrap_or_else(|_| "".to_string())
} else {
"http://localhost:8081".to_string()
"".to_string()
};
let cats = categories.join(", ");
let prompt = format!(

View file

@ -109,7 +109,7 @@ async fn detect_anomalies_in_table(
}
let botmodels_host =
std::env::var("BOTMODELS_HOST").unwrap_or_else(|_| "http://localhost:8085".to_string());
std::env::var("BOTMODELS_HOST").unwrap_or_else(|_| "".to_string());
let botmodels_key =
std::env::var("BOTMODELS_API_KEY").unwrap_or_else(|_| "starter".to_string());

View file

@ -138,7 +138,7 @@ pub async fn process_qrcode(
.ok()
});
config_url.unwrap_or_else(|| {
std::env::var("BOTMODELS_URL").unwrap_or_else(|_| "http://localhost:8001".to_string())
std::env::var("BOTMODELS_URL").unwrap_or_else(|_| "".to_string())
})
};
@ -188,7 +188,7 @@ pub async fn process_audio_to_text(
audio_url: &str,
) -> Result<(String, Option<serde_json::Value>), String> {
let botmodels_url =
std::env::var("BOTMODELS_URL").unwrap_or_else(|_| "http://localhost:8001".to_string());
std::env::var("BOTMODELS_URL").unwrap_or_else(|_| "".to_string());
let client = reqwest::Client::new();
@ -237,7 +237,7 @@ pub async fn process_video_description(
video_url: &str,
) -> Result<(String, Option<serde_json::Value>), String> {
let botmodels_url =
std::env::var("BOTMODELS_URL").unwrap_or_else(|_| "http://localhost:8001".to_string());
std::env::var("BOTMODELS_URL").unwrap_or_else(|_| "".to_string());
let client = reqwest::Client::new();

View file

@ -120,7 +120,7 @@ impl Default for McpConnection {
fn default() -> Self {
Self {
connection_type: ConnectionType::Http,
url: "http://localhost:8080".to_string(),
url: "".to_string(),
port: None,
timeout_seconds: 30,
max_retries: 3,

View file

@ -76,7 +76,7 @@ pub async fn export_test(
let script = r#"
import { test, expect } from '@playwright/test';
test('Recorded test', async ({ page }) => {
await page.goto('http://localhost:3000');
await page.goto('');
// Add actions
});
"#;

View file

@ -23,7 +23,7 @@ pub mod caldav;
pub mod ui;
fn get_bot_context() -> (Uuid, Uuid) {
let sm = crate::core::secrets::SecretsManager::from_env().ok();
let sm = crate::core::secrets::SecretsManager::get().ok().map(|sm| sm.clone());
let (org_id, bot_id) = if let Some(sm) = sm {
let sm_owned = sm.clone();
let (tx, rx) = std::sync::mpsc::channel();

View file

@ -894,7 +894,7 @@ pub fn apply_wizard_config(config: &WizardConfig) -> io::Result<()> {
println!(" Data directory: {}", config.data_dir.display());
println!("\n Next steps:");
println!(" 1. Run: botserver start");
println!(" 2. Open: http://localhost:4242");
println!(" 2. Open: ");
println!(" 3. Login with: {}", config.admin.username);
Ok(())

View file

@ -162,10 +162,10 @@ pub fn cache_health_check() -> bool {
/// Check if Qdrant vector database is healthy
pub fn vector_db_health_check() -> bool {
let qdrant_url = if let Ok(sm) = crate::core::secrets::SecretsManager::from_env() {
let qdrant_url = if let Ok(sm) = crate::core::secrets::SecretsManager::get() {
sm.get_vectordb_config_sync().0
} else {
"http://localhost:6333".to_string()
"".to_string()
};
let urls = [
@ -226,7 +226,7 @@ pub fn zitadel_health_check() -> bool {
"2",
"-m",
"3",
"http://localhost:8300/debug/healthz",
"/debug/healthz",
])
})
.and_then(|c| c.execute());
@ -327,7 +327,7 @@ pub fn drive_health_check() -> bool {
/// Check if ALM (Forgejo) is healthy
pub fn alm_health_check() -> bool {
let urls = ["http://localhost:3000", "https://localhost:3000"];
let urls = ["", "https://localhost:3000"];
for url in &urls {
if let Ok(output) = SafeCommand::new("curl")

View file

@ -91,7 +91,7 @@ impl CustomDatabaseConfig {
.first::<String>(&mut conn)
.ok()
.filter(|s| !s.is_empty())
.unwrap_or_else(|| "localhost".to_string());
.unwrap_or_else(|| "".to_string());
let port: u16 = bot_configuration
.filter(bot_id.eq(target_bot_id))
@ -272,27 +272,21 @@ impl AppConfig {
.unwrap_or_else(|| default.to_string())
};
let get_u16 = |key: &str, default: u16| -> u16 {
config_map
.get(key)
.and_then(|v| v.parse().ok())
.unwrap_or(default)
};
let get_u16 = |key: &str, default: u16| -> u16 {
config_map
.get(key)
.and_then(|v| v.parse().ok())
.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(),
)
});
// Read from Vault with fallback to defaults
let secrets = SecretsManager::get().ok().map(|sm| sm.clone());
let (drive_server, drive_access, drive_secret) = secrets
.as_ref()
.and_then(|s| s.get_drive_config().ok())
.unwrap_or((String::new(), String::new(), String::new()));
let drive = DriveConfig {
let drive = DriveConfig {
server: drive_server,
access_key: drive_access,
secret_key: drive_secret,
@ -317,7 +311,7 @@ impl AppConfig {
server: ServerConfig {
host: get_str("server_host", "0.0.0.0"),
port,
base_url: config_map.get("server_base_url").cloned().unwrap_or_else(|| "http://localhost:8080".to_string()),
base_url: config_map.get("server_base_url").cloned().unwrap_or_else(|| String::new()),
},
site_path: {
ConfigManager::new(pool.clone()).get_config(
@ -331,17 +325,11 @@ impl AppConfig {
}
pub fn from_env() -> Result<Self, anyhow::Error> {
// Read from Vault with fallback to defaults
let secrets = SecretsManager::from_env().ok();
let secrets = SecretsManager::get().ok().map(|sm| sm.clone());
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(),
)
});
.and_then(|s| s.get_drive_config().ok())
.unwrap_or((String::new(), String::new(), String::new()));
let minio = DriveConfig {
server: drive_server,
@ -368,7 +356,7 @@ impl AppConfig {
server: ServerConfig {
host: "0.0.0.0".to_string(),
port,
base_url: "http://localhost:8080".to_string(),
base_url: "".to_string(),
},
site_path: format!("{}/sites", crate::core::shared::utils::get_stack_path()),

View file

@ -24,8 +24,8 @@ pub async fn reload_config(
// Get LLM config
let llm_url = config_manager
.get_config(&default_bot_id, "llm-url", Some("http://localhost:8081"))
.unwrap_or_else(|_| "http://localhost:8081".to_string());
.get_config(&default_bot_id, "llm-url", Some(""))
.unwrap_or_else(|_| "".to_string());
let llm_model = config_manager
.get_config(&default_bot_id, "llm-model", Some("local"))

View file

@ -81,7 +81,7 @@ pub async fn provision_user_handler(
.config
.as_ref()
.map(|c| c.server.base_url.clone())
.unwrap_or_else(|| "http://localhost:9000".to_string());
.unwrap_or_else(|| "".to_string());
let provisioning = UserProvisioningService::new(state.conn.clone(), s3_client, base_url);
@ -114,7 +114,7 @@ pub async fn deprovision_user_handler(
.config
.as_ref()
.map(|c| c.server.base_url.clone())
.unwrap_or_else(|| "http://localhost:9000".to_string());
.unwrap_or_else(|| "".to_string());
let provisioning = UserProvisioningService::new(state.conn.clone(), s3_client, base_url);
@ -257,7 +257,7 @@ pub async fn check_services_status(State(state): State<Arc<AppState>>) -> impl I
let client = create_tls_client(Some(2));
if let Ok(response) = client.get("http://localhost:8300/healthz").send().await {
if let Ok(response) = client.get("/healthz").send().await {
status.directory = response.status().is_success();
}

View file

@ -39,7 +39,7 @@ pub struct EmbeddingConfig {
impl Default for EmbeddingConfig {
fn default() -> Self {
Self {
embedding_url: "http://localhost:8082".to_string(),
embedding_url: "".to_string(),
embedding_model: "BAAI/bge-multilingual-gemma2".to_string(),
embedding_key: None,
dimensions: 2048,
@ -58,7 +58,7 @@ impl EmbeddingConfig {
/// Load embedding config from bot's config.csv (similar to llm-url, llm-model)
/// This allows configuring embedding server per-bot in config.csv:
/// embedding-url,http://localhost:8082
/// embedding-url,
/// embedding-model,bge-small-en-v1.5
/// embedding-dimensions,384
/// embedding-batch-size,16
@ -77,7 +77,7 @@ impl EmbeddingConfig {
.ok()
.filter(|s| !s.is_empty()),
Err(_) => None,
}.unwrap_or_else(|| "http://localhost:8082".to_string());
}.unwrap_or_else(|| "".to_string());
let embedding_model = match pool.get() {
Ok(mut conn) => bot_configuration

View file

@ -22,7 +22,7 @@ pub struct QdrantConfig {
impl Default for QdrantConfig {
fn default() -> Self {
Self {
url: "http://localhost:6333".to_string(),
url: "".to_string(),
api_key: None,
timeout_secs: 30,
}
@ -36,8 +36,8 @@ impl QdrantConfig {
} else {
let config_manager = ConfigManager::new(pool);
let url = config_manager
.get_config(bot_id, "vectordb-url", Some("http://localhost:6333"))
.unwrap_or_else(|_| "http://localhost:6333".to_string());
.get_config(bot_id, "vectordb-url", Some(""))
.unwrap_or_else(|_| "".to_string());
(url, None)
};
Self {

View file

@ -439,7 +439,7 @@ async fn get_bot_config(state: &AppState) -> HashMap<String, String> {
fn get_base_url(state: &AppState) -> String {
let _ = state;
"http://localhost:9000".to_string()
"".to_string()
}
async fn create_or_get_oauth_user(

View file

@ -15,7 +15,7 @@ pub async fn setup_alm() -> anyhow::Result<()> {
let config_path = stack_path.join("conf/alm-ci/config.yaml");
// Check Vault if already set up
if let Ok(secrets_manager) = crate::core::secrets::SecretsManager::from_env() {
if let Ok(secrets_manager) = crate::core::secrets::SecretsManager::get() {
if secrets_manager.is_enabled() {
if let Ok(secrets) = secrets_manager.get_secret(crate::core::secrets::SecretPaths::ALM).await {
if let (Some(user), Some(token)) = (secrets.get("username"), secrets.get("runner_token")) {
@ -52,7 +52,7 @@ PATH = {}/data/alm/gitea.db
[server]
HTTP_PORT = 3000
DOMAIN = localhost
ROOT_URL = http://localhost:3000
ROOT_URL =
[security]
INSTALL_LOCK = true
@ -67,7 +67,7 @@ INSTALL_LOCK = true
// Generate credentials and attempt to configure via HTTP API
let username = "botserver";
let password = generate_random_string(32);
let alm_url = "http://localhost:3000";
let alm_url = "";
// Try to create admin user and get runner token via HTTP API
// Note: Forgejo CLI binary may segfault on some systems, so we use curl
@ -95,7 +95,7 @@ INSTALL_LOCK = true
}
// Store in Vault
if let Ok(secrets_manager) = crate::core::secrets::SecretsManager::from_env() {
if let Ok(secrets_manager) = crate::core::secrets::SecretsManager::get() {
if secrets_manager.is_enabled() {
let mut secrets = HashMap::new();
secrets.insert("url".to_string(), alm_url.to_string());

View file

@ -437,7 +437,7 @@ async fn vault_migrate(env_file: &str) -> Result<()> {
}
}
let manager = SecretsManager::from_env()?;
let manager = SecretsManager::get()?.clone();
if !manager.is_enabled() {
return Err(anyhow::anyhow!(
"Vault not configured. Set VAULT_ADDR and VAULT_TOKEN"
@ -605,7 +605,7 @@ async fn vault_migrate(env_file: &str) -> Result<()> {
}
async fn vault_put(path: &str, kvs: &[&str]) -> Result<()> {
let manager = SecretsManager::from_env()?;
let manager = SecretsManager::get()?.clone();
if !manager.is_enabled() {
return Err(anyhow::anyhow!("Vault not configured"));
}
@ -669,7 +669,7 @@ async fn vault_put(path: &str, kvs: &[&str]) -> Result<()> {
}
async fn vault_get(path: &str, key: Option<&str>) -> Result<()> {
let manager = SecretsManager::from_env()?;
let manager = SecretsManager::get()?.clone();
if !manager.is_enabled() {
return Err(anyhow::anyhow!("Vault not configured"));
}
@ -751,7 +751,7 @@ async fn print_version(show_all: bool) -> Result<()> {
println!();
println!("Secrets:");
if let Ok(manager) = SecretsManager::from_env() {
if let Ok(manager) = SecretsManager::get() {
if manager.is_enabled() {
match manager.health_check().await {
Ok(true) => println!(" Vault: connected"),
@ -805,7 +805,7 @@ fn generate_secret_key() -> String {
}
async fn rotate_secret(component: &str) -> Result<()> {
let manager = SecretsManager::from_env()?;
let manager = SecretsManager::get()?.clone();
if !manager.is_enabled() {
return Err(anyhow::anyhow!(
"Vault not configured. Set VAULT_ADDR and VAULT_TOKEN"
@ -1096,7 +1096,7 @@ async fn rotate_all_secrets() -> Result<()> {
return Ok(());
}
let manager = SecretsManager::from_env()?;
let manager = SecretsManager::get()?.clone();
if !manager.is_enabled() {
return Err(anyhow::anyhow!("Vault not configured"));
}
@ -1183,7 +1183,7 @@ async fn verify_rotation(component: &str) -> Result<()> {
match component {
"tables" => {
let manager = SecretsManager::from_env()?;
let manager = SecretsManager::get()?.clone();
let secrets = manager.get_secret(SecretPaths::TABLES).await?;
let host = secrets.get("host").cloned().unwrap_or_else(|| "localhost".to_string());
@ -1228,9 +1228,9 @@ async fn verify_rotation(component: &str) -> Result<()> {
// Try to determine the health endpoint
let health_urls = vec![
"http://localhost:8080/health",
"http://localhost:5858/health",
"http://localhost:3000/health",
"/health",
"/health",
"/health",
];
let mut success = false;
@ -1268,7 +1268,7 @@ async fn verify_rotation(component: &str) -> Result<()> {
}
async fn vault_health() -> Result<()> {
let manager = SecretsManager::from_env()?;
let manager = SecretsManager::get()?.clone();
if !manager.is_enabled() {
println!("x Vault not configured");

View file

@ -74,11 +74,11 @@ pub async fn setup_directory() -> anyhow::Result<crate::core::package_manager::s
let stack_path = get_stack_path();
let base_url = "http://localhost:8300".to_string();
let base_url = "".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 let Ok(secrets_manager) = crate::core::secrets::SecretsManager::get() {
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")) {
@ -98,7 +98,7 @@ pub async fn setup_directory() -> anyhow::Result<crate::core::package_manager::s
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()),
redirect_uri: secrets.get("redirect_uri").cloned().unwrap_or_else(|| "/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(),
@ -135,7 +135,7 @@ pub async fn setup_directory() -> anyhow::Result<crate::core::package_manager::s
.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 let Ok(secrets_manager) = crate::core::secrets::SecretsManager::get() {
if secrets_manager.is_enabled() {
let mut secrets = HashMap::new();
secrets.insert("url".to_string(), config.base_url.clone());

View file

@ -74,7 +74,7 @@ impl DirectorySetup {
issuer: self.base_url.clone(),
client_id,
client_secret,
redirect_uri: "http://localhost:3000/auth/callback".to_string(),
redirect_uri: "/auth/callback".to_string(),
project_id,
api_url: self.base_url.clone(),
service_account_key: None,
@ -227,8 +227,8 @@ impl DirectorySetup {
let app_body = serde_json::json!({
"name": "BotServer",
"redirectUris": [
"http://localhost:3000/auth/callback",
"http://localhost:8080/auth/callback"
"/auth/callback",
"/auth/callback"
],
"responseTypes": ["OIDC_RESPONSE_TYPE_CODE"],
"grantTypes": [
@ -237,7 +237,7 @@ impl DirectorySetup {
],
"appType": "OIDC_APP_TYPE_WEB",
"authMethodType": "OIDC_AUTH_METHOD_TYPE_POST",
"postLogoutRedirectUris": ["http://localhost:3000"],
"postLogoutRedirectUris": [""],
"devMode": true
});
@ -320,14 +320,14 @@ mod tests {
#[test]
fn test_directory_config_serialization() {
let config = DirectoryConfig {
base_url: "http://localhost:8300".to_string(),
issuer_url: "http://localhost:8300".to_string(),
issuer: "http://localhost:8300".to_string(),
base_url: "".to_string(),
issuer_url: "".to_string(),
issuer: "".to_string(),
client_id: "test_client".to_string(),
client_secret: "test_secret".to_string(),
redirect_uri: "http://localhost:3000/callback".to_string(),
redirect_uri: "/callback".to_string(),
project_id: "12345".to_string(),
api_url: "http://localhost:8300".to_string(),
api_url: "".to_string(),
service_account_key: None,
};

View file

@ -7,6 +7,7 @@ use std::env;
use std::path::PathBuf;
use std::sync::Arc;
use std::sync::Arc as StdArc;
use std::sync::OnceLock;
use std::sync::RwLock as StdRwLock;
use uuid::Uuid;
use vaultrs::client::{VaultClient, VaultClientSettingsBuilder};
@ -130,19 +131,19 @@ impl SecretsManager {
settings_builder.verify(true);
if ca_path.exists() {
info!("Using CA certificate for Vault: {}", ca_cert);
debug!("Using CA certificate for Vault: {}", ca_cert);
settings_builder.ca_certs(vec![ca_cert]);
}
}
if cert_path.exists() && key_path.exists() && !skip_verify {
info!("Using mTLS client certificate for Vault: {}", client_cert);
debug!("Using mTLS client certificate for Vault: {}", client_cert);
}
let settings = settings_builder.build()?;
let client = VaultClient::new(settings)?;
info!("Vault client initialized with TLS: {}", addr);
debug!("Vault client initialized with TLS: {}", addr);
Ok(Self {
client: Some(StdArc::new(client)),
@ -152,6 +153,26 @@ impl SecretsManager {
})
}
pub fn get() -> Result<&'static SecretsManager> {
static INIT: OnceLock<Result<SecretsManager>> = OnceLock::new();
INIT.get_or_init(|| {
match Self::from_env() {
Ok(manager) => {
info!("SecretsManager singleton initialized (Vault: {})", env::var("VAULT_ADDR").unwrap_or_default());
Ok(manager)
}
Err(e) => {
warn!("Failed to initialize SecretsManager: {}", e);
Err(e)
}
}
}).as_ref().map_err(|e| anyhow!("SecretsManager initialization failed: {}", e))
}
pub fn get_clone() -> Result<SecretsManager> {
Self::get().map(|sm| sm.clone())
}
pub fn is_enabled(&self) -> bool {
self.enabled
}
@ -208,30 +229,33 @@ impl SecretsManager {
default.to_string()
}
pub fn get_drive_config(&self) -> (String, String, String) {
pub fn get_drive_config(&self) -> Result<(String, String, String)> {
// Try to read from Vault using std process (curl)
if let Ok(vault_addr) = std::env::var("VAULT_ADDR") {
if let Ok(vault_token) = std::env::var("VAULT_TOKEN") {
let ca_cert = std::env::var("VAULT_CACERT").unwrap_or_default();
log::info!("Attempting to read drive config from Vault: {}", vault_addr);
let url = format!("{}/v1/secret/data/gbo/drive", vault_addr);
// Use curl via Command for reliable TLS
let result = std::process::Command::new("curl")
.args(&["-sf", "--cacert", &ca_cert, "-H", &format!("X-Vault-Token: {}", &vault_token), &url])
.output();
match result {
Ok(output) if output.status.success() => {
if let Ok(data) = serde_json::from_slice::<serde_json::Value>(&output.stdout) {
if let Some(secret_data) = data.get("data").and_then(|d| d.get("data")) {
let host = secret_data.get("host").and_then(|v| v.as_str()).unwrap_or("localhost:9100");
let accesskey = secret_data.get("accesskey").and_then(|v| v.as_str()).unwrap_or("minioadmin");
let secret = secret_data.get("secret").and_then(|v| v.as_str()).unwrap_or("minioadmin");
let host = secret_data.get("host").and_then(|v| v.as_str())
.ok_or_else(|| anyhow!("drive host not configured in Vault"))?;
let accesskey = secret_data.get("accesskey").and_then(|v| v.as_str())
.ok_or_else(|| anyhow!("drive accesskey not configured in Vault"))?;
let secret = secret_data.get("secret").and_then(|v| v.as_str())
.ok_or_else(|| anyhow!("drive secret not configured in Vault"))?;
log::info!("get_drive_config: Successfully read from Vault - host={}", host);
return (host.to_string(), accesskey.to_string(), secret.to_string());
return Ok((host.to_string(), accesskey.to_string(), secret.to_string()));
}
}
}
@ -245,82 +269,86 @@ impl SecretsManager {
}
}
log::warn!("get_drive_config: Falling back to defaults - Vault not available");
("localhost:9100".to_string(), "minioadmin".to_string(), "minioadmin".to_string())
Err(anyhow!("Drive configuration not available in Vault and VAULT_ADDR/VAULT_TOKEN not set"))
}
pub fn get_cache_config(&self) -> (String, u16, Option<String>) {
pub fn get_cache_config(&self) -> Result<(String, u16, Option<String>)> {
if let Ok(vault_addr) = std::env::var("VAULT_ADDR") {
if let Ok(vault_token) = std::env::var("VAULT_TOKEN") {
let ca_cert = std::env::var("VAULT_CACERT").unwrap_or_default();
log::info!("Attempting to read cache config from Vault: {}", vault_addr);
let url = format!("{}/v1/secret/data/gbo/cache", vault_addr);
let result = std::process::Command::new("curl")
.args(&["-sf", "--cacert", &ca_cert, "-H", &format!("X-Vault-Token: {}", &vault_token), &url])
.output();
if let Ok(output) = result {
if output.status.success() {
if let Ok(data) = serde_json::from_slice::<serde_json::Value>(&output.stdout) {
if let Some(secret_data) = data.get("data").and_then(|d| d.get("data")) {
let host = secret_data.get("host").and_then(|v| v.as_str()).unwrap_or("localhost");
let port = secret_data.get("port").and_then(|v| v.as_str()).unwrap_or("6379").parse().unwrap_or(6379);
let host = secret_data.get("host").and_then(|v| v.as_str())
.ok_or_else(|| anyhow!("cache host not configured in Vault"))?;
let port = secret_data.get("port").and_then(|v| v.as_str())
.ok_or_else(|| anyhow!("cache port not configured in Vault"))?
.parse().map_err(|e| anyhow!("Invalid cache port: {}", e))?;
let password = secret_data.get("password").and_then(|v| v.as_str()).map(|s| s.to_string());
log::info!("get_cache_config: Successfully read from Vault - host={}", host);
return (host.to_string(), port, password);
return Ok((host.to_string(), port, password));
}
}
}
}
}
}
log::warn!("get_cache_config: Falling back to defaults");
("localhost".to_string(), 6379, None)
Err(anyhow!("Cache configuration not available in Vault and VAULT_ADDR/VAULT_TOKEN not set"))
}
pub fn get_qdrant_config(&self) -> (String, Option<String>) {
pub fn get_qdrant_config(&self) -> Result<(String, Option<String>)> {
if let Ok(vault_addr) = std::env::var("VAULT_ADDR") {
if let Ok(vault_token) = std::env::var("VAULT_TOKEN") {
let ca_cert = std::env::var("VAULT_CACERT").unwrap_or_default();
log::info!("Attempting to read qdrant config from Vault: {}", vault_addr);
let url = format!("{}/v1/secret/data/gbo/vectordb", vault_addr);
let result = std::process::Command::new("curl")
.args(&["-sf", "--cacert", &ca_cert, "-H", &format!("X-Vault-Token: {}", &vault_token), &url])
.output();
if let Ok(output) = result {
if output.status.success() {
if let Ok(data) = serde_json::from_slice::<serde_json::Value>(&output.stdout) {
if let Some(secret_data) = data.get("data").and_then(|d| d.get("data")) {
let url = secret_data.get("url").and_then(|v| v.as_str()).unwrap_or("http://localhost:6333");
let url = secret_data.get("url").and_then(|v| v.as_str())
.ok_or_else(|| anyhow!("vectordb url not configured in Vault"))?;
let api_key = secret_data.get("api_key").and_then(|v| v.as_str()).map(|s| s.to_string());
log::info!("get_qdrant_config: Successfully read from Vault - url={}", url);
return (url.to_string(), api_key);
return Ok((url.to_string(), api_key));
}
}
}
}
}
}
log::warn!("get_qdrant_config: Falling back to defaults");
("http://localhost:6333".to_string(), None)
Err(anyhow!("VectorDB configuration not available in Vault and VAULT_ADDR/VAULT_TOKEN not set"))
}
pub fn get_database_config_sync(&self) -> (String, u16, String, String, String) {
pub fn get_database_config_sync(&self) -> Result<(String, u16, String, String, String)> {
if let Ok(secrets) = Self::get_from_env(SecretPaths::TABLES) {
return (
secrets.get("host").cloned().unwrap_or_else(|| "localhost".to_string()),
secrets.get("port").and_then(|p| p.parse().ok()).unwrap_or(5432),
secrets.get("database").cloned().unwrap_or_else(|| "botserver".to_string()),
secrets.get("username").cloned().unwrap_or_else(|| "gbuser".to_string()),
secrets.get("password").cloned().unwrap_or_default(),
);
let host = secrets.get("host").cloned()
.ok_or_else(|| anyhow!("database host not configured"))?;
let port = secrets.get("port").and_then(|p| p.parse().ok())
.ok_or_else(|| anyhow!("database port not configured"))?;
let database = secrets.get("database").cloned()
.ok_or_else(|| anyhow!("database name not configured"))?;
let username = secrets.get("username").cloned()
.ok_or_else(|| anyhow!("database username not configured"))?;
let password = secrets.get("password").cloned().unwrap_or_default();
return Ok((host, port, database, username, password));
}
("localhost".to_string(), 5432, "botserver".to_string(), "gbuser".to_string(), "changeme".to_string())
Err(anyhow!("Database configuration not available"))
}
pub async fn get_drive_credentials(&self) -> Result<(String, String)> {
@ -333,17 +361,16 @@ impl SecretsManager {
pub async fn get_database_config(&self) -> Result<(String, u16, String, String, String)> {
let s = self.get_secret(SecretPaths::TABLES).await?;
Ok((
s.get("host").cloned().unwrap_or_else(|| "localhost".into()),
s.get("port").and_then(|p| p.parse().ok()).unwrap_or(5432),
s.get("database")
.cloned()
.unwrap_or_else(|| "botserver".into()),
s.get("username")
.cloned()
.unwrap_or_else(|| "gbuser".into()),
s.get("password").cloned().unwrap_or_default(),
))
let host = s.get("host").cloned()
.ok_or_else(|| anyhow!("database host not configured in Vault"))?;
let port = s.get("port").and_then(|p| p.parse().ok())
.ok_or_else(|| anyhow!("database port not configured in Vault"))?;
let database = s.get("database").cloned()
.ok_or_else(|| anyhow!("database name not configured in Vault"))?;
let username = s.get("username").cloned()
.ok_or_else(|| anyhow!("database username not configured in Vault"))?;
let password = s.get("password").cloned().unwrap_or_default();
Ok((host, port, database, username, password))
}
pub async fn get_database_url(&self) -> Result<String> {
@ -374,10 +401,10 @@ impl SecretsManager {
pub async fn get_directory_config(&self) -> Result<(String, String, String, String)> {
let s = self.get_secret(SecretPaths::DIRECTORY).await?;
let url = s.get("url").cloned()
.ok_or_else(|| anyhow!("directory url not configured in Vault"))?;
Ok((
s.get("url")
.cloned()
.unwrap_or_else(|| "http://localhost:9000".into()),
url,
s.get("project_id").cloned().unwrap_or_default(),
s.get("client_id").cloned().unwrap_or_default(),
s.get("client_secret").cloned().unwrap_or_default(),
@ -394,20 +421,17 @@ impl SecretsManager {
pub async fn get_vectordb_config(&self) -> Result<(String, Option<String>)> {
let s = self.get_secret(SecretPaths::VECTORDB).await?;
Ok((
s.get("url")
.cloned()
.unwrap_or_else(|| "http://localhost:6333".into()),
s.get("api_key").cloned(),
))
let url = s.get("url").cloned()
.ok_or_else(|| anyhow!("vectordb url not configured in Vault"))?;
Ok((url, s.get("api_key").cloned()))
}
pub async fn get_observability_config(&self) -> Result<(String, String, String, String)> {
let s = self.get_secret(SecretPaths::OBSERVABILITY).await?;
let url = s.get("url").cloned()
.ok_or_else(|| anyhow!("observability url not configured in Vault"))?;
Ok((
s.get("url")
.cloned()
.unwrap_or_else(|| "http://localhost:8086".into()),
url,
s.get("org").cloned().unwrap_or_else(|| "system".into()),
s.get("bucket").cloned().unwrap_or_else(|| "metrics".into()),
s.get("token").cloned().unwrap_or_default(),
@ -441,13 +465,13 @@ impl SecretsManager {
});
if let Ok(Some(secrets)) = rx.recv() {
return (
secrets.get("url").cloned().unwrap_or_else(|| "http://localhost:9000".into()),
secrets.get("url").cloned().unwrap_or_else(|| "".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())
("".to_string(), String::new(), String::new(), String::new())
}
pub fn get_email_config(&self) -> (String, u16, String, String, String) {
@ -496,14 +520,14 @@ impl SecretsManager {
});
if let Ok(Some(secrets)) = rx.recv() {
return (
secrets.get("url").cloned().unwrap_or_else(|| "http://localhost:8081".into()),
secrets.get("url").cloned().unwrap_or_else(|| "".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()),
secrets.get("ollama_url").cloned().unwrap_or_else(|| "".into()),
);
}
("http://localhost:8081".to_string(), "gpt-4".to_string(), None, None, "http://localhost:11434".to_string())
("".to_string(), "gpt-4".to_string(), None, None, "".to_string())
}
pub fn get_meet_config(&self) -> (String, String, String) {
@ -524,12 +548,12 @@ impl SecretsManager {
});
if let Ok(Some(secrets)) = rx.recv() {
return (
secrets.get("url").cloned().unwrap_or_else(|| "http://localhost:7880".into()),
secrets.get("url").cloned().unwrap_or_else(|| "".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())
("".to_string(), String::new(), String::new())
}
pub fn get_vectordb_config_sync(&self) -> (String, Option<String>) {
@ -550,11 +574,11 @@ impl SecretsManager {
});
if let Ok(Some(secrets)) = rx.recv() {
return (
secrets.get("url").cloned().unwrap_or_else(|| "http://localhost:6333".into()),
secrets.get("url").cloned().unwrap_or_else(|| "".into()),
secrets.get("api_key").cloned(),
);
}
("http://localhost:6333".to_string(), None)
("".to_string(), None)
}
pub fn get_observability_config_sync(&self) -> (String, String, String, String) {
@ -575,13 +599,13 @@ impl SecretsManager {
});
if let Ok(Some(secrets)) = rx.recv() {
return (
secrets.get("url").cloned().unwrap_or_else(|| "http://localhost:8086".into()),
secrets.get("url").cloned().unwrap_or_else(|| "".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())
("".to_string(), "system".to_string(), "metrics".to_string(), String::new())
}
pub fn get_alm_config(&self) -> (String, String, String) {
@ -602,12 +626,12 @@ impl SecretsManager {
});
if let Ok(Some(secrets)) = rx.recv() {
return (
secrets.get("url").cloned().unwrap_or_else(|| "http://localhost:9000".into()),
secrets.get("url").cloned().unwrap_or_else(|| "".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())
("".to_string(), String::new(), String::new())
}
pub fn get_jwt_secret_sync(&self) -> String {
@ -712,12 +736,14 @@ impl SecretsManager {
secrets.insert("username".into(), "gbuser".into());
secrets.insert("password".into(), "changeme".into());
}
"directory" | "gbo/directory" | "system/directory" => {
secrets.insert("url".into(), "http://localhost:9000".into());
secrets.insert("project_id".into(), String::new());
secrets.insert("client_id".into(), String::new());
secrets.insert("client_secret".into(), String::new());
}
"directory" | "gbo/directory" | "system/directory" => {
secrets.insert("url".into(), "".into());
secrets.insert("host".into(), "localhost".into());
secrets.insert("port".into(), "9000".into());
secrets.insert("project_id".into(), String::new());
secrets.insert("client_id".into(), String::new());
secrets.insert("client_secret".into(), String::new());
}
"drive" | "gbo/drive" | "system/drive" => {
secrets.insert("host".into(), "localhost".into());
secrets.insert("port".into(), "9000".into());
@ -737,35 +763,35 @@ impl SecretsManager {
secrets.insert("smtp_from".into(), String::new());
}
"llm" | "gbo/llm" | "system/llm" => {
secrets.insert("url".into(), "http://localhost:8081".into());
secrets.insert("url".into(), "".into());
secrets.insert("model".into(), "gpt-4".into());
secrets.insert("openai_key".into(), String::new());
secrets.insert("anthropic_key".into(), String::new());
secrets.insert("ollama_url".into(), "http://localhost:11434".into());
secrets.insert("ollama_url".into(), "".into());
}
"encryption" | "gbo/encryption" | "system/encryption" => {
secrets.insert("master_key".into(), String::new());
}
"meet" | "gbo/meet" | "system/meet" => {
secrets.insert("url".into(), "http://localhost:7880".into());
secrets.insert("url".into(), "".into());
secrets.insert("app_id".into(), String::new());
secrets.insert("app_secret".into(), String::new());
}
"vectordb" | "gbo/vectordb" | "system/vectordb" => {
secrets.insert("url".to_string(), "http://localhost:6333".into());
secrets.insert("url".to_string(), "".into());
secrets.insert("host".to_string(), "localhost".into());
secrets.insert("port".to_string(), "6333".into());
secrets.insert("grpc_port".to_string(), "6334".into());
secrets.insert("api_key".to_string(), String::new());
}
"observability" | "gbo/observability" | "system/observability" => {
secrets.insert("url".into(), "http://localhost:8086".into());
secrets.insert("url".into(), "".into());
secrets.insert("org".into(), "system".into());
secrets.insert("bucket".into(), "metrics".into());
secrets.insert("token".into(), String::new());
}
"alm" | "gbo/alm" | "system/alm" => {
secrets.insert("url".into(), "http://localhost:3000".into());
secrets.insert("url".into(), "".into());
secrets.insert("token".into(), String::new());
secrets.insert("default_org".into(), String::new());
}
@ -779,14 +805,14 @@ impl SecretsManager {
secrets.insert("secret_key".into(), String::new());
}
"app" | "gbo/app" | "system/app" => {
secrets.insert("url".into(), "http://localhost:8080".into());
secrets.insert("url".into(), "".into());
secrets.insert("environment".into(), "development".into());
}
"jwt" | "gbo/jwt" | "system/jwt" => {
secrets.insert("secret".into(), String::new());
}
"models" | "gbo/models" | "system/models" => {
secrets.insert("url".into(), "http://localhost:8001".into());
secrets.insert("url".into(), "".into());
}
_ => {
log::debug!("No default values for secret path: {}", path);

View file

@ -18,7 +18,7 @@ pub async fn send_invitation_email(
custom_message: Option<String>,
invitation_id: Uuid,
) -> Result<(), String> {
let smtp = crate::core::secrets::SecretsManager::from_env()
let smtp = crate::core::secrets::SecretsManager::get()
.ok()
.and_then(|sm| {
let sm_owned = sm.clone();
@ -28,9 +28,7 @@ pub async fn send_invitation_email(
.enable_all()
.build();
let result = if let Ok(rt) = rt {
rt.block_on(async move {
sm_owned.get_secret(crate::core::secrets::SecretPaths::EMAIL).await.ok()
})
rt.block_on(async move { sm_owned.get_secret(crate::core::secrets::SecretPaths::EMAIL).await.ok() })
} else {
None
};

View file

@ -250,13 +250,13 @@ impl Default for TestAppStateBuilder {
#[cfg(feature = "directory")]
pub fn create_mock_auth_service() -> AuthService {
let config = ZitadelConfig {
issuer_url: "http://localhost:9000".to_string(),
issuer: "http://localhost:9000".to_string(),
issuer_url: "".to_string(),
issuer: "".to_string(),
client_id: "mock_client_id".to_string(),
client_secret: "mock_client_secret".to_string(),
redirect_uri: "http://localhost:3000/callback".to_string(),
redirect_uri: "/callback".to_string(),
project_id: "mock_project_id".to_string(),
api_url: "http://localhost:9000".to_string(),
api_url: "".to_string(),
service_account_key: None,
};

View file

@ -34,7 +34,7 @@ static SECRETS_MANAGER: std::sync::LazyLock<Arc<RwLock<Option<SecretsManager>>>>
std::sync::LazyLock::new(|| Arc::new(RwLock::new(None)));
pub async fn init_secrets_manager() -> Result<()> {
let manager = SecretsManager::from_env()?;
let manager = SecretsManager::get()?.clone();
let mut guard = SECRETS_MANAGER.write().map_err(|e| anyhow::anyhow!("Lock poisoned: {}", e))?;
*guard = Some(manager);
Ok(())

View file

@ -490,16 +490,7 @@ impl ApiUrls {
pub struct InternalUrls;
impl InternalUrls {
pub const DIRECTORY_BASE: &'static str = "http://localhost:8300";
pub const DATABASE: &'static str = "postgres://localhost:5432";
pub const CACHE: &'static str = "redis://localhost:6379";
pub const DRIVE: &'static str = "http://localhost:9100";
pub const EMAIL: &'static str = "http://localhost:8025";
pub const LLM: &'static str = "http://localhost:8081";
pub const EMBEDDING: &'static str = "http://localhost:8082";
pub const QDRANT: &'static str = "http://localhost:6334";
pub const FORGEJO: &'static str = "http://localhost:3000";
pub const LIVEKIT: &'static str = "http://localhost:7880";
// No localhost defaults - services must be configured via Vault or env vars
pub const BOTMODELS_VISION_QRCODE: &'static str = "/api/vision/qrcode";
pub const BOTMODELS_SPEECH_TO_TEXT: &'static str = "/api/speech/to-text";
pub const BOTMODELS_VISION_DESCRIBE_VIDEO: &'static str = "/api/vision/describe-video";

View file

@ -268,7 +268,7 @@ fn save_setup_credentials(result: &BootstrapResult) {
Password: {:<46}
Email: {:<46}
🌐 LOGIN NOW: http://localhost:8080/suite/login ║
🌐 LOGIN NOW: /suite/login
@ -358,7 +358,7 @@ fn print_bootstrap_credentials(result: &BootstrapResult) {
println!("{:^60}", "");
println!("{:56}", "🌐 LOGIN NOW:");
println!("{:^60}", "");
println!("{:56}", "http://localhost:8080/suite/login");
println!("{:56}", "/suite/login");
println!("{:^60}", "");
println!("{}", separator);
println!("{:^60}", "");

View file

@ -65,8 +65,8 @@ fn is_tracking_pixel_enabled(state: &Arc<AppState>, bot_id: Option<Uuid>) -> boo
fn inject_tracking_pixel(html_body: &str, tracking_id: &str, state: &Arc<AppState>) -> String {
let config_manager = crate::core::config::ConfigManager::new(state.conn.clone());
let base_url = config_manager
.get_config(&Uuid::nil(), "server-url", Some("http://localhost:9000"))
.unwrap_or_else(|_| "http://localhost:9000".to_string());
.get_config(&Uuid::nil(), "server-url", Some(""))
.unwrap_or_else(|_| "".to_string());
let pixel_url = format!("{}/api/email/tracking/pixel/{}", base_url, tracking_id);
let pixel_html = format!(

View file

@ -31,8 +31,8 @@ pub fn is_tracking_pixel_enabled(state: &Arc<AppState>, bot_id: Option<Uuid>) ->
pub fn inject_tracking_pixel(html_body: &str, tracking_id: &str, state: &Arc<AppState>) -> String {
let config_manager = crate::core::config::ConfigManager::new(state.conn.clone());
let base_url = config_manager
.get_config(&Uuid::nil(), "server-url", Some("http://localhost:9000"))
.unwrap_or_else(|_| "http://localhost:9000".to_string());
.get_config(&Uuid::nil(), "server-url", Some(""))
.unwrap_or_else(|_| "".to_string());
let pixel_url = format!("{}/api/email/tracking/pixel/{}", base_url, tracking_id);
let pixel_html = format!(

View file

@ -421,11 +421,11 @@ impl EmailEmbeddingGenerator {
}
pub async fn generate_text_embedding(&self, text: &str) -> Result<Vec<f32>> {
let embedding_url = if let Ok(sm) = crate::core::secrets::SecretsManager::from_env() {
let embedding_url = if let Ok(sm) = crate::core::secrets::SecretsManager::get() {
let (llm_url, _, _, _, ollama_url) = sm.get_llm_config();
if !ollama_url.is_empty() { ollama_url } else { llm_url }
} else {
"http://localhost:8082".to_string()
"".to_string()
};
match self.generate_local_embedding(text, &embedding_url).await {
Ok(embedding) => Ok(embedding),

View file

@ -45,14 +45,14 @@ pub async fn ensure_llama_servers_running(
.get_config(&default_bot_id, "llm-server", Some("true"))
.unwrap_or_else(|_| "true".to_string()),
config_manager
.get_config(&default_bot_id, "llm-url", Some("http://localhost:8081"))
.unwrap_or_else(|_| "http://localhost:8081".to_string()),
.get_config(&default_bot_id, "llm-url", Some(""))
.unwrap_or_else(|_| "".to_string()),
config_manager
.get_config(&default_bot_id, "llm-model", None)
.unwrap_or_default(),
config_manager
.get_config(&default_bot_id, "embedding-url", Some("http://localhost:8082"))
.unwrap_or_else(|_| "http://localhost:8082".to_string()),
.get_config(&default_bot_id, "embedding-url", Some(""))
.unwrap_or_else(|_| "".to_string()),
embedding_model_result.unwrap_or_default(),
config_manager
.get_config(&default_bot_id, "llm-server-path", None)

View file

@ -289,7 +289,7 @@ impl LLMProvider for OpenAIClient {
128000 // Cerebras gpt-oss models and GPT-4 variants
} else if model.contains("gpt-3.5") {
16385
} else if model.starts_with("http://localhost:808") || model == "local" {
} else if model.starts_with("") || model == "local" {
768 // Local llama.cpp server context limit
} else {
32768 // Default conservative limit for modern models
@ -378,7 +378,7 @@ impl LLMProvider for OpenAIClient {
128000 // Cerebras gpt-oss models and GPT-4 variants
} else if model.contains("gpt-3.5") {
16385
} else if model.starts_with("http://localhost:808") || model == "local" {
} else if model.starts_with("") || model == "local" {
768 // Local llama.cpp server context limit
} else {
32768 // Default conservative limit for modern models
@ -885,10 +885,10 @@ mod tests {
fn test_openai_client_new_custom_url() {
let client = OpenAIClient::new(
"test_key".to_string(),
Some("http://localhost:9000".to_string()),
Some("".to_string()),
None,
);
assert_eq!(client.base_url, "http://localhost:9000");
assert_eq!(client.base_url, "");
}
#[test]

View file

@ -305,16 +305,16 @@ pub async fn init_redis() -> Option<Arc<redis::Client>> {
let mut urls: Vec<String> = Vec::new();
if let Some(url) = env_url {
urls.push(url);
} else if let Ok(secrets) = SecretsManager::from_env() {
} else if let Ok(secrets) = SecretsManager::get() {
if let Ok(data) = secrets.get_secret(SecretPaths::CACHE).await {
let host = data.get("host").cloned().unwrap_or_else(|| "localhost".into());
let host = data.get("host").cloned().unwrap_or_else(|| "".into());
let port = data.get("port").and_then(|p| p.parse().ok()).unwrap_or(6379);
urls.push(format!("redis://{}:{}", host, port));
if let Some(pass) = data.get("password") {
urls.push(format!("redis://:{}@{}:{}", pass, host, port));
}
} else {
urls.push("redis://localhost:6379".to_string());
urls.push(String::new());
}
} else {
urls.push("redis://localhost:6379".to_string());
@ -453,8 +453,8 @@ pub async fn create_app_state(
url
} else {
config_manager
.get_config(&default_bot_id, "llm-url", Some("http://localhost:8081"))
.unwrap_or_else(|_| "http://localhost:8081".to_string())
.get_config(&default_bot_id, "llm-url", Some(""))
.unwrap_or_else(|_| "".to_string())
};
info!("LLM URL: {}", llm_url);
@ -486,8 +486,8 @@ pub async fn create_app_state(
&& !llm_url.contains("127.0.0.1")
&& (llm_url.contains("api.z.ai") || llm_url.contains("openai.com") || llm_url.contains("anthropic.com"))
{
warn!("External LLM URL configured ({}), but no API key provided. Falling back to local LLM at http://localhost:8081", llm_url);
"http://localhost:8081".to_string()
warn!("External LLM URL configured ({}), but no API key provided. Falling back to local LLM at ", llm_url);
"".to_string()
} else {
llm_url
};
@ -660,7 +660,7 @@ fn init_directory_service() -> Result<(Arc<Mutex<crate::directory::AuthService>>
let base_url = json
.get("base_url")
.and_then(|v| v.as_str())
.unwrap_or("http://localhost:8300");
.unwrap_or("");
let client_id = json.get("client_id").and_then(|v| v.as_str()).unwrap_or("");
let client_secret = json
.get("client_secret")
@ -703,13 +703,13 @@ fn init_directory_service() -> Result<(Arc<Mutex<crate::directory::AuthService>>
#[cfg(feature = "directory")]
fn default_zitadel_config() -> crate::directory::ZitadelConfig {
crate::directory::ZitadelConfig {
issuer_url: "http://localhost:8300".to_string(),
issuer: "http://localhost:8300".to_string(),
issuer_url: "".to_string(),
issuer: "".to_string(),
client_id: String::new(),
client_secret: String::new(),
redirect_uri: "http://localhost:8300/callback".to_string(),
redirect_uri: "/callback".to_string(),
project_id: "default".to_string(),
api_url: "http://localhost:8300".to_string(),
api_url: "".to_string(),
service_account_key: None,
}
}
@ -787,9 +787,9 @@ fn init_llm_provider(
.get_config(
&bot_id,
"embedding-url",
Some("http://localhost:8082"),
Some(""),
)
.unwrap_or_else(|_| "http://localhost:8082".to_string());
.unwrap_or_else(|_| "".to_string());
let embedding_model = config_manager
.get_config(&bot_id, "embedding-model", Some("all-MiniLM-L6-v2"))
.unwrap_or_else(|_| "all-MiniLM-L6-v2".to_string());

View file

@ -161,8 +161,8 @@ async fn get_llm_config(state: &Arc<AppState>, bot_id: Uuid) -> Result<(String,
let config = ConfigManager::new(state.conn.clone());
let llm_url = config
.get_config(&bot_id, "llm-url", Some("http://localhost:8081"))
.unwrap_or_else(|_| "http://localhost:8081".to_string());
.get_config(&bot_id, "llm-url", Some(""))
.unwrap_or_else(|_| "".to_string());
let llm_model = config
.get_config(&bot_id, "llm-model", None)

View file

@ -97,8 +97,8 @@ pub async fn send_campaign_email(
let config = ConfigManager::new(state.conn.clone());
let base_url = config
.get_config(&bot_id, "server-url", Some("http://localhost:3000"))
.unwrap_or_else(|_| "http://localhost:3000".to_string());
.get_config(&bot_id, "server-url", Some(""))
.unwrap_or_else(|_| "".to_string());
let body_html = payload
.body_html

View file

@ -357,7 +357,7 @@ impl Default for ExporterConfig {
fn default() -> Self {
Self {
exporter_type: ExporterType::Otlp,
endpoint: "http://localhost:4317".to_string(),
endpoint: "".to_string(),
headers: HashMap::new(),
batch_size: 512,
flush_interval_ms: 5000,

View file

@ -100,9 +100,9 @@ impl CorsConfig {
pub fn development() -> Self {
Self {
allowed_origins: vec![
"http://localhost:3000".to_string(),
"http://localhost:8080".to_string(),
"http://localhost:9000".to_string(),
"".to_string(),
"".to_string(),
"".to_string(),
"http://127.0.0.1:3000".to_string(),
"http://127.0.0.1:8080".to_string(),
"http://127.0.0.1:9000".to_string(),
@ -502,7 +502,7 @@ mod tests {
fn test_development_config() {
let config = CorsConfig::development();
assert!(!config.allowed_origins.is_empty());
assert!(config.allowed_origins.contains(&"http://localhost:3000".to_string()));
assert!(config.allowed_origins.contains(&"".to_string()));
}
#[test]
@ -542,7 +542,7 @@ mod tests {
#[test]
fn test_valid_origin_format() {
assert!(is_valid_origin_format("https://example.com"));
assert!(is_valid_origin_format("http://localhost:3000"));
assert!(is_valid_origin_format(""));
assert!(is_valid_origin_format("https://api.example.com:8443"));
assert!(!is_valid_origin_format("ftp://example.com"));
@ -557,7 +557,7 @@ mod tests {
.allow_localhost(true);
assert!(validator.is_allowed("https://example.com"));
assert!(validator.is_allowed("http://localhost:3000"));
assert!(validator.is_allowed(""));
assert!(!validator.is_allowed("https://evil.com"));
}
@ -573,7 +573,7 @@ mod tests {
#[test]
fn test_localhost_detection() {
assert!(is_localhost_origin("http://localhost"));
assert!(is_localhost_origin("http://localhost:3000"));
assert!(is_localhost_origin(""));
assert!(is_localhost_origin("https://localhost:8443"));
assert!(is_localhost_origin("http://127.0.0.1"));
assert!(is_localhost_origin("http://127.0.0.1:9000"));
@ -584,7 +584,7 @@ mod tests {
fn test_extract_host() {
assert_eq!(extract_host("https://example.com"), Some("example.com"));
assert_eq!(extract_host("https://example.com:8443"), Some("example.com"));
assert_eq!(extract_host("http://localhost:3000"), Some("localhost"));
assert_eq!(extract_host(""), Some("localhost"));
assert_eq!(extract_host("invalid"), None);
}

View file

@ -30,11 +30,12 @@ pub struct TlsIntegration {
impl TlsIntegration {
pub fn new(tls_enabled: bool) -> Self {
let (qdrant_url, _) = if let Ok(sm) = crate::core::secrets::SecretsManager::from_env() {
sm.get_vectordb_config_sync()
} else {
("http://localhost:6333".to_string(), None)
};
let sm = crate::core::secrets::SecretsManager::get().ok();
let (qdrant_url, _) = sm
.as_ref()
.map(|sm| sm.get_vectordb_config_sync())
.unwrap_or((String::new(), None));
let qdrant_secure = qdrant_url.replace("http://", "https://");
let qdrant_port: u16 = qdrant_url
.split(':')
@ -47,18 +48,13 @@ impl TlsIntegration {
.and_then(|p| p.parse().ok())
.unwrap_or(6334);
let (llm_url, _, _, _, _) = if let Ok(sm) = crate::core::secrets::SecretsManager::from_env()
{
sm.get_llm_config()
} else {
(
"http://localhost:8081".to_string(),
"gpt-4".to_string(),
None,
None,
"http://localhost:11434".to_string(),
)
};
let (llm_url, _, _, _, _) = sm.as_ref().map(|sm| sm.get_llm_config()).unwrap_or((
String::new(),
String::new(),
None,
None,
String::new(),
));
let llm_secure = llm_url.replace("http://", "https://");
let llm_port: u16 = llm_url
.split(':')
@ -71,55 +67,41 @@ impl TlsIntegration {
.and_then(|p| p.parse().ok())
.unwrap_or(8444);
let (cache_host, cache_port, _) =
if let Ok(sm) = crate::core::secrets::SecretsManager::from_env() {
sm.get_cache_config()
} else {
("localhost".to_string(), 6379, None)
};
let (cache_host, cache_port, _) = sm
.as_ref()
.map(|sm| sm.get_cache_config())
.and_then(|r| r.ok())
.unwrap_or((String::new(), 6379, None));
let cache_tls_port = cache_port + 1;
let (db_host, db_port, _, _, _) =
if let Ok(sm) = crate::core::secrets::SecretsManager::from_env() {
sm.get_database_config_sync()
} else {
(
"localhost".to_string(),
5432,
"botserver".to_string(),
"gbuser".to_string(),
"changeme".to_string(),
)
};
let (db_host, db_port, _, _, _) = sm
.as_ref()
.map(|sm| sm.get_database_config_sync())
.and_then(|r| r.ok())
.unwrap_or((
String::new(),
5432,
String::new(),
String::new(),
String::new(),
));
let db_tls_port = db_port + 1;
let (drive_host, _drive_accesskey, _drive_secret) =
if let Ok(sm) = crate::core::secrets::SecretsManager::from_env() {
sm.get_drive_config()
} else {
(
"localhost:9100".to_string(),
"minioadmin".to_string(),
"minioadmin".to_string(),
)
};
let (drive_host, _, _) = sm
.as_ref()
.map(|sm| sm.get_drive_config())
.and_then(|r| r.ok())
.unwrap_or((String::new(), String::new(), String::new()));
let drive_port: u16 = drive_host
.split(':')
.next_back()
.and_then(|p| p.parse().ok())
.unwrap_or(9100);
let (directory_url, _, _, _) =
if let Ok(sm) = crate::core::secrets::SecretsManager::from_env() {
sm.get_directory_config_sync()
} else {
(
"http://localhost:9000".to_string(),
String::new(),
String::new(),
String::new(),
)
};
let (directory_url, _, _, _) = sm
.as_ref()
.map(|sm| sm.get_directory_config_sync())
.unwrap_or((String::new(), String::new(), String::new(), String::new()));
let directory_secure = directory_url.replace("http://", "https://");
let directory_port: u16 = directory_url
.split(':')
@ -137,8 +119,8 @@ impl TlsIntegration {
services.insert(
"api".to_string(),
ServiceUrls {
original: "http://localhost:8080".to_string(),
secure: "https://localhost:8443".to_string(),
original: String::new(),
secure: String::new(),
port: 8080,
tls_port: 8443,
},
@ -157,8 +139,8 @@ impl TlsIntegration {
services.insert(
"embedding".to_string(),
ServiceUrls {
original: "http://localhost:8082".to_string(),
secure: "https://localhost:8445".to_string(),
original: String::new(),
secure: String::new(),
port: 8082,
tls_port: 8445,
},

View file

@ -28,8 +28,8 @@ pub struct ZitadelAuthConfig {
impl Default for ZitadelAuthConfig {
fn default() -> Self {
Self {
issuer_url: "http://localhost:9000".to_string(),
api_url: "http://localhost:9000".to_string(),
issuer_url: "".to_string(),
api_url: "".to_string(),
client_id: String::new(),
client_secret: String::new(),
project_id: String::new(),

View file

@ -26,7 +26,7 @@ pub struct TimeSeriesConfig {
impl Default for TimeSeriesConfig {
fn default() -> Self {
Self {
url: "http://localhost:8086".to_string(),
url: "".to_string(),
token: String::new(),
org: "system".to_string(),
bucket: "metrics".to_string(),

View file

@ -11,7 +11,7 @@ impl EmbeddingGenerator {
}
pub async fn generate_text_embedding(&self, text: &str) -> Result<Vec<f32>> {
let embedding_url = "http://localhost:8082".to_string();
let embedding_url = "".to_string();
match self.generate_local_embedding(text, &embedding_url).await {
Ok(embedding) => Ok(embedding),
Err(e) => {

View file

@ -586,7 +586,7 @@ impl VideoEngine {
};
let botmodels_url =
std::env::var("BOTMODELS_URL").unwrap_or_else(|_| "http://localhost:8001".to_string());
std::env::var("BOTMODELS_URL").unwrap_or_else(|_| "".to_string());
let client = reqwest::Client::new();
let response = client
@ -691,7 +691,7 @@ impl VideoEngine {
output_dir: &str,
) -> Result<TTSResponse, Box<dyn std::error::Error + Send + Sync>> {
let botmodels_url =
std::env::var("BOTMODELS_URL").unwrap_or_else(|_| "http://localhost:8001".to_string());
std::env::var("BOTMODELS_URL").unwrap_or_else(|_| "".to_string());
let client = reqwest::Client::new();
let response = client
@ -1022,7 +1022,7 @@ impl VideoEngine {
let clip = clips.iter().find(|c| c.id == clip_id).ok_or("Clip not found")?;
let botmodels_url =
std::env::var("BOTMODELS_URL").unwrap_or_else(|_| "http://localhost:8001".to_string());
std::env::var("BOTMODELS_URL").unwrap_or_else(|_| "".to_string());
let client = reqwest::Client::new();
let response = client
@ -1056,7 +1056,7 @@ impl VideoEngine {
let clip = clips.iter().find(|c| c.id == req.clip_id).ok_or("Clip not found")?;
let botmodels_url =
std::env::var("BOTMODELS_URL").unwrap_or_else(|_| "http://localhost:8001".to_string());
std::env::var("BOTMODELS_URL").unwrap_or_else(|_| "".to_string());
let client = reqwest::Client::new();
let response = client
@ -1097,7 +1097,7 @@ impl VideoEngine {
let track = tracks.iter().find(|t| t.id == audio_track_id).ok_or("Audio track not found")?;
let botmodels_url =
std::env::var("BOTMODELS_URL").unwrap_or_else(|_| "http://localhost:8001".to_string());
std::env::var("BOTMODELS_URL").unwrap_or_else(|_| "".to_string());
let client = reqwest::Client::new();
let response = client
@ -1143,7 +1143,7 @@ impl VideoEngine {
let track = tracks.iter().find(|t| t.id == audio_track_id).ok_or("Audio track not found")?;
let botmodels_url =
std::env::var("BOTMODELS_URL").unwrap_or_else(|_| "http://localhost:8001".to_string());
std::env::var("BOTMODELS_URL").unwrap_or_else(|_| "".to_string());
let client = reqwest::Client::new();
let response = client

View file

@ -992,26 +992,26 @@ mod tests {
#[test]
fn test_page_url_patterns() {
let login = LoginPage::new("http://localhost:4242");
let login = LoginPage::new("");
assert_eq!(LoginPage::url_pattern(), "/login");
assert_eq!(login.base_url(), "http://localhost:4242");
assert_eq!(login.base_url(), "");
let dashboard = DashboardPage::new("http://localhost:4242");
let dashboard = DashboardPage::new("");
assert_eq!(DashboardPage::url_pattern(), "/dashboard");
assert_eq!(dashboard.base_url(), "http://localhost:4242");
assert_eq!(dashboard.base_url(), "");
let chat = ChatPage::new("http://localhost:4242", "test-bot");
let chat = ChatPage::new("", "test-bot");
assert_eq!(ChatPage::url_pattern(), "/chat/");
assert_eq!(chat.base_url(), "http://localhost:4242");
assert_eq!(chat.base_url(), "");
assert_eq!(chat.bot_name(), "test-bot");
let queue = QueuePage::new("http://localhost:4242");
let queue = QueuePage::new("");
assert_eq!(QueuePage::url_pattern(), "/queue");
assert_eq!(queue.base_url(), "http://localhost:4242");
assert_eq!(queue.base_url(), "");
let bots = BotManagementPage::new("http://localhost:4242");
let bots = BotManagementPage::new("");
assert_eq!(BotManagementPage::url_pattern(), "/admin/bots");
assert_eq!(bots.base_url(), "http://localhost:4242");
assert_eq!(bots.base_url(), "");
}
#[test]