diff --git a/src/bootstrap/mod.rs b/src/bootstrap/mod.rs index 1bb8033c..f37244ae 100644 --- a/src/bootstrap/mod.rs +++ b/src/bootstrap/mod.rs @@ -6,6 +6,7 @@ use aws_config::BehaviorVersion; use aws_sdk_s3::Client; use diesel::connection::SimpleConnection; use log::{error, info, trace}; +use rand::distr::Alphanumeric; use std::io::{self, Write}; use std::path::Path; use std::process::Command; @@ -17,35 +18,12 @@ pub struct BootstrapManager { pub tenant: Option, } impl BootstrapManager { - fn is_postgres_running() -> bool { - match Command::new("pg_isready").arg("-q").status() { - Ok(status) => status.success(), - Err(_) => Command::new("pgrep") - .arg("postgres") - .output() - .map(|o| !o.stdout.is_empty()) - .unwrap_or(false), - } - } pub async fn new(install_mode: InstallMode, tenant: Option) -> Self { trace!( "Initializing BootstrapManager with mode {:?} and tenant {:?}", install_mode, tenant ); - if !Self::is_postgres_running() { - let pm = PackageManager::new(install_mode.clone(), tenant.clone()) - .expect("Failed to initialize PackageManager"); - if let Err(e) = pm.start("tables") { - error!( - "Failed to start Tables server component automatically: {}", - e - ); - panic!("Database not available and auto-start failed."); - } else { - trace!("Tables server started successfully"); - } - } Self { install_mode, tenant, @@ -84,6 +62,18 @@ impl BootstrapManager { } Ok(()) } + + + fn generate_secure_password(&self, length: usize) -> String { + let mut rng = rand::rng(); + (0..length) + .map(|_| { + let byte = rand::Rng::sample(&mut rng, Alphanumeric); + char::from(byte) + }) + .collect() + } + pub async fn bootstrap(&mut self) { if let Ok(tables_server) = std::env::var("TABLES_SERVER") { if !tables_server.is_empty() { @@ -116,7 +106,30 @@ impl BootstrapManager { } } } + } else { + let db_env_path = std::env::current_dir().unwrap().join(".env"); + let db_password = self.generate_secure_password(32); + let database_url = std::env::var("DATABASE_URL").unwrap_or_else(|_| { + format!("postgres://gbuser:{}@localhost:5432/botserver", db_password) + }); + let db_line = format!("DATABASE_URL={}\n", database_url); + let drive_password = self.generate_secure_password(16); + let drive_user = "gbdriveuser".to_string(); + + let env_path = std::env::current_dir().unwrap().join(".env"); + let env_content = format!( + "\nDRIVE_SERVER=http://localhost:9000\nDRIVE_ACCESSKEY={}\nDRIVE_SECRET={}\n", + drive_user, drive_password + ); + let _ = std::fs::OpenOptions::new() + .create(true) + .append(true) + .open(&env_path) + .and_then(|mut file| std::io::Write::write_all(&mut file, env_content.as_bytes())); + + let _ = std::fs::write(&db_env_path, db_line); } + let pm = PackageManager::new(self.install_mode.clone(), self.tenant.clone()).unwrap(); let required_components = vec!["tables", "drive", "cache", "llm"]; for component in required_components { @@ -190,8 +203,8 @@ impl BootstrapManager { } } } - - async fn create_s3_operator(config: &AppConfig) -> Client { + + async fn get_drive_client(config: &AppConfig) -> Client { let endpoint = if !config.drive.server.ends_with('/') { format!("{}/", config.drive.server) } else { @@ -222,7 +235,7 @@ impl BootstrapManager { if !templates_dir.exists() { return Ok(()); } - let client = Self::create_s3_operator(_config).await; + let client = Self::get_drive_client(_config).await; let mut read_dir = tokio::fs::read_dir(templates_dir).await?; while let Some(entry) = read_dir.next_entry().await? { let path = entry.path(); diff --git a/src/config/mod.rs b/src/config/mod.rs index 4f8beb10..0f3d492a 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -140,7 +140,7 @@ impl AppConfig { pub fn from_env() -> Result { let database_url = std::env::var("DATABASE_URL").unwrap(); let (db_username, db_password, db_server, db_port, db_name) = - parse_database_url(&database_url); + crate::shared::utils::parse_database_url(&database_url); let database = DatabaseConfig { username: db_username, password: db_password, @@ -174,34 +174,8 @@ impl AppConfig { }) } } -fn parse_database_url(url: &str) -> (String, String, String, u32, String) { - if let Some(stripped) = url.strip_prefix("postgres://") { - let parts: Vec<&str> = stripped.split('@').collect(); - if parts.len() == 2 { - let user_pass: Vec<&str> = parts[0].split(':').collect(); - let host_db: Vec<&str> = parts[1].split('/').collect(); - if user_pass.len() >= 2 && host_db.len() >= 2 { - let username = user_pass[0].to_string(); - let password = user_pass[1].to_string(); - let host_port: Vec<&str> = host_db[0].split(':').collect(); - let server = host_port[0].to_string(); - let port = host_port - .get(1) - .and_then(|p| p.parse().ok()) - .unwrap_or(5432); - let database = host_db[1].to_string(); - return (username, password, server, port, database); - } - } - } - ( - "gbuser".to_string(), - "".to_string(), - "localhost".to_string(), - 5432, - "botserver".to_string(), - ) -} + + pub struct ConfigManager { conn: DbPool, } diff --git a/src/main.rs b/src/main.rs index 58b8c28a..10b64706 100644 --- a/src/main.rs +++ b/src/main.rs @@ -149,6 +149,15 @@ async fn main() -> std::io::Result<()> { let mut bootstrap = BootstrapManager::new(install_mode.clone(), tenant.clone()).await; let env_path = std::env::current_dir().unwrap().join(".env"); let cfg = if env_path.exists() { + + + progress_tx_clone + .send(BootstrapProgress::StartingComponent( + "all services".to_string(), + )) + .ok(); + bootstrap.start_all().map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?; + progress_tx_clone .send(BootstrapProgress::ConnectingDatabase) .ok(); @@ -167,25 +176,21 @@ async fn main() -> std::io::Result<()> { } } else { bootstrap.bootstrap().await; + + progress_tx_clone + .send(BootstrapProgress::StartingComponent( + "all services".to_string(), + )) + .ok(); + bootstrap.start_all().map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?; + match create_conn() { Ok(pool) => AppConfig::from_database(&pool) .unwrap_or_else(|_| AppConfig::from_env().expect("Failed to load config")), Err(_) => AppConfig::from_env().expect("Failed to load config from env"), } }; - progress_tx_clone - .send(BootstrapProgress::StartingComponent( - "all services".to_string(), - )) - .ok(); - if let Err(e) = bootstrap.start_all() { - progress_tx_clone - .send(BootstrapProgress::BootstrapError(format!( - "Failed to start services: {}", - e - ))) - .ok(); - } + progress_tx_clone .send(BootstrapProgress::UploadingTemplates) .ok(); diff --git a/src/package_manager/installer.rs b/src/package_manager/installer.rs index 57926f1e..3d765b96 100644 --- a/src/package_manager/installer.rs +++ b/src/package_manager/installer.rs @@ -1,9 +1,9 @@ use crate::package_manager::component::ComponentConfig; use crate::package_manager::os::detect_os; use crate::package_manager::{InstallMode, OsType}; +use crate::shared::utils::parse_database_url; use anyhow::Result; use log::trace; -use rand::distr::Alphanumeric; use std::collections::HashMap; use std::path::PathBuf; @@ -61,19 +61,8 @@ impl PackageManager { fn register_drive(&mut self) { - let drive_password = self.generate_secure_password(16); - let drive_user = "gbdriveuser".to_string(); - - let env_path = std::env::current_dir().unwrap().join(".env"); - let env_content = format!( - "\nDRIVE_ACCESSKEY={}\nDRIVE_SECRET={}\nDRIVE_SERVER=http://localhost:9000\n", - drive_user, drive_password - ); - let _ = std::fs::OpenOptions::new() - .create(true) - .append(true) - .open(&env_path) - .and_then(|mut file| std::io::Write::write_all(&mut file, env_content.as_bytes())); + let drive_user = std::env::var("DRIVE_ACCESSKEY").unwrap(); + let drive_password = std::env::var("DRIVE_SECRET").unwrap(); self.components.insert( "drive".to_string(), @@ -95,8 +84,8 @@ impl PackageManager { pre_install_cmds_windows: vec![], post_install_cmds_windows: vec![], env_vars: HashMap::from([ - ("DRIVE_ROOT_USER".to_string(), drive_user.clone()), - ("DRIVE_ROOT_PASSWORD".to_string(), drive_password.clone()), + ("MINIO_ROOT_USER".to_string(), drive_user.clone()), + ("MINIO_ROOT_PASSWORD".to_string(), drive_password.clone()), ]), data_download_list: Vec::new(), exec_cmd: "nohup {{BIN_PATH}}/minio server {{DATA_PATH}} --address :9000 --console-address :9001 > {{LOGS_PATH}}/minio.log 2>&1 &".to_string(), @@ -110,14 +99,9 @@ impl PackageManager { fn register_tables(&mut self) { - let db_env_path = std::env::current_dir().unwrap().join(".env"); - let db_password = self.generate_secure_password(32); - let database_url = std::env::var("DATABASE_URL") - .unwrap_or_else(|_| format!("postgres://gbuser:{}@localhost:5432/botserver", db_password)); - let db_line = format!("DATABASE_URL={}\n", database_url); - - - let _ = std::fs::write(&db_env_path, db_line); + let database_url = std::env::var("DATABASE_URL").unwrap(); + let (_db_username, db_password, _db_server, _db_port, _db_name) = + parse_database_url(&database_url); self.components.insert( "tables".to_string(), @@ -756,10 +740,12 @@ impl PackageManager { rendered_cmd ); + let child = std::process::Command::new("sh") .current_dir(&bin_path) .arg("-c") .arg(&rendered_cmd) + .envs(&component.env_vars) .spawn(); std::thread::sleep(std::time::Duration::from_secs(2)); @@ -787,14 +773,4 @@ impl PackageManager { } } - fn generate_secure_password(&self, length: usize) -> String { - let mut rng = rand::rng(); - (0..length) - .map(|_| { - let byte = rand::Rng::sample(&mut rng, Alphanumeric); - char::from(byte) - }) - .collect() - } - } diff --git a/src/shared/utils.rs b/src/shared/utils.rs index b13d1581..cc5573be 100644 --- a/src/shared/utils.rs +++ b/src/shared/utils.rs @@ -151,3 +151,33 @@ pub fn create_conn() -> Result { let manager = ConnectionManager::::new(database_url); Pool::builder().build(manager) } + + +pub fn parse_database_url(url: &str) -> (String, String, String, u32, String) { + if let Some(stripped) = url.strip_prefix("postgres://") { + let parts: Vec<&str> = stripped.split('@').collect(); + if parts.len() == 2 { + let user_pass: Vec<&str> = parts[0].split(':').collect(); + let host_db: Vec<&str> = parts[1].split('/').collect(); + if user_pass.len() >= 2 && host_db.len() >= 2 { + let username = user_pass[0].to_string(); + let password = user_pass[1].to_string(); + let host_port: Vec<&str> = host_db[0].split(':').collect(); + let server = host_port[0].to_string(); + let port = host_port + .get(1) + .and_then(|p| p.parse().ok()) + .unwrap_or(5432); + let database = host_db[1].to_string(); + return (username, password, server, port, database); + } + } + } + ( + "gbuser".to_string(), + "".to_string(), + "localhost".to_string(), + 5432, + "botserver".to_string(), + ) +} \ No newline at end of file