2025-10-18 19:08:00 -03:00
|
|
|
use crate::config::AppConfig;
|
|
|
|
|
use crate::package_manager::{InstallMode, PackageManager};
|
2025-10-19 11:08:23 -03:00
|
|
|
use anyhow::Result;
|
2025-10-19 19:28:08 -03:00
|
|
|
use log::{debug, trace, warn};
|
|
|
|
|
use rand::distr::Alphanumeric;
|
|
|
|
|
use rand::rngs::ThreadRng;
|
|
|
|
|
use rand::Rng;
|
|
|
|
|
use sha2::{Digest, Sha256};
|
2025-10-18 19:08:00 -03:00
|
|
|
|
|
|
|
|
pub struct BootstrapManager {
|
2025-10-19 11:08:23 -03:00
|
|
|
pub install_mode: InstallMode,
|
|
|
|
|
pub tenant: Option<String>,
|
2025-10-18 19:08:00 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl BootstrapManager {
|
2025-10-19 11:08:23 -03:00
|
|
|
pub fn new(install_mode: InstallMode, tenant: Option<String>) -> Self {
|
2025-10-19 19:28:08 -03:00
|
|
|
trace!("Initializing BootstrapManager with mode {:?} and tenant {:?}", install_mode, tenant);
|
2025-10-18 19:08:00 -03:00
|
|
|
Self {
|
2025-10-19 11:08:23 -03:00
|
|
|
install_mode,
|
2025-10-18 19:08:00 -03:00
|
|
|
tenant,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-19 15:03:27 -03:00
|
|
|
pub fn start_all(&mut self) -> Result<()> {
|
|
|
|
|
let pm = PackageManager::new(self.install_mode.clone(), self.tenant.clone())?;
|
|
|
|
|
let components = vec![
|
2025-10-19 19:28:08 -03:00
|
|
|
"tables", "cache", "drive", "llm", "email", "proxy", "directory",
|
|
|
|
|
"alm", "alm_ci", "dns", "webmail", "meeting", "table_editor",
|
|
|
|
|
"doc_editor", "desktop", "devtools", "bot", "system", "vector_db", "host"
|
2025-10-19 15:03:27 -03:00
|
|
|
];
|
|
|
|
|
|
|
|
|
|
for component in components {
|
2025-10-19 19:28:08 -03:00
|
|
|
if pm.is_installed(component) {
|
|
|
|
|
trace!("Starting component: {}", component);
|
|
|
|
|
pm.start(component)?;
|
|
|
|
|
} else {
|
|
|
|
|
trace!("Component {} not installed, skipping start", component);
|
|
|
|
|
}
|
2025-10-19 15:03:27 -03:00
|
|
|
}
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-18 19:08:00 -03:00
|
|
|
pub fn bootstrap(&mut self) -> Result<AppConfig> {
|
2025-10-19 11:08:23 -03:00
|
|
|
let pm = PackageManager::new(self.install_mode.clone(), self.tenant.clone())?;
|
2025-10-19 14:02:47 -03:00
|
|
|
let required_components = vec!["tables", "cache", "drive", "llm"];
|
2025-10-19 19:28:08 -03:00
|
|
|
|
2025-10-19 11:08:23 -03:00
|
|
|
for component in required_components {
|
|
|
|
|
if !pm.is_installed(component) {
|
2025-10-19 19:28:08 -03:00
|
|
|
trace!("Installing required component: {}", component);
|
2025-10-19 11:08:23 -03:00
|
|
|
futures::executor::block_on(pm.install(component))?;
|
2025-10-19 19:28:08 -03:00
|
|
|
trace!("Starting component after install: {}", component);
|
|
|
|
|
pm.start(component)?;
|
2025-10-19 11:08:23 -03:00
|
|
|
} else {
|
2025-10-19 19:28:08 -03:00
|
|
|
trace!("Required component {} already installed", component);
|
2025-10-18 19:08:00 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-19 19:28:08 -03:00
|
|
|
let config = match diesel::Connection::establish("postgres://botserver:botserver@localhost:5432/botserver") {
|
2025-10-19 11:08:23 -03:00
|
|
|
Ok(mut conn) => {
|
2025-10-19 19:28:08 -03:00
|
|
|
self.setup_secure_credentials(&mut conn)?;
|
2025-10-19 11:08:23 -03:00
|
|
|
AppConfig::from_database(&mut conn)
|
2025-10-18 19:08:00 -03:00
|
|
|
}
|
2025-10-19 11:08:23 -03:00
|
|
|
Err(e) => {
|
|
|
|
|
warn!("Failed to connect to database for config: {}", e);
|
|
|
|
|
AppConfig::from_env()
|
2025-10-18 19:08:00 -03:00
|
|
|
}
|
2025-10-19 11:08:23 -03:00
|
|
|
};
|
2025-10-18 19:08:00 -03:00
|
|
|
|
2025-10-19 11:08:23 -03:00
|
|
|
Ok(config)
|
2025-10-18 19:08:00 -03:00
|
|
|
}
|
2025-10-19 19:28:08 -03:00
|
|
|
|
|
|
|
|
fn setup_secure_credentials(&self, conn: &mut diesel::PgConnection) -> Result<()> {
|
|
|
|
|
use crate::shared::models::schema::bots::dsl::*;
|
|
|
|
|
use diesel::prelude::*;
|
|
|
|
|
use uuid::Uuid;
|
|
|
|
|
|
|
|
|
|
let farm_password = std::env::var("FARM_PASSWORD").unwrap_or_else(|_| self.generate_secure_password(32));
|
|
|
|
|
let db_password = self.generate_secure_password(16);
|
|
|
|
|
|
|
|
|
|
let encrypted_db_password = self.encrypt_password(&db_password, &farm_password);
|
|
|
|
|
|
|
|
|
|
let env_contents = format!(
|
|
|
|
|
"FARM_PASSWORD={}\nDATABASE_URL=postgres://gbuser:{}@localhost:5432/botserver",
|
|
|
|
|
farm_password, db_password
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
std::fs::write(".env", env_contents)
|
|
|
|
|
.map_err(|e| anyhow::anyhow!("Failed to write .env file: {}", e))?;
|
|
|
|
|
|
|
|
|
|
let system_bot_id = Uuid::parse_str("00000000-0000-0000-0000-000000000000")?;
|
|
|
|
|
diesel::update(bots)
|
|
|
|
|
.filter(bot_id.eq(system_bot_id))
|
|
|
|
|
.set(config.eq(serde_json::json!({
|
|
|
|
|
"encrypted_db_password": encrypted_db_password,
|
|
|
|
|
})))
|
|
|
|
|
.execute(conn)?;
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn generate_secure_password(&self, length: usize) -> String {
|
|
|
|
|
let mut rng: ThreadRng = rand::thread_rng();
|
|
|
|
|
rng.sample_iter(&Alphanumeric)
|
|
|
|
|
.take(length)
|
|
|
|
|
.map(char::from)
|
|
|
|
|
.collect()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn encrypt_password(&self, password: &str, key: &str) -> String {
|
|
|
|
|
let mut hasher = Sha256::new();
|
|
|
|
|
hasher.update(key.as_bytes());
|
|
|
|
|
hasher.update(password.as_bytes());
|
|
|
|
|
format!("{:x}", hasher.finalize())
|
|
|
|
|
}
|
2025-10-18 19:08:00 -03:00
|
|
|
}
|