feat(bootstrap): add secure password generation and env setup
Added functionality to generate secure passwords for database and drive server credentials during bootstrap. Removed the PostgreSQL running check and auto-start logic as it's no longer needed. Renamed `create_s3_operator` to more descriptive `get_drive_client`. The bootstrap process now automatically sets up required environment variables in .env file including database URL and drive server credentials.
This commit is contained in:
parent
fd73d207cc
commit
b52e4b2737
5 changed files with 100 additions and 102 deletions
|
|
@ -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<String>,
|
||||
}
|
||||
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<String>) -> 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();
|
||||
|
|
|
|||
|
|
@ -140,7 +140,7 @@ impl AppConfig {
|
|||
pub fn from_env() -> Result<Self, anyhow::Error> {
|
||||
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,
|
||||
}
|
||||
|
|
|
|||
31
src/main.rs
31
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();
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -151,3 +151,33 @@ pub fn create_conn() -> Result<DbPool, r2d2::Error> {
|
|||
let manager = ConnectionManager::<PgConnection>::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(),
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue