From 7d8f141fc2f96b93c1f3e7518133b744d918bcdc Mon Sep 17 00:00:00 2001 From: "Rodrigo Rodriguez (Pragmatismo)" Date: Sat, 4 Apr 2026 09:24:44 -0300 Subject: [PATCH] refactor: Replace all hardcoded ./botserver-stack paths with get_stack_path()/get_work_path() - Adds get_stack_path() helper: returns /opt/gbo in production (.env without botserver-stack), ./botserver-stack in dev - Adds get_work_path() helper: returns /opt/gbo/work in production, ./botserver-stack/data/system/work in dev - Updated 35+ files to use dynamic path resolution - Production system container no longer needs botserver-stack directory - Work files go to /opt/gbo/work instead of /opt/gbo/bin/botserver-stack --- src/auto_task/designer_ai.rs | 2 +- src/auto_task/intent_classifier.rs | 2 +- src/basic/keywords/app_server.rs | 7 +-- src/basic/keywords/import_export.rs | 5 +- src/basic/keywords/qrcode.rs | 2 +- src/basic/keywords/use_tool.rs | 5 +- src/console/wizard.rs | 4 +- src/core/bootstrap/bootstrap_manager.rs | 7 ++- src/core/bootstrap/bootstrap_utils.rs | 36 +++++++------- src/core/bootstrap/instance.rs | 28 ++++++----- src/core/bootstrap/utils.rs | 49 ++++++++++++++---- src/core/bootstrap/vault.rs | 25 +++++----- src/core/bot/manager.rs | 2 +- src/core/bot/mod.rs | 2 +- src/core/bot/tool_context.rs | 14 ++++-- src/core/bot/tool_executor.rs | 10 ++-- src/core/config/mod.rs | 8 +-- src/core/dns/mod.rs | 2 +- src/core/kb/website_crawler_service.rs | 8 +-- src/core/package_manager/alm_setup.rs | 4 +- src/core/package_manager/installer.rs | 3 +- src/core/package_manager/mod.rs | 4 +- src/core/package_manager/setup.rs | 5 +- src/core/secrets/mod.rs | 8 +-- src/core/shared/utils.rs | 52 +++++++++++++++----- src/designer/designer_api/llm_integration.rs | 2 +- src/directory/auth_routes.rs | 3 +- src/directory/bootstrap.rs | 3 +- src/drive/local_file_monitor.rs | 8 +-- src/llm/local.rs | 4 +- src/main.rs | 2 +- src/main_module/bootstrap.rs | 11 ++--- src/main_module/server.rs | 4 +- src/security/command_guard.rs | 13 ++--- src/security/mod.rs | 2 +- 35 files changed, 201 insertions(+), 145 deletions(-) diff --git a/src/auto_task/designer_ai.rs b/src/auto_task/designer_ai.rs index 0c92002a..65d61e10 100644 --- a/src/auto_task/designer_ai.rs +++ b/src/auto_task/designer_ai.rs @@ -863,7 +863,7 @@ Respond ONLY with valid JSON."# .config .as_ref() .map(|c| c.site_path.clone()) - .unwrap_or_else(|| "./botserver-stack/sites".to_string()) + .unwrap_or_else(|| format!("{}/sites", crate::core::shared::utils::get_stack_path())) } fn read_file( diff --git a/src/auto_task/intent_classifier.rs b/src/auto_task/intent_classifier.rs index 3a1f95d3..1e6f91b9 100644 --- a/src/auto_task/intent_classifier.rs +++ b/src/auto_task/intent_classifier.rs @@ -1103,7 +1103,7 @@ END TRIGGER .config .as_ref() .map(|c| c.site_path.clone()) - .unwrap_or_else(|| "./botserver-stack/sites".to_string()); + .unwrap_or_else(|| format!("{}/sites", crate::core::shared::utils::get_stack_path())); let full_path = format!("{}/{}.gbai/{}", site_path, bot_id, path); diff --git a/src/basic/keywords/app_server.rs b/src/basic/keywords/app_server.rs index 68c71f78..26508975 100644 --- a/src/basic/keywords/app_server.rs +++ b/src/basic/keywords/app_server.rs @@ -1,5 +1,6 @@ use crate::core::shared::get_content_type; use crate::core::shared::state::AppState; +use crate::core::shared::utils::get_stack_path; use axum::{ body::Body, extract::{Path, State}, @@ -101,7 +102,7 @@ pub async fn serve_vendor_file( let local_paths = [ format!("./botui/ui/suite/js/vendor/{}", file_path), format!("../botui/ui/suite/js/vendor/{}", file_path), - format!("./botserver-stack/static/js/vendor/{}", file_path), + format!("{}/static/js/vendor/{}", get_stack_path(), file_path), ]; for local_path in &local_paths { @@ -351,7 +352,7 @@ async fn serve_app_file_internal(state: &AppState, app_name: &str, file_path: &s .config .as_ref() .map(|c| c.site_path.clone()) - .unwrap_or_else(|| "./botserver-stack/sites".to_string()); + .unwrap_or_else(|| format!("{}/sites", get_stack_path())); let full_path = format!( "{}/{}.gbai/{}.gbapp/{}/{}", @@ -403,7 +404,7 @@ pub async fn list_all_apps(State(state): State>) -> impl IntoRespo .config .as_ref() .map(|c| c.site_path.clone()) - .unwrap_or_else(|| "./botserver-stack/sites".to_string()); + .unwrap_or_else(|| format!("{}/sites", get_stack_path())); let mut apps = Vec::new(); diff --git a/src/basic/keywords/import_export.rs b/src/basic/keywords/import_export.rs index 94ed28ea..2360841e 100644 --- a/src/basic/keywords/import_export.rs +++ b/src/basic/keywords/import_export.rs @@ -1,5 +1,6 @@ use crate::core::shared::models::UserSession; use crate::core::shared::state::AppState; +use crate::core::shared::utils::get_work_path; use log::{error, trace}; use rhai::{Array, Dynamic, Engine, Map}; use serde_json::Value; @@ -237,7 +238,7 @@ fn resolve_file_path( .config .as_ref() .map(|c| c.data_dir.as_str()) - .unwrap_or("./botserver-stack/data"); + .unwrap_or(&get_work_path()); let base_path = format!("{}/bots/{}/gbdrive", data_dir, user.bot_id); let full_path = format!("{}/{}", base_path, file_path); @@ -267,7 +268,7 @@ fn resolve_export_path( .config .as_ref() .map(|c| c.data_dir.as_str()) - .unwrap_or("./botserver-stack/data"); + .unwrap_or(&get_work_path()); let base_path = format!("{}/bots/{}/gbdrive", data_dir, user.bot_id); std::fs::create_dir_all(&base_path)?; diff --git a/src/basic/keywords/qrcode.rs b/src/basic/keywords/qrcode.rs index 444a27a3..fc2d3b62 100644 --- a/src/basic/keywords/qrcode.rs +++ b/src/basic/keywords/qrcode.rs @@ -252,7 +252,7 @@ fn execute_qr_code_generation( .config .as_ref() .map(|c| c.data_dir.as_str()) - .unwrap_or("./botserver-stack/data"); + .unwrap_or(&crate::core::shared::utils::get_work_path()); let final_path = match output_path { Some(path) => { diff --git a/src/basic/keywords/use_tool.rs b/src/basic/keywords/use_tool.rs index 1c35e37c..ce6325bc 100644 --- a/src/basic/keywords/use_tool.rs +++ b/src/basic/keywords/use_tool.rs @@ -145,9 +145,8 @@ fn associate_tool_with_session( // Check if tool's .mcp.json file exists in work directory // Use relative path from botserver binary current directory - let gb_dir = std::env::current_dir() - .unwrap_or_else(|_| PathBuf::from(".")) - .join("botserver-stack/data/system"); + let gb_dir = + std::path::PathBuf::from(crate::core::shared::utils::get_stack_path()).join("data/system"); // Get bot name to construct the path let bot_name = get_bot_name_from_id(state, &user.bot_id)?; diff --git a/src/console/wizard.rs b/src/console/wizard.rs index 62cb552d..dbfd2cff 100644 --- a/src/console/wizard.rs +++ b/src/console/wizard.rs @@ -120,7 +120,7 @@ impl Default for WizardConfig { organization: OrgConfig::default(), template: None, install_mode: InstallMode::Development, - data_dir: PathBuf::from("./botserver-stack"), + data_dir: PathBuf::from(crate::core::shared::utils::get_stack_path()), } } } @@ -839,7 +839,7 @@ pub fn load_wizard_config(path: &str) -> io::Result { } pub fn should_run_wizard() -> bool { - !std::path::Path::new("./botserver-stack").exists() + !std::path::Path::new(&crate::core::shared::utils::get_stack_path()).exists() && !std::path::Path::new("/opt/gbo").exists() } diff --git a/src/core/bootstrap/bootstrap_manager.rs b/src/core/bootstrap/bootstrap_manager.rs index 34ab790c..6e729846 100644 --- a/src/core/bootstrap/bootstrap_manager.rs +++ b/src/core/bootstrap/bootstrap_manager.rs @@ -3,6 +3,7 @@ use crate::core::bootstrap::bootstrap_types::{BootstrapManager, BootstrapProgres use crate::core::bootstrap::bootstrap_utils::{alm_ci_health_check, alm_health_check, cache_health_check, drive_health_check, safe_pkill, tables_health_check, vault_health_check, vector_db_health_check, zitadel_health_check}; use crate::core::config::AppConfig; use crate::core::package_manager::{InstallMode, PackageManager}; +use crate::core::shared::utils::get_stack_path; use crate::security::command_guard::SafeCommand; use log::{info, warn}; use std::path::PathBuf; @@ -10,9 +11,7 @@ use tokio::time::{sleep, Duration}; impl BootstrapManager { pub fn new(mode: InstallMode, tenant: Option) -> Self { - let stack_path = std::env::var("BOTSERVER_STACK_PATH") - .map(PathBuf::from) - .unwrap_or_else(|_| PathBuf::from("./botserver-stack")); + let stack_path = PathBuf::from(get_stack_path()); Self { install_mode: mode, @@ -28,7 +27,7 @@ impl BootstrapManager { pub fn vault_bin(&self) -> String { self.stack_dir("bin/vault/vault") .to_str() - .unwrap_or("./botserver-stack/bin/vault/vault") + .unwrap_or(&get_stack_path()) .to_string() } diff --git a/src/core/bootstrap/bootstrap_utils.rs b/src/core/bootstrap/bootstrap_utils.rs index d9e9f7d0..6098f98c 100644 --- a/src/core/bootstrap/bootstrap_utils.rs +++ b/src/core/bootstrap/bootstrap_utils.rs @@ -1,23 +1,25 @@ // Bootstrap utility functions +use crate::core::shared::utils::get_stack_path; use crate::security::command_guard::SafeCommand; use log::{debug, info, warn}; -/// Get list of processes to kill +/// Get list of processes to kill (only used in dev with local botserver-stack) pub fn get_processes_to_kill() -> Vec<(&'static str, Vec<&'static str>)> { + let stack = get_stack_path(); vec![ - ("botserver-stack/bin/vault", vec!["-9", "-f"]), - ("botserver-stack/bin/tables", vec!["-9", "-f"]), - ("botserver-stack/bin/drive", vec!["-9", "-f"]), - ("botserver-stack/bin/cache", vec!["-9", "-f"]), - ("botserver-stack/bin/directory", vec!["-9", "-f"]), - ("botserver-stack/bin/llm", vec!["-9", "-f"]), - ("botserver-stack/bin/email", vec!["-9", "-f"]), - ("botserver-stack/bin/proxy", vec!["-9", "-f"]), - ("botserver-stack/bin/dns", vec!["-9", "-f"]), - ("botserver-stack/bin/meeting", vec!["-9", "-f"]), - ("botserver-stack/bin/vector_db", vec!["-9", "-f"]), - ("botserver-stack/bin/zitadel", vec!["-9", "-f"]), - ("botserver-stack/bin/alm", vec!["-9", "-f"]), + (&format!("{}/bin/vault", stack), vec!["-9", "-f"]), + (&format!("{}/bin/tables", stack), vec!["-9", "-f"]), + (&format!("{}/bin/drive", stack), vec!["-9", "-f"]), + (&format!("{}/bin/cache", stack), vec!["-9", "-f"]), + (&format!("{}/bin/directory", stack), vec!["-9", "-f"]), + (&format!("{}/bin/llm", stack), vec!["-9", "-f"]), + (&format!("{}/bin/email", stack), vec!["-9", "-f"]), + (&format!("{}/bin/proxy", stack), vec!["-9", "-f"]), + (&format!("{}/bin/dns", stack), vec!["-9", "-f"]), + (&format!("{}/bin/meeting", stack), vec!["-9", "-f"]), + (&format!("{}/bin/vector_db", stack), vec!["-9", "-f"]), + (&format!("{}/bin/zitadel", stack), vec!["-9", "-f"]), + (&format!("{}/bin/alm", stack), vec!["-9", "-f"]), ("forgejo", vec!["-9", "-f"]), ("caddy", vec!["-9", "-f"]), ("postgres", vec!["-9", "-f"]), @@ -130,7 +132,8 @@ pub fn cache_health_check() -> bool { } } - if let Ok(output) = SafeCommand::new("./botserver-stack/bin/cache/bin/valkey-cli") + let stack_path = get_stack_path(); + if let Ok(output) = SafeCommand::new(&format!("{}/bin/cache/bin/valkey-cli", stack_path)) .and_then(|c| c.args(&["ping"])) .and_then(|c| c.execute()) { @@ -265,8 +268,7 @@ pub fn tables_health_check() -> bool { return output.status.success(); } - let stack_path = - std::env::var("BOTSERVER_STACK_PATH").unwrap_or_else(|_| "./botserver-stack".to_string()); + let stack_path = get_stack_path(); let pg_isready = format!("{}/bin/tables/bin/pg_isready", stack_path); if let Ok(output) = SafeCommand::new(&pg_isready) .and_then(|c| c.args(&["-h", "127.0.0.1", "-p", "5432"])) diff --git a/src/core/bootstrap/instance.rs b/src/core/bootstrap/instance.rs index 2e2bd2e9..e529da33 100644 --- a/src/core/bootstrap/instance.rs +++ b/src/core/bootstrap/instance.rs @@ -2,6 +2,7 @@ //! //! Extracted from mod.rs +use crate::core::shared::utils::get_stack_path; use crate::security::command_guard::SafeCommand; use log::warn; use std::fs; @@ -9,8 +10,7 @@ use std::path::PathBuf; /// Check if another instance is already running pub fn check_single_instance() -> Result> { - let stack_path = std::env::var("BOTSERVER_STACK_PATH") - .unwrap_or_else(|_| "./botserver-stack".to_string()); + let stack_path = get_stack_path(); let lock_file = PathBuf::from(&stack_path).join(".lock"); if lock_file.exists() { if let Ok(pid_str) = fs::read_to_string(&lock_file) { @@ -22,26 +22,28 @@ pub fn check_single_instance() -> Result> { .and_then(|cmd| cmd.execute().ok()) { if output.status.success() { - warn!("Another botserver process (PID {}) is already running on this stack", pid); - return Ok(false); - } + warn!( + "Another botserver process (PID {}) is already running on this stack", + pid + ); + return Ok(false); } } } } + } - let pid = std::process::id(); - if let Some(parent) = lock_file.parent() { - fs::create_dir_all(parent).ok(); - } - fs::write(&lock_file, pid.to_string()).ok(); - Ok(true) + let pid = std::process::id(); + if let Some(parent) = lock_file.parent() { + fs::create_dir_all(parent).ok(); + } + fs::write(&lock_file, pid.to_string()).ok(); + Ok(true) } /// Release the instance lock pub fn release_instance_lock() { - let stack_path = std::env::var("BOTSERVER_STACK_PATH") - .unwrap_or_else(|_| "./botserver-stack".to_string()); + let stack_path = get_stack_path(); let lock_file = PathBuf::from(&stack_path).join(".lock"); if lock_file.exists() { fs::remove_file(&lock_file).ok(); diff --git a/src/core/bootstrap/utils.rs b/src/core/bootstrap/utils.rs index 8a7e3dfc..e8ba190a 100644 --- a/src/core/bootstrap/utils.rs +++ b/src/core/bootstrap/utils.rs @@ -60,10 +60,15 @@ pub fn safe_curl(args: &[&str]) -> Option { /// Check Vault health status pub fn vault_health_check() -> bool { - let client_cert = - std::path::Path::new("./botserver-stack/conf/system/certificates/botserver/client.crt"); - let client_key = - std::path::Path::new("./botserver-stack/conf/system/certificates/botserver/client.key"); + let stack_path = crate::core::shared::utils::get_stack_path(); + let client_cert = std::path::Path::new(&format!( + "{}/conf/system/certificates/botserver/client.crt", + stack_path + )); + let client_key = std::path::Path::new(&format!( + "{}/conf/system/certificates/botserver/client.key", + stack_path + )); let certs_exist = client_cert.exists() && client_key.exists(); info!("Vault health check: certs_exist={}", certs_exist); @@ -78,9 +83,15 @@ pub fn vault_health_check() -> bool { "-m", "5", "--cert", - "./botserver-stack/conf/system/certificates/botserver/client.crt", + &format!( + "{}/conf/system/certificates/botserver/client.crt", + stack_path + ), "--key", - "./botserver-stack/conf/system/certificates/botserver/client.key", + &format!( + "{}/conf/system/certificates/botserver/client.key", + stack_path + ), "https://localhost:8200/v1/sys/health?standbyok=true&uninitcode=200&sealedcode=200", ]) } else { @@ -138,8 +149,17 @@ pub fn dump_all_component_logs(log_dir: &Path) { error!("========================================================================"); let components = vec![ - "vault", "tables", "drive", "cache", "directory", "llm", - "vector_db", "email", "proxy", "dns", "meeting" + "vault", + "tables", + "drive", + "cache", + "directory", + "llm", + "vector_db", + "email", + "proxy", + "dns", + "meeting", ]; for component in components { @@ -148,12 +168,21 @@ pub fn dump_all_component_logs(log_dir: &Path) { continue; } - let log_files = vec!["stdout.log", "stderr.log", "postgres.log", "vault.log", "minio.log"]; + let log_files = vec![ + "stdout.log", + "stderr.log", + "postgres.log", + "vault.log", + "minio.log", + ]; for log_file in log_files { let log_path = component_log_dir.join(log_file); if log_path.exists() { - error!("-------------------- {} ({}) --------------------", component, log_file); + error!( + "-------------------- {} ({}) --------------------", + component, log_file + ); match fs::read_to_string(&log_path) { Ok(content) => { let lines: Vec<&str> = content.lines().rev().take(30).collect(); diff --git a/src/core/bootstrap/vault.rs b/src/core/bootstrap/vault.rs index 303b93e2..8a5aa5cd 100644 --- a/src/core/bootstrap/vault.rs +++ b/src/core/bootstrap/vault.rs @@ -2,6 +2,7 @@ //! //! Extracted from mod.rs +use crate::core::shared::utils::get_stack_path; use anyhow::Result; use log::info; use std::env; @@ -10,8 +11,7 @@ use std::path::PathBuf; /// Check if stack has been installed pub fn has_installed_stack() -> bool { - let stack_path = env::var("BOTSERVER_STACK_PATH") - .unwrap_or_else(|_| "./botserver-stack".to_string()); + let stack_path = get_stack_path(); let stack_dir = PathBuf::from(&stack_path); if !stack_dir.exists() { return false; @@ -30,14 +30,15 @@ pub fn has_installed_stack() -> bool { pub fn reset_vault_only() -> Result<()> { if has_installed_stack() { log::error!("REFUSING to reset Vault credentials - botserver-stack is installed!"); - log::error!("If you need to re-initialize, manually delete botserver-stack directory first"); - return Err(anyhow::anyhow!( - "Cannot reset Vault - existing installation detected. Manual intervention required." - )); + log::error!( + "If you need to re-initialize, manually delete botserver-stack directory first" + ); + return Err(anyhow::anyhow!( + "Cannot reset Vault - existing installation detected. Manual intervention required." + )); } - let stack_path = env::var("BOTSERVER_STACK_PATH") - .unwrap_or_else(|_| "./botserver-stack".to_string()); + let stack_path = get_stack_path(); let vault_init = PathBuf::from(&stack_path).join("conf/vault/init.json"); let env_file = PathBuf::from("./.env"); @@ -58,10 +59,12 @@ pub fn reset_vault_only() -> Result<()> { pub fn get_db_password_from_vault() -> Option { use crate::core::bootstrap::bootstrap_utils::safe_sh_command; - let vault_addr = env::var("VAULT_ADDR").unwrap_or_else(|_| "https://localhost:8200".to_string()); + let vault_addr = + env::var("VAULT_ADDR").unwrap_or_else(|_| "https://localhost:8200".to_string()); let vault_token = env::var("VAULT_TOKEN").ok()?; - let vault_cacert = env::var("VAULT_CACERT").unwrap_or_else(|_| "./botserver-stack/conf/system/certificates/ca/ca.crt".to_string()); - let vault_bin = format!("{}/bin/vault/vault", env::var("BOTSERVER_STACK_PATH").unwrap_or_else(|_| "./botserver-stack".to_string())); + let vault_cacert = env::var("VAULT_CACERT") + .unwrap_or_else(|_| format!("{}/conf/system/certificates/ca/ca.crt", get_stack_path())); + let vault_bin = format!("{}/bin/vault/vault", get_stack_path()); let cmd = format!( "VAULT_ADDR={} VAULT_TOKEN={} VAULT_CACERT={} {} kv get -field=password secret/gbo/tables 2>/dev/null", diff --git a/src/core/bot/manager.rs b/src/core/bot/manager.rs index 986395e1..532a4e77 100644 --- a/src/core/bot/manager.rs +++ b/src/core/bot/manager.rs @@ -957,7 +957,7 @@ TALK response Err(e) => { warn!("Upload failed (mc not available): {}", e); - let fs_path = format!("./botserver-stack/minio/{}/{}", bucket, path); + let fs_path = format!("{}/minio/{}/{}", crate::core::shared::utils::get_stack_path(), bucket, path); if let Some(parent) = std::path::Path::new(&fs_path).parent() { std::fs::create_dir_all(parent)?; } diff --git a/src/core/bot/mod.rs b/src/core/bot/mod.rs index 93d04b63..40f67bac 100644 --- a/src/core/bot/mod.rs +++ b/src/core/bot/mod.rs @@ -249,7 +249,7 @@ impl BotOrchestrator { .config .as_ref() .map(|c| c.site_path.clone()) - .unwrap_or_else(|| "./botserver-stack/sites".to_string()) + .unwrap_or_else(|| format!("{}/sites", crate::core::shared::utils::get_stack_path())) .into(), "./templates".into(), "../bottemplates".into(), diff --git a/src/core/bot/tool_context.rs b/src/core/bot/tool_context.rs index a4e6b022..2c98fb74 100644 --- a/src/core/bot/tool_context.rs +++ b/src/core/bot/tool_context.rs @@ -37,9 +37,8 @@ pub fn get_session_tools( // Build path to work/{bot_name}.gbai/{bot_name}.gbdialog directory // Use relative path from botserver binary location - let gb_dir = std::env::current_dir() - .unwrap_or_else(|_| PathBuf::from(".")) - .join("botserver-stack/data/system"); + let gb_dir = + std::path::PathBuf::from(crate::core::shared::utils::get_stack_path()).join("data/system"); // Ensure work directory exists (create if not) let work_base = gb_dir.join("work"); @@ -51,7 +50,12 @@ pub fn get_session_tools( let work_path = work_base.join(format!("{}.gbai/{}.gbdialog", bot_name, bot_name)); - info!("Loading {} tools for session {} from {:?}", tool_names.len(), session_id, work_path); + info!( + "Loading {} tools for session {} from {:?}", + tool_names.len(), + session_id, + work_path + ); let mut tools = Vec::new(); @@ -102,7 +106,7 @@ fn format_tool_for_openai(mcp_json: &Value, tool_name: &str) -> Option { json!({ "type": param_type, "description": param_desc - }) + }), ); } diff --git a/src/core/bot/tool_executor.rs b/src/core/bot/tool_executor.rs index 4f333b58..d8d7c54e 100644 --- a/src/core/bot/tool_executor.rs +++ b/src/core/bot/tool_executor.rs @@ -37,9 +37,7 @@ pub struct ToolExecutor; impl ToolExecutor { /// Log tool execution errors to a dedicated log file fn log_tool_error(bot_name: &str, tool_name: &str, error_msg: &str) { - let log_path = std::env::current_dir() - .unwrap_or_else(|_| std::path::PathBuf::from(".")) - .join("botserver-stack/data/system/work") + let log_path = std::path::PathBuf::from(crate::core::shared::utils::get_work_path()) .join(format!("{}_tool_errors.log", bot_name)); // Create work directory if it doesn't exist @@ -353,10 +351,8 @@ impl ToolExecutor { return source_path; } - // Try compiled work directory (botserver-stack/data/system/work relative to current dir) - let work_path = std::env::current_dir() - .unwrap_or_else(|_| PathBuf::from(".")) - .join("botserver-stack/data/system/work") + // Try compiled work directory (work relative to stack path) + let work_path = std::path::PathBuf::from(crate::core::shared::utils::get_work_path()) .join(format!("{}.gbai", bot_name)) .join(format!("{}.gbdialog", bot_name)) .join(format!("{}.bas", tool_name)); diff --git a/src/core/config/mod.rs b/src/core/config/mod.rs index e26480ce..1f0a031e 100644 --- a/src/core/config/mod.rs +++ b/src/core/config/mod.rs @@ -323,10 +323,10 @@ impl AppConfig { ConfigManager::new(pool.clone()).get_config( &Uuid::nil(), "SITES_ROOT", - Some("./botserver-stack/sites"), + Some(&format!("{}/sites", crate::core::shared::utils::get_stack_path())), )? }, - data_dir: get_str("DATA_DIR", "./botserver-stack/data"), + data_dir: get_str("DATA_DIR", &format!("{}/data", crate::core::shared::utils::get_stack_path())), }) } pub fn from_env() -> Result { @@ -371,8 +371,8 @@ impl AppConfig { base_url: "http://localhost:8080".to_string(), }, - site_path: "./botserver-stack/sites".to_string(), - data_dir: "./botserver-stack/data".to_string(), + site_path: format!("{}/sites", crate::core::shared::utils::get_stack_path()), + data_dir: format!("{}/data", crate::core::shared::utils::get_stack_path()), }) } } diff --git a/src/core/dns/mod.rs b/src/core/dns/mod.rs index 589a8ef1..91ddfb02 100644 --- a/src/core/dns/mod.rs +++ b/src/core/dns/mod.rs @@ -34,7 +34,7 @@ impl Default for DnsConfig { fn default() -> Self { Self { enabled: false, - zone_file_path: PathBuf::from("./botserver-stack/conf/dns/botserver.local.zone"), + zone_file_path: PathBuf::from(&format!("{}/conf/dns/botserver.local.zone", crate::core::shared::utils::get_stack_path())), domain: "botserver.local".to_string(), max_entries_per_ip: 5, ttl_seconds: 60, diff --git a/src/core/kb/website_crawler_service.rs b/src/core/kb/website_crawler_service.rs index b58026d3..ce06795d 100644 --- a/src/core/kb/website_crawler_service.rs +++ b/src/core/kb/website_crawler_service.rs @@ -230,9 +230,7 @@ impl WebsiteCrawlerService { let kb_name = format!("website_{}", sanitize_url_for_kb(&website.url)); - let work_path = std::env::current_dir() - .unwrap_or_else(|_| std::path::PathBuf::from(".")) - .join("botserver-stack/data/system/work") + let work_path = std::path::PathBuf::from(crate::core::shared::utils::get_work_path()) .join(&bot_name) .join(format!("{}.gbkb", bot_name)) .join(&kb_name); @@ -344,9 +342,7 @@ impl WebsiteCrawlerService { trace!("Scanning .bas files for USE WEBSITE commands"); // Use the correct work directory path instead of plain "work" - let work_dir = std::env::current_dir() - .unwrap_or_else(|_| std::path::PathBuf::from(".")) - .join("botserver-stack/data/system/work"); + let work_dir = std::path::PathBuf::from(crate::core::shared::utils::get_work_path()); if !work_dir.exists() { return Ok(()); diff --git a/src/core/package_manager/alm_setup.rs b/src/core/package_manager/alm_setup.rs index 12510d9e..78d14616 100644 --- a/src/core/package_manager/alm_setup.rs +++ b/src/core/package_manager/alm_setup.rs @@ -2,10 +2,10 @@ use std::collections::HashMap; use std::path::PathBuf; use log::{info, warn}; use super::generate_random_string; +use crate::core::shared::utils::get_stack_path; pub async fn setup_alm() -> anyhow::Result<()> { - let stack_path_raw = std::env::var("BOTSERVER_STACK_PATH") - .unwrap_or_else(|_| "./botserver-stack".to_string()); + let stack_path_raw = get_stack_path(); let stack_path = std::fs::canonicalize(&stack_path_raw) .unwrap_or_else(|_| PathBuf::from(&stack_path_raw)); diff --git a/src/core/package_manager/installer.rs b/src/core/package_manager/installer.rs index 303463bf..b654c649 100644 --- a/src/core/package_manager/installer.rs +++ b/src/core/package_manager/installer.rs @@ -1,6 +1,7 @@ use crate::core::package_manager::component::ComponentConfig; use crate::core::package_manager::os::detect_os; use crate::core::package_manager::{InstallMode, OsType}; +use crate::core::shared::utils::get_stack_path; use crate::security::command_guard::SafeCommand; use anyhow::{Context, Result}; use log::{error, info, trace, warn}; @@ -1011,7 +1012,7 @@ EOF"#.to_string(), ); env.insert( "VAULT_CACERT".to_string(), - "./botserver-stack/conf/system/certificates/ca/ca.crt".to_string(), + format!("{}/conf/system/certificates/ca/ca.crt", get_stack_path()), ); env }, diff --git a/src/core/package_manager/mod.rs b/src/core/package_manager/mod.rs index daaf9988..aff8a453 100644 --- a/src/core/package_manager/mod.rs +++ b/src/core/package_manager/mod.rs @@ -70,9 +70,9 @@ pub use alm_setup::setup_alm; pub async fn setup_directory() -> anyhow::Result { use std::path::PathBuf; use std::collections::HashMap; + use crate::core::shared::utils::get_stack_path; - let stack_path = std::env::var("BOTSERVER_STACK_PATH") - .unwrap_or_else(|_| "./botserver-stack".to_string()); + let stack_path = get_stack_path(); let base_url = "http://localhost:8300".to_string(); let config_path = PathBuf::from(&stack_path).join("conf/system/directory_config.json"); diff --git a/src/core/package_manager/setup.rs b/src/core/package_manager/setup.rs index b24727bd..f844ec77 100644 --- a/src/core/package_manager/setup.rs +++ b/src/core/package_manager/setup.rs @@ -115,8 +115,7 @@ impl DirectorySetup { /// The steps YAML configures FirstInstance.Org.PatPath which tells Zitadel to /// create a machine user with IAM_OWNER role and write its PAT to disk fn load_pat_token(&mut self) -> Result<()> { - let stack_path = std::env::var("BOTSERVER_STACK_PATH") - .unwrap_or_else(|_| "./botserver-stack".to_string()); + let stack_path = crate::core::shared::utils::get_stack_path(); let pat_path = PathBuf::from(&stack_path).join("conf/directory/admin-pat.txt"); @@ -139,7 +138,7 @@ impl DirectorySetup { } // Also check the legacy location - let legacy_pat_path = std::path::Path::new("./botserver-stack/conf/directory/admin-pat.txt"); + let legacy_pat_path = std::path::Path::new(&format!("{}/conf/directory/admin-pat.txt", crate::core::shared::utils::get_stack_path())); if legacy_pat_path.exists() { let pat_token = std::fs::read_to_string(legacy_pat_path) .map_err(|e| anyhow::anyhow!("Failed to read PAT file: {e}"))? diff --git a/src/core/secrets/mod.rs b/src/core/secrets/mod.rs index d420cc7b..052ce135 100644 --- a/src/core/secrets/mod.rs +++ b/src/core/secrets/mod.rs @@ -1,3 +1,4 @@ +use crate::core::shared::utils::get_stack_path; use anyhow::{anyhow, Result}; use diesel::PgConnection; use log::{debug, info, warn}; @@ -89,13 +90,14 @@ impl SecretsManager { .and_then(|v| v.parse().ok()) .unwrap_or(300); + let stack_path = get_stack_path(); let ca_cert = env::var("VAULT_CACERT") - .unwrap_or_else(|_| "./botserver-stack/conf/system/certificates/ca/ca.crt".to_string()); + .unwrap_or_else(|_| format!("{}/conf/system/certificates/ca/ca.crt", stack_path)); let client_cert = env::var("VAULT_CLIENT_CERT").unwrap_or_else(|_| { - "./botserver-stack/conf/system/certificates/botserver/client.crt".to_string() + format!("{}/conf/system/certificates/botserver/client.crt", stack_path) }); let client_key = env::var("VAULT_CLIENT_KEY").unwrap_or_else(|_| { - "./botserver-stack/conf/system/certificates/botserver/client.key".to_string() + format!("{}/conf/system/certificates/botserver/client.key", stack_path) }); let enabled = !token.is_empty() && !addr.is_empty(); diff --git a/src/core/shared/utils.rs b/src/core/shared/utils.rs index 36326fcf..bdd50e3b 100644 --- a/src/core/shared/utils.rs +++ b/src/core/shared/utils.rs @@ -96,14 +96,40 @@ pub fn get_work_path() -> String { .build(); let result = match rt { Ok(rt) => rt.block_on(sm.get_value("gbo/app", "work_path")) - .unwrap_or_else(|_| "./work".to_string()), - Err(_) => "./work".to_string(), + .unwrap_or_else(|_| get_work_path_default()), + Err(_) => get_work_path_default(), }; let _ = tx.send(result); }); - rx.recv().unwrap_or_else(|_| "./work".to_string()) + rx.recv().unwrap_or_else(|_| get_work_path_default()) } else { - "./work".to_string() + get_work_path_default() + } +} + +/// Returns the work directory path. +/// In production (system container with .env but no botserver-stack): /opt/gbo/work +/// In development (with botserver-stack directory): ./botserver-stack/data/system/work +fn get_work_path_default() -> String { + let has_stack = std::path::Path::new("./botserver-stack").exists(); + let has_env = std::path::Path::new("./.env").exists(); + if has_env && !has_stack { + "/opt/gbo/work".to_string() + } else { + "./botserver-stack/data/system/work".to_string() + } +} + +/// Returns the stack base path. +/// In production (system container with .env but no botserver-stack): /opt/gbo +/// In development (with botserver-stack directory): ./botserver-stack +pub fn get_stack_path() -> String { + let has_stack = std::path::Path::new("./botserver-stack").exists(); + let has_env = std::path::Path::new("./.env").exists(); + if has_env && !has_stack { + "/opt/gbo".to_string() + } else { + "./botserver-stack".to_string() } } @@ -144,12 +170,13 @@ pub async fn create_s3_operator( }; // Set CA cert for self-signed TLS (dev stack) - if std::path::Path::new(CA_CERT_PATH).exists() { - std::env::set_var("AWS_CA_BUNDLE", CA_CERT_PATH); - std::env::set_var("SSL_CERT_FILE", CA_CERT_PATH); + let ca_cert = ca_cert_path(); + if std::path::Path::new(&ca_cert).exists() { + std::env::set_var("AWS_CA_BUNDLE", &ca_cert); + std::env::set_var("SSL_CERT_FILE", &ca_cert); debug!( "Set AWS_CA_BUNDLE and SSL_CERT_FILE to {} for S3 client", - CA_CERT_PATH + ca_cert ); } @@ -445,8 +472,11 @@ pub fn sanitize_sql_value(value: &str) -> String { value.replace('\'', "''") } -/// Default path to the local CA certificate used for internal service TLS (dev stack) -pub const CA_CERT_PATH: &str = "./botserver-stack/conf/system/certificates/ca/ca.crt"; +/// Returns the path to the local CA certificate used for internal service TLS (dev stack). +/// In production this path may not exist, which is fine — the system CA store is used instead. +pub fn ca_cert_path() -> String { + format!("{}/conf/system/certificates/ca/ca.crt", get_stack_path()) +} /// Creates an HTTP client with proper TLS verification. /// @@ -460,7 +490,7 @@ pub const CA_CERT_PATH: &str = "./botserver-stack/conf/system/certificates/ca/ca /// # Returns /// A reqwest::Client configured for TLS verification pub fn create_tls_client(timeout_secs: Option) -> Client { - create_tls_client_with_ca(CA_CERT_PATH, timeout_secs) + create_tls_client_with_ca(&ca_cert_path(), timeout_secs) } /// Creates an HTTP client with a custom CA certificate path. diff --git a/src/designer/designer_api/llm_integration.rs b/src/designer/designer_api/llm_integration.rs index 98d8ede0..336035d3 100644 --- a/src/designer/designer_api/llm_integration.rs +++ b/src/designer/designer_api/llm_integration.rs @@ -417,7 +417,7 @@ pub async fn apply_file_change( .config .as_ref() .map(|c| c.site_path.clone()) - .unwrap_or_else(|| "./botserver-stack/sites".to_string()); + .unwrap_or_else(|| format!("{}/sites", crate::core::shared::utils::get_stack_path())); let local_path = format!("{site_path}/{}.gbai/{}.gbapp/{app_name}/{file_name}", sanitized_name, sanitized_name); if let Some(parent) = std::path::Path::new(&local_path).parent() { diff --git a/src/directory/auth_routes.rs b/src/directory/auth_routes.rs index e6feca18..ffbf80a9 100644 --- a/src/directory/auth_routes.rs +++ b/src/directory/auth_routes.rs @@ -8,6 +8,7 @@ use axum::{ use log::{error, info, warn}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; +use crate::core::shared::utils::get_stack_path; use std::sync::Arc; use tokio::sync::RwLock; use once_cell::sync::Lazy; @@ -148,7 +149,7 @@ pub async fn login( })?; // Try to get admin token: first PAT file, then OAuth client credentials - let pat_path = std::path::Path::new("./botserver-stack/conf/directory/admin-pat.txt"); + let pat_path = std::path::Path::new(&format!("{}/conf/directory/admin-pat.txt", get_stack_path())); let admin_token = std::fs::read_to_string(pat_path) .map(|s| s.trim().to_string()) .unwrap_or_default(); diff --git a/src/directory/bootstrap.rs b/src/directory/bootstrap.rs index e01fe337..3fb6bae9 100644 --- a/src/directory/bootstrap.rs +++ b/src/directory/bootstrap.rs @@ -3,6 +3,7 @@ use log::{error, info, warn}; use rand::Rng; use std::fs; use std::os::unix::fs::PermissionsExt; +use crate::core::shared::utils::get_stack_path; use uuid::Uuid; use super::client::ZitadelClient; @@ -299,7 +300,7 @@ fn save_setup_credentials(result: &BootstrapResult) { fn save_admin_pat_token(pat_token: &str) { // Create directory if it doesn't exist - let pat_dir = std::path::Path::new("./botserver-stack/conf/directory"); + let pat_dir = std::path::Path::new(&format!("{}/conf/directory", get_stack_path())); if let Err(e) = fs::create_dir_all(pat_dir) { error!("Failed to create PAT directory: {}", e); return; diff --git a/src/drive/local_file_monitor.rs b/src/drive/local_file_monitor.rs index 260bf417..4dc21e34 100644 --- a/src/drive/local_file_monitor.rs +++ b/src/drive/local_file_monitor.rs @@ -1,6 +1,7 @@ use crate::basic::compiler::BasicCompiler; use crate::core::kb::{EmbeddingConfig, KnowledgeBaseManager}; use crate::core::shared::state::AppState; +use crate::core::shared::utils::get_work_path; use diesel::prelude::*; use log::{debug, error, info, trace, warn}; use std::collections::HashMap; @@ -43,12 +44,7 @@ pub struct LocalFileMonitor { impl LocalFileMonitor { pub fn new(state: Arc) -> Self { - // Use botserver-stack/data/system/work as the work directory - let work_root = std::env::current_dir() - .unwrap_or_else(|_| PathBuf::from(".")) - .join("botserver-stack/data/system/work"); - - // Use /opt/gbo/data as the base directory for source files + let work_root = PathBuf::from(get_work_path()); let data_dir = PathBuf::from("/opt/gbo/data"); #[cfg(any(feature = "research", feature = "llm"))] diff --git a/src/llm/local.rs b/src/llm/local.rs index e74520d9..e6ec3bc4 100644 --- a/src/llm/local.rs +++ b/src/llm/local.rs @@ -73,7 +73,7 @@ pub async fn ensure_llama_servers_running( // Use default models when config is empty (no default.gbai/config.csv) let llm_server_path = if llm_server_path.is_empty() { - "./botserver-stack/bin/llm/build/bin".to_string() + format!("{}/bin/llm/build/bin", crate::core::shared::utils::get_stack_path()) } else { llm_server_path }; @@ -94,7 +94,7 @@ pub async fn ensure_llama_servers_running( // For llama-server startup, use path relative to botserver root // The models are in /data/llm/ and the llama-server runs from botserver root - let stack_path = std::env::var("BOTSERVER_STACK_PATH").unwrap_or_else(|_| "./botserver-stack".to_string()); + let stack_path = crate::core::shared::utils::get_stack_path(); let llm_model_path = format!("{stack_path}/data/llm/{}", llm_model); let embedding_model_path = format!("{stack_path}/data/llm/{}", embedding_model); if !llm_server_enabled { diff --git a/src/main.rs b/src/main.rs index 8c8f5e25..9b4c2d13 100644 --- a/src/main.rs +++ b/src/main.rs @@ -206,7 +206,7 @@ async fn main() -> std::io::Result<()> { dotenvy::dotenv().ok(); let env_path_early = std::path::Path::new("./.env"); - let vault_init_path_early = std::path::Path::new("./botserver-stack/conf/vault/init.json"); + let vault_init_path_early = std::path::Path::new(&format!("{}/conf/vault/init.json", crate::core::shared::utils::get_stack_path())); let vault_addr = std::env::var("VAULT_ADDR").unwrap_or_default(); let is_remote_vault = !vault_addr.is_empty() && !vault_addr.contains("localhost") diff --git a/src/main_module/bootstrap.rs b/src/main_module/bootstrap.rs index 9b631dd4..99f11e42 100644 --- a/src/main_module/bootstrap.rs +++ b/src/main_module/bootstrap.rs @@ -13,8 +13,7 @@ use crate::core::config::ConfigManager; use crate::core::package_manager::InstallMode; use crate::core::session::SessionManager; use crate::core::shared::state::AppState; -use crate::core::shared::utils::create_conn; -use crate::core::shared::utils::create_s3_operator; +use crate::core::shared::utils::{create_conn, create_s3_operator, get_stack_path}; use super::BootstrapProgress; @@ -88,8 +87,7 @@ pub async fn run_bootstrap( trace!("Creating BootstrapManager..."); let mut bootstrap = BootstrapManager::new(install_mode, tenant); - let stack_path = std::env::var("BOTSERVER_STACK_PATH") - .unwrap_or_else(|_| "./botserver-stack".to_string()); + let stack_path = get_stack_path(); let env_path = std::path::Path::new("./.env"); let stack_env_path_str = format!("{}/.env", stack_path); let vault_init_path_str = format!("{}/conf/vault/init.json", stack_path); @@ -652,8 +650,7 @@ fn init_directory_service() -> Result<(Arc> let zitadel_config = { // Try to load from directory_config.json first // Use same path as DirectorySetup saves to (BOTSERVER_STACK_PATH/conf/system/directory_config.json) - let stack_path = std::env::var("BOTSERVER_STACK_PATH") - .unwrap_or_else(|_| "./botserver-stack".to_string()); + let stack_path = get_stack_path(); let config_path = format!("{}/conf/system/directory_config.json", stack_path); if let Ok(content) = std::fs::read_to_string(&config_path) { if let Ok(json) = serde_json::from_str::(&content) { @@ -718,7 +715,7 @@ fn default_zitadel_config() -> crate::directory::ZitadelConfig { async fn bootstrap_directory_admin(zitadel_config: &crate::directory::ZitadelConfig) { use crate::directory::{bootstrap, ZitadelClient}; - let pat_path = std::path::Path::new("./botserver-stack/conf/directory/admin-pat.txt"); + let pat_path = std::path::Path::new(&format!("{}/conf/directory/admin-pat.txt", get_stack_path())); let bootstrap_client = if pat_path.exists() { match std::fs::read_to_string(pat_path) { Ok(pat_token) => { diff --git a/src/main_module/server.rs b/src/main_module/server.rs index 084c06a0..b2a6a9c5 100644 --- a/src/main_module/server.rs +++ b/src/main_module/server.rs @@ -402,7 +402,7 @@ pub async fn run_axum_server( .config .as_ref() .map(|c| c.site_path.clone()) - .unwrap_or_else(|| "./botserver-stack/sites".to_string()); + .unwrap_or_else(|| format!("{}/sites", crate::core::shared::utils::get_stack_path())); info!("Serving apps from: {}", site_path); @@ -522,7 +522,7 @@ pub async fn run_axum_server( .layer(cors) .layer(TraceLayer::new_for_http()); - let cert_dir = std::path::Path::new("./botserver-stack/conf/system/certificates"); + let cert_dir = std::path::Path::new(&format!("{}/conf/system/certificates", crate::core::shared::utils::get_stack_path())); let cert_path = cert_dir.join("api/server.crt"); let key_path = cert_dir.join("api/server.key"); diff --git a/src/security/command_guard.rs b/src/security/command_guard.rs index 14d8b51a..e0e941c0 100644 --- a/src/security/command_guard.rs +++ b/src/security/command_guard.rs @@ -3,6 +3,7 @@ use std::collections::HashSet; use std::path::PathBuf; use std::process::{Child, Output}; use std::sync::LazyLock; +use crate::core::shared::utils::get_stack_path; static ALLOWED_COMMANDS: LazyLock> = LazyLock::new(|| { HashSet::from([ @@ -336,8 +337,7 @@ impl SafeCommand { ]; // Add botserver-stack/bin/shared to PATH if it exists - let stack_path = std::env::var("BOTSERVER_STACK_PATH") - .unwrap_or_else(|_| "./botserver-stack".to_string()); + let stack_path = get_stack_path(); let shared_bin = format!("{}/bin/shared", stack_path); if std::path::Path::new(&shared_bin).exists() { path_entries.insert(0, shared_bin); @@ -390,8 +390,7 @@ impl SafeCommand { ]; // Add botserver-stack/bin/shared to PATH if it exists - let stack_path = std::env::var("BOTSERVER_STACK_PATH") - .unwrap_or_else(|_| "./botserver-stack".to_string()); + let stack_path = get_stack_path(); let shared_bin = format!("{}/bin/shared", stack_path); if std::path::Path::new(&shared_bin).exists() { path_entries.insert(0, shared_bin); @@ -452,8 +451,7 @@ impl SafeCommand { ]; // Add botserver-stack/bin/shared to PATH if it exists - let stack_path = std::env::var("BOTSERVER_STACK_PATH") - .unwrap_or_else(|_| "./botserver-stack".to_string()); + let stack_path = get_stack_path(); let shared_bin = format!("{}/bin/shared", stack_path); if std::path::Path::new(&shared_bin).exists() { path_entries.insert(0, shared_bin); @@ -506,8 +504,7 @@ impl SafeCommand { ]; // Add botserver-stack/bin/shared to PATH if it exists - let stack_path = std::env::var("BOTSERVER_STACK_PATH") - .unwrap_or_else(|_| "./botserver-stack".to_string()); + let stack_path = get_stack_path(); let shared_bin = format!("{}/bin/shared", stack_path); if std::path::Path::new(&shared_bin).exists() { path_entries.insert(0, shared_bin); diff --git a/src/security/mod.rs b/src/security/mod.rs index 1747d9e4..af4e7d6c 100644 --- a/src/security/mod.rs +++ b/src/security/mod.rs @@ -287,7 +287,7 @@ impl SecurityManager { if let Some(ref manager) = self.mtls_manager { info!("Initializing mTLS for all services"); - let base_path = PathBuf::from("./botserver-stack/conf/system"); + let base_path = PathBuf::from(&format!("{}/conf/system", crate::core::shared::utils::get_stack_path())); let ca_path = base_path.join("ca/ca.crt"); let cert_path = base_path.join("certs/api.crt");