diff --git a/Cargo.toml b/Cargo.toml index a38be01c..91df33bc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -90,7 +90,7 @@ urlencoding = "2.1" uuid = { version = "1.11", features = ["serde", "v4"] } zip = "2.2" time = "0.3.44" -aws-sdk-s3 = "1.108.0" +aws-sdk-s3 = { version = "1.108.0", features = ["behavior-version-latest"] } headless_chrome = { version = "1.0.18", optional = true } rand = "0.9.2" pdf-extract = "0.10.0" diff --git a/src/bootstrap/mod.rs b/src/bootstrap/mod.rs index 9226368d..4378a1a9 100644 --- a/src/bootstrap/mod.rs +++ b/src/bootstrap/mod.rs @@ -11,6 +11,13 @@ use diesel::RunQueryDsl; use rand::distr::Alphanumeric; use sha2::{Digest, Sha256}; use std::path::Path; +use std::process::Command; +use std::io::{self, Write}; + +pub struct ComponentInfo { + pub name: &'static str, + pub termination_command: &'static str, +} pub struct BootstrapManager { pub install_mode: InstallMode, @@ -33,42 +40,40 @@ impl BootstrapManager { pub fn start_all(&mut self) -> Result<()> { let pm = PackageManager::new(self.install_mode.clone(), self.tenant.clone())?; let components = vec![ - "tables", - "cache", - "drive", - "llm", - "email", - "proxy", - "directory", - "alm", - "alm_ci", - "dns", - "webmail", - "meeting", - "table_editor", - "doc_editor", - "desktop", - "devtools", - "bot", - "system", - "vector_db", - "host", + ComponentInfo { name: "tables", termination_command: "pg_ctl" }, + ComponentInfo { name: "cache", termination_command: "valkey-server" }, + ComponentInfo { name: "drive", termination_command: "minio" }, + ComponentInfo { name: "llm", termination_command: "llama-server" }, + ComponentInfo { name: "email", termination_command: "stalwart" }, + ComponentInfo { name: "proxy", termination_command: "caddy" }, + ComponentInfo { name: "directory", termination_command: "zitadel" }, + ComponentInfo { name: "alm", termination_command: "forgejo" }, + ComponentInfo { name: "alm_ci", termination_command: "forgejo-runner" }, + ComponentInfo { name: "dns", termination_command: "coredns" }, + ComponentInfo { name: "webmail", termination_command: "php" }, + ComponentInfo { name: "meeting", termination_command: "livekit-server" }, + ComponentInfo { name: "table_editor", termination_command: "nocodb" }, + ComponentInfo { name: "doc_editor", termination_command: "coolwsd" }, + ComponentInfo { name: "desktop", termination_command: "xrdp" }, + ComponentInfo { name: "devtools", termination_command: "" }, + ComponentInfo { name: "bot", termination_command: "" }, + ComponentInfo { name: "system", termination_command: "" }, + ComponentInfo { name: "vector_db", termination_command: "qdrant" }, + ComponentInfo { name: "host", termination_command: "" }, ]; for component in components { - if pm.is_installed(component) { - trace!("Starting component: {}", component); - pm.start(component)?; + if pm.is_installed(component.name) { + + trace!("Starting component: {}", component.name); + pm.start(component.name)?; } else { - trace!("Component {} not installed, skipping start", component); - // After installing a component, update the default bot configuration - // This is a placeholder for the logic that will write config.csv to the - // default.gbai bucket and upsert into the bot_config table. - // The actual implementation will use the AppState's S3 client to upload - // the updated CSV after each component installation. - // Now perform the actual update: - if let Err(e) = self.update_bot_config(component) { - error!("Failed to update bot config after installing {}: {}", component, e); + + + + trace!("Component {} not installed, skipping start", component.name); + if let Err(e) = self.update_bot_config(component.name) { + error!("Failed to update bot config after installing {}: {}", component.name, e); } } } @@ -127,7 +132,47 @@ Ok(()) for component in required_components { if !pm.is_installed(component) { + + // Determine termination command from package manager component config + let termination_cmd = pm.components.get(component) + .and_then(|cfg| cfg.binary_name.clone()) + .unwrap_or_else(|| component.to_string()); + + // If a termination command is defined, check for leftover running process + if !termination_cmd.is_empty() { + let check = Command::new("pgrep") + .arg("-f") + .arg(&termination_cmd) + .output(); + + if let Ok(output) = check { + if !output.stdout.is_empty() { + println!("Component '{}' appears to be already running from a previous install.", component); + println!("Do you want to terminate it? (y/n)"); + let mut input = String::new(); + io::stdout().flush().unwrap(); + io::stdin().read_line(&mut input).unwrap(); + if input.trim().eq_ignore_ascii_case("y") { + let _ = Command::new("pkill") + .arg("-f") + .arg(&termination_cmd) + .status(); + println!("Terminated existing '{}' process.", component); + } else { + println!("Skipping start of '{}' as it is already running.", component); + continue; + } + } + } + } + + + if component == "tables" { + + + + let db_password = self.generate_secure_password(16); let farm_password = self.generate_secure_password(32); diff --git a/src/main.rs b/src/main.rs index a494ea64..783025f3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -92,7 +92,9 @@ async fn main() -> std::io::Result<()> { } dotenv().ok(); - env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init(); + env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")) + .write_style(env_logger::WriteStyle::Always) + .init(); info!("Starting BotServer bootstrap process"); diff --git a/src/package_manager/installer.rs b/src/package_manager/installer.rs index a86feee5..98981456 100644 --- a/src/package_manager/installer.rs +++ b/src/package_manager/installer.rs @@ -160,7 +160,7 @@ env_vars: HashMap::from([ macos_packages: vec![], windows_packages: vec![], download_url: Some("https://github.com/theseus-rs/postgresql-binaries/releases/download/18.0.0/postgresql-18.0.0-x86_64-unknown-linux-gnu.tar.gz".to_string()), - binary_name: None, + binary_name: Some("postgres".to_string()), pre_install_cmds_linux: vec![], post_install_cmds_linux: vec![ "chmod +x ./bin/*".to_string(), @@ -216,13 +216,16 @@ self.components.insert( download_url: Some("https://download.valkey.io/releases/valkey-9.0.0-jammy-x86_64.tar.gz".to_string()), binary_name: Some("valkey-server".to_string()), pre_install_cmds_linux: vec![], - post_install_cmds_linux: vec![], + post_install_cmds_linux: vec![ + "tar -xzf {{BIN_PATH}}/valkey-9.0.0-jammy-x86_64.tar.gz -C {{BIN_PATH}}".to_string(), + "mv {{BIN_PATH}}/valkey-9.0.0-jammy-x86_64/valkey-server {{BIN_PATH}}/valkey-server".to_string(), +], pre_install_cmds_macos: vec![], post_install_cmds_macos: vec![], pre_install_cmds_windows: vec![], post_install_cmds_windows: vec![], env_vars: HashMap::new(), - exec_cmd: "./valkey-server --port 6379 --dir {{DATA_PATH}}".to_string(), + exec_cmd: "{{BIN_PATH}}/valkey-server --port 6379 --dir {{DATA_PATH}}".to_string(), }, ); }