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 aws_sdk_s3::Client;
|
||||||
use diesel::connection::SimpleConnection;
|
use diesel::connection::SimpleConnection;
|
||||||
use log::{error, info, trace};
|
use log::{error, info, trace};
|
||||||
|
use rand::distr::Alphanumeric;
|
||||||
use std::io::{self, Write};
|
use std::io::{self, Write};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
@ -17,35 +18,12 @@ pub struct BootstrapManager {
|
||||||
pub tenant: Option<String>,
|
pub tenant: Option<String>,
|
||||||
}
|
}
|
||||||
impl BootstrapManager {
|
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 {
|
pub async fn new(install_mode: InstallMode, tenant: Option<String>) -> Self {
|
||||||
trace!(
|
trace!(
|
||||||
"Initializing BootstrapManager with mode {:?} and tenant {:?}",
|
"Initializing BootstrapManager with mode {:?} and tenant {:?}",
|
||||||
install_mode,
|
install_mode,
|
||||||
tenant
|
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 {
|
Self {
|
||||||
install_mode,
|
install_mode,
|
||||||
tenant,
|
tenant,
|
||||||
|
|
@ -84,6 +62,18 @@ impl BootstrapManager {
|
||||||
}
|
}
|
||||||
Ok(())
|
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) {
|
pub async fn bootstrap(&mut self) {
|
||||||
if let Ok(tables_server) = std::env::var("TABLES_SERVER") {
|
if let Ok(tables_server) = std::env::var("TABLES_SERVER") {
|
||||||
if !tables_server.is_empty() {
|
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 pm = PackageManager::new(self.install_mode.clone(), self.tenant.clone()).unwrap();
|
||||||
let required_components = vec!["tables", "drive", "cache", "llm"];
|
let required_components = vec!["tables", "drive", "cache", "llm"];
|
||||||
for component in required_components {
|
for component in required_components {
|
||||||
|
|
@ -191,7 +204,7 @@ 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('/') {
|
let endpoint = if !config.drive.server.ends_with('/') {
|
||||||
format!("{}/", config.drive.server)
|
format!("{}/", config.drive.server)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -222,7 +235,7 @@ impl BootstrapManager {
|
||||||
if !templates_dir.exists() {
|
if !templates_dir.exists() {
|
||||||
return Ok(());
|
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?;
|
let mut read_dir = tokio::fs::read_dir(templates_dir).await?;
|
||||||
while let Some(entry) = read_dir.next_entry().await? {
|
while let Some(entry) = read_dir.next_entry().await? {
|
||||||
let path = entry.path();
|
let path = entry.path();
|
||||||
|
|
|
||||||
|
|
@ -140,7 +140,7 @@ impl AppConfig {
|
||||||
pub fn from_env() -> Result<Self, anyhow::Error> {
|
pub fn from_env() -> Result<Self, anyhow::Error> {
|
||||||
let database_url = std::env::var("DATABASE_URL").unwrap();
|
let database_url = std::env::var("DATABASE_URL").unwrap();
|
||||||
let (db_username, db_password, db_server, db_port, db_name) =
|
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 {
|
let database = DatabaseConfig {
|
||||||
username: db_username,
|
username: db_username,
|
||||||
password: db_password,
|
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 {
|
pub struct ConfigManager {
|
||||||
conn: DbPool,
|
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 mut bootstrap = BootstrapManager::new(install_mode.clone(), tenant.clone()).await;
|
||||||
let env_path = std::env::current_dir().unwrap().join(".env");
|
let env_path = std::env::current_dir().unwrap().join(".env");
|
||||||
let cfg = if env_path.exists() {
|
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
|
progress_tx_clone
|
||||||
.send(BootstrapProgress::ConnectingDatabase)
|
.send(BootstrapProgress::ConnectingDatabase)
|
||||||
.ok();
|
.ok();
|
||||||
|
|
@ -167,25 +176,21 @@ async fn main() -> std::io::Result<()> {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
bootstrap.bootstrap().await;
|
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() {
|
match create_conn() {
|
||||||
Ok(pool) => AppConfig::from_database(&pool)
|
Ok(pool) => AppConfig::from_database(&pool)
|
||||||
.unwrap_or_else(|_| AppConfig::from_env().expect("Failed to load config")),
|
.unwrap_or_else(|_| AppConfig::from_env().expect("Failed to load config")),
|
||||||
Err(_) => AppConfig::from_env().expect("Failed to load config from env"),
|
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
|
progress_tx_clone
|
||||||
.send(BootstrapProgress::UploadingTemplates)
|
.send(BootstrapProgress::UploadingTemplates)
|
||||||
.ok();
|
.ok();
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
use crate::package_manager::component::ComponentConfig;
|
use crate::package_manager::component::ComponentConfig;
|
||||||
use crate::package_manager::os::detect_os;
|
use crate::package_manager::os::detect_os;
|
||||||
use crate::package_manager::{InstallMode, OsType};
|
use crate::package_manager::{InstallMode, OsType};
|
||||||
|
use crate::shared::utils::parse_database_url;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use log::trace;
|
use log::trace;
|
||||||
use rand::distr::Alphanumeric;
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
|
@ -61,19 +61,8 @@ impl PackageManager {
|
||||||
|
|
||||||
fn register_drive(&mut self) {
|
fn register_drive(&mut self) {
|
||||||
|
|
||||||
let drive_password = self.generate_secure_password(16);
|
let drive_user = std::env::var("DRIVE_ACCESSKEY").unwrap();
|
||||||
let drive_user = "gbdriveuser".to_string();
|
let drive_password = std::env::var("DRIVE_SECRET").unwrap();
|
||||||
|
|
||||||
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()));
|
|
||||||
|
|
||||||
self.components.insert(
|
self.components.insert(
|
||||||
"drive".to_string(),
|
"drive".to_string(),
|
||||||
|
|
@ -95,8 +84,8 @@ impl PackageManager {
|
||||||
pre_install_cmds_windows: vec![],
|
pre_install_cmds_windows: vec![],
|
||||||
post_install_cmds_windows: vec![],
|
post_install_cmds_windows: vec![],
|
||||||
env_vars: HashMap::from([
|
env_vars: HashMap::from([
|
||||||
("DRIVE_ROOT_USER".to_string(), drive_user.clone()),
|
("MINIO_ROOT_USER".to_string(), drive_user.clone()),
|
||||||
("DRIVE_ROOT_PASSWORD".to_string(), drive_password.clone()),
|
("MINIO_ROOT_PASSWORD".to_string(), drive_password.clone()),
|
||||||
]),
|
]),
|
||||||
data_download_list: Vec::new(),
|
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(),
|
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) {
|
fn register_tables(&mut self) {
|
||||||
|
|
||||||
let db_env_path = std::env::current_dir().unwrap().join(".env");
|
let database_url = std::env::var("DATABASE_URL").unwrap();
|
||||||
let db_password = self.generate_secure_password(32);
|
let (_db_username, db_password, _db_server, _db_port, _db_name) =
|
||||||
let database_url = std::env::var("DATABASE_URL")
|
parse_database_url(&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);
|
|
||||||
|
|
||||||
self.components.insert(
|
self.components.insert(
|
||||||
"tables".to_string(),
|
"tables".to_string(),
|
||||||
|
|
@ -756,10 +740,12 @@ impl PackageManager {
|
||||||
rendered_cmd
|
rendered_cmd
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
let child = std::process::Command::new("sh")
|
let child = std::process::Command::new("sh")
|
||||||
.current_dir(&bin_path)
|
.current_dir(&bin_path)
|
||||||
.arg("-c")
|
.arg("-c")
|
||||||
.arg(&rendered_cmd)
|
.arg(&rendered_cmd)
|
||||||
|
.envs(&component.env_vars)
|
||||||
.spawn();
|
.spawn();
|
||||||
|
|
||||||
std::thread::sleep(std::time::Duration::from_secs(2));
|
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);
|
let manager = ConnectionManager::<PgConnection>::new(database_url);
|
||||||
Pool::builder().build(manager)
|
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