refactor: Replace all hardcoded ./botserver-stack paths with get_stack_path()/get_work_path()
Some checks failed
BotServer CI/CD / build (push) Failing after 1m28s

- 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
This commit is contained in:
Rodrigo Rodriguez (Pragmatismo) 2026-04-04 09:24:44 -03:00
parent c05e40d35b
commit 7d8f141fc2
35 changed files with 201 additions and 145 deletions

View file

@ -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(

View file

@ -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);

View file

@ -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<Arc<AppState>>) -> 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();

View file

@ -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)?;

View file

@ -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) => {

View file

@ -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)?;

View file

@ -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<WizardConfig> {
}
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()
}

View file

@ -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<String>) -> 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()
}

View file

@ -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"]))

View file

@ -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<bool, Box<dyn std::error::Error>> {
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<bool, Box<dyn std::error::Error>> {
.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();

View file

@ -60,10 +60,15 @@ pub fn safe_curl(args: &[&str]) -> Option<std::process::Output> {
/// 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();

View file

@ -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<String> {
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",

View file

@ -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)?;
}

View file

@ -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(),

View file

@ -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<Value> {
json!({
"type": param_type,
"description": param_desc
})
}),
);
}

View file

@ -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));

View file

@ -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<Self, anyhow::Error> {
@ -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()),
})
}
}

View file

@ -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,

View file

@ -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(());

View file

@ -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));

View file

@ -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
},

View file

@ -70,9 +70,9 @@ pub use alm_setup::setup_alm;
pub async fn setup_directory() -> anyhow::Result<crate::core::package_manager::setup::DirectoryConfig> {
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");

View file

@ -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}"))?

View file

@ -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();

View file

@ -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<u64>) -> 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.

View file

@ -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() {

View file

@ -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();

View file

@ -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;

View file

@ -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<AppState>) -> 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"))]

View file

@ -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 <stack_path>/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 {

View file

@ -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")

View file

@ -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<Mutex<crate::directory::AuthService>>
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::<serde_json::Value>(&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) => {

View file

@ -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");

View file

@ -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<HashSet<&'static str>> = 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);

View file

@ -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");