Refactor config loading and DB URL parsing
This commit is contained in:
parent
30b026585d
commit
de5b651b89
6 changed files with 189 additions and 273 deletions
3
gbot.sh
3
gbot.sh
|
|
@ -1,2 +1,3 @@
|
||||||
pkill postgres && rm -rf botserver-stack && clear && \
|
set +e
|
||||||
|
pkill postgres && rm .env -rf botserver-stack && clear && \
|
||||||
RUST_LOG=trace,hyper_util=off cargo run
|
RUST_LOG=trace,hyper_util=off cargo run
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,10 @@ use crate::config::AppConfig;
|
||||||
use crate::package_manager::{InstallMode, PackageManager};
|
use crate::package_manager::{InstallMode, PackageManager};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use diesel::{Connection, RunQueryDsl};
|
use diesel::{Connection, RunQueryDsl};
|
||||||
use log::trace;
|
use dotenvy::dotenv;
|
||||||
|
use log::{info, trace};
|
||||||
use rand::distr::Alphanumeric;
|
use rand::distr::Alphanumeric;
|
||||||
use rand::Rng;
|
use rand::rng;
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
|
|
||||||
pub struct BootstrapManager {
|
pub struct BootstrapManager {
|
||||||
|
|
@ -68,26 +69,33 @@ impl BootstrapManager {
|
||||||
|
|
||||||
for component in required_components {
|
for component in required_components {
|
||||||
if !pm.is_installed(component) {
|
if !pm.is_installed(component) {
|
||||||
trace!("Installing required component: {}", component);
|
|
||||||
futures::executor::block_on(pm.install(component))?;
|
|
||||||
trace!("Starting component after install: {}", component);
|
|
||||||
pm.start(component)?;
|
|
||||||
|
|
||||||
if component == "tables" {
|
if component == "tables" {
|
||||||
let db_password = self.generate_secure_password(16);
|
let db_password = self.generate_secure_password(16);
|
||||||
let farm_password = self.generate_secure_password(32);
|
let farm_password = self.generate_secure_password(32);
|
||||||
let encrypted_db_password = self.encrypt_password(&db_password, &farm_password);
|
|
||||||
|
|
||||||
let env_contents = format!(
|
let env_contents = format!(
|
||||||
"FARM_PASSWORD={}\nDATABASE_URL=postgres://gbuser:{}@localhost:5432/botserver",
|
"FARM_PASSWORD={}\nDATABASE_URL=postgres://gbuser:{}@localhost:5432/botserver",
|
||||||
farm_password, db_password
|
farm_password, db_password
|
||||||
);
|
);
|
||||||
|
|
||||||
std::fs::write(".env", env_contents)
|
std::fs::write(".env", &env_contents)
|
||||||
.map_err(|e| anyhow::anyhow!("Failed to write .env file: {}", e))?;
|
.map_err(|e| anyhow::anyhow!("Failed to write .env file: {}", e))?;
|
||||||
|
dotenv().ok();
|
||||||
|
trace!("Generated database credentials and wrote to .env file");
|
||||||
|
}
|
||||||
|
|
||||||
trace!("Waiting 5 seconds for database to start...");
|
trace!("Installing required component: {}", component);
|
||||||
std::thread::sleep(std::time::Duration::from_secs(5));
|
futures::executor::block_on(pm.install(component))?;
|
||||||
|
|
||||||
|
trace!("Starting component after install: {}", component);
|
||||||
|
pm.start(component)?;
|
||||||
|
|
||||||
|
if component == "tables" {
|
||||||
|
trace!("Starting component after install: {}", component);
|
||||||
|
|
||||||
|
let database_url = std::env::var("DATABASE_URL").unwrap();
|
||||||
|
let mut conn = diesel::PgConnection::establish(&database_url)
|
||||||
|
.map_err(|e| anyhow::anyhow!("Failed to connect to database: {}", e))?;
|
||||||
|
|
||||||
let migration_dir = include_dir::include_dir!("./migrations");
|
let migration_dir = include_dir::include_dir!("./migrations");
|
||||||
let mut migration_files: Vec<_> = migration_dir
|
let mut migration_files: Vec<_> = migration_dir
|
||||||
|
|
@ -103,10 +111,6 @@ impl BootstrapManager {
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
migration_files.sort();
|
migration_files.sort();
|
||||||
let mut conn = diesel::PgConnection::establish(&format!(
|
|
||||||
"postgres://gbuser:{}@localhost:5432/botserver",
|
|
||||||
db_password
|
|
||||||
))?;
|
|
||||||
|
|
||||||
for migration_file in migration_files {
|
for migration_file in migration_files {
|
||||||
let migration = std::fs::read_to_string(&migration_file)?;
|
let migration = std::fs::read_to_string(&migration_file)?;
|
||||||
|
|
@ -114,42 +118,22 @@ impl BootstrapManager {
|
||||||
diesel::sql_query(&migration).execute(&mut conn)?;
|
diesel::sql_query(&migration).execute(&mut conn)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.setup_secure_credentials(&mut conn, &encrypted_db_password)?;
|
|
||||||
config = AppConfig::from_database(&mut conn);
|
config = AppConfig::from_database(&mut conn);
|
||||||
|
info!("Database migrations completed and configuration loaded");
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
trace!("Required component {} already installed", component);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(config)
|
Ok(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup_secure_credentials(
|
|
||||||
&self,
|
|
||||||
conn: &mut diesel::PgConnection,
|
|
||||||
encrypted_db_password: &str,
|
|
||||||
) -> Result<()> {
|
|
||||||
use crate::shared::models::schema::bots::dsl::*;
|
|
||||||
use diesel::prelude::*;
|
|
||||||
use uuid::Uuid;
|
|
||||||
|
|
||||||
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 {
|
fn generate_secure_password(&self, length: usize) -> String {
|
||||||
let rng = rand::rng();
|
// Ensure the Rng trait is in scope for `sample`
|
||||||
rng.sample_iter(&Alphanumeric)
|
use rand::Rng;
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
|
||||||
|
std::iter::repeat_with(|| rng.sample(Alphanumeric) as char)
|
||||||
.take(length)
|
.take(length)
|
||||||
.map(char::from)
|
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -79,22 +79,14 @@ impl AppConfig {
|
||||||
pub fn database_url(&self) -> String {
|
pub fn database_url(&self) -> String {
|
||||||
format!(
|
format!(
|
||||||
"postgres://{}:{}@{}:{}/{}",
|
"postgres://{}:{}@{}:{}/{}",
|
||||||
self.database.username,
|
self.database.username, self.database.password, self.database.server, self.database.port, self.database.database
|
||||||
self.database.password,
|
|
||||||
self.database.server,
|
|
||||||
self.database.port,
|
|
||||||
self.database.database
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn database_custom_url(&self) -> String {
|
pub fn database_custom_url(&self) -> String {
|
||||||
format!(
|
format!(
|
||||||
"postgres://{}:{}@{}:{}/{}",
|
"postgres://{}:{}@{}:{}/{}",
|
||||||
self.database_custom.username,
|
self.database_custom.username, self.database_custom.password, self.database_custom.server, self.database_custom.port, self.database_custom.database
|
||||||
self.database_custom.password,
|
|
||||||
self.database_custom.server,
|
|
||||||
self.database_custom.port,
|
|
||||||
self.database_custom.database
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -133,49 +125,37 @@ impl AppConfig {
|
||||||
};
|
};
|
||||||
|
|
||||||
let get_str = |key: &str, default: &str| -> String {
|
let get_str = |key: &str, default: &str| -> String {
|
||||||
config_map
|
config_map.get(key).map(|v| v.config_value.clone()).unwrap_or_else(|| default.to_string())
|
||||||
.get(key)
|
|
||||||
.map(|v| v.config_value.clone())
|
|
||||||
.unwrap_or_else(|| default.to_string())
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let get_u32 = |key: &str, default: u32| -> u32 {
|
let get_u32 = |key: &str, default: u32| -> u32 {
|
||||||
config_map
|
config_map.get(key).and_then(|v| v.config_value.parse().ok()).unwrap_or(default)
|
||||||
.get(key)
|
|
||||||
.and_then(|v| v.config_value.parse().ok())
|
|
||||||
.unwrap_or(default)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let get_u16 = |key: &str, default: u16| -> u16 {
|
let get_u16 = |key: &str, default: u16| -> u16 {
|
||||||
config_map
|
config_map.get(key).and_then(|v| v.config_value.parse().ok()).unwrap_or(default)
|
||||||
.get(key)
|
|
||||||
.and_then(|v| v.config_value.parse().ok())
|
|
||||||
.unwrap_or(default)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let get_bool = |key: &str, default: bool| -> bool {
|
let get_bool = |key: &str, default: bool| -> bool {
|
||||||
config_map
|
config_map.get(key).map(|v| v.config_value.to_lowercase() == "true").unwrap_or(default)
|
||||||
.get(key)
|
|
||||||
.map(|v| v.config_value.to_lowercase() == "true")
|
|
||||||
.unwrap_or(default)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let stack_path = PathBuf::from(get_str("STACK_PATH", "./botserver-stack"));
|
let stack_path = PathBuf::from(get_str("STACK_PATH", "./botserver-stack"));
|
||||||
|
|
||||||
let database = DatabaseConfig {
|
let database = DatabaseConfig {
|
||||||
username: get_str("TABLES_USERNAME", "botserver"),
|
username: get_str("TABLES_USERNAME", "gbuser"),
|
||||||
password: get_str("TABLES_PASSWORD", "botserver"),
|
password: get_str("TABLES_PASSWORD", ""),
|
||||||
server: get_str("TABLES_SERVER", "localhost"),
|
server: get_str("TABLES_SERVER", "localhost"),
|
||||||
port: get_u32("TABLES_PORT", 5432),
|
port: get_u32("TABLES_PORT", 5432),
|
||||||
database: get_str("TABLES_DATABASE", "botserver"),
|
database: get_str("TABLES_DATABASE", "botserver"),
|
||||||
};
|
};
|
||||||
|
|
||||||
let database_custom = DatabaseConfig {
|
let database_custom = DatabaseConfig {
|
||||||
username: get_str("CUSTOM_USERNAME", "user"),
|
username: get_str("CUSTOM_USERNAME", "gbuser"),
|
||||||
password: get_str("CUSTOM_PASSWORD", "pass"),
|
password: get_str("CUSTOM_PASSWORD", ""),
|
||||||
server: get_str("CUSTOM_SERVER", "localhost"),
|
server: get_str("CUSTOM_SERVER", "localhost"),
|
||||||
port: get_u32("CUSTOM_PORT", 5432),
|
port: get_u32("CUSTOM_PORT", 5432),
|
||||||
database: get_str("CUSTOM_DATABASE", "custom"),
|
database: get_str("CUSTOM_DATABASE", "botserver"),
|
||||||
};
|
};
|
||||||
|
|
||||||
let minio = DriveConfig {
|
let minio = DriveConfig {
|
||||||
|
|
@ -203,10 +183,7 @@ impl AppConfig {
|
||||||
|
|
||||||
AppConfig {
|
AppConfig {
|
||||||
minio,
|
minio,
|
||||||
server: ServerConfig {
|
server: ServerConfig { host: get_str("SERVER_HOST", "127.0.0.1"), port: get_u16("SERVER_PORT", 8080) },
|
||||||
host: get_str("SERVER_HOST", "127.0.0.1"),
|
|
||||||
port: get_u16("SERVER_PORT", 8080),
|
|
||||||
},
|
|
||||||
database,
|
database,
|
||||||
database_custom,
|
database_custom,
|
||||||
email,
|
email,
|
||||||
|
|
@ -221,52 +198,39 @@ impl AppConfig {
|
||||||
pub fn from_env() -> Self {
|
pub fn from_env() -> Self {
|
||||||
warn!("Loading configuration from environment variables");
|
warn!("Loading configuration from environment variables");
|
||||||
|
|
||||||
let stack_path =
|
let stack_path = std::env::var("STACK_PATH").unwrap_or_else(|_| "./botserver-stack".to_string());
|
||||||
std::env::var("STACK_PATH").unwrap_or_else(|_| "./botserver-stack".to_string());
|
|
||||||
|
let database_url = std::env::var("DATABASE_URL").unwrap_or_else(|_| "postgres://gbuser:@localhost:5432/botserver".to_string());
|
||||||
|
let (db_username, db_password, db_server, db_port, db_name) = parse_database_url(&database_url);
|
||||||
|
|
||||||
let database = DatabaseConfig {
|
let database = DatabaseConfig {
|
||||||
username: std::env::var("TABLES_USERNAME").unwrap_or_else(|_| "botserver".to_string()),
|
username: db_username,
|
||||||
password: std::env::var("TABLES_PASSWORD").unwrap_or_else(|_| "botserver".to_string()),
|
password: db_password,
|
||||||
server: std::env::var("TABLES_SERVER").unwrap_or_else(|_| "localhost".to_string()),
|
server: db_server,
|
||||||
port: std::env::var("TABLES_PORT")
|
port: db_port,
|
||||||
.ok()
|
database: db_name,
|
||||||
.and_then(|p| p.parse().ok())
|
|
||||||
.unwrap_or(5432),
|
|
||||||
database: std::env::var("TABLES_DATABASE").unwrap_or_else(|_| "botserver".to_string()),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let database_custom = DatabaseConfig {
|
let database_custom = DatabaseConfig {
|
||||||
username: std::env::var("CUSTOM_USERNAME").unwrap_or_else(|_| "user".to_string()),
|
username: std::env::var("CUSTOM_USERNAME").unwrap_or_else(|_| "gbuser".to_string()),
|
||||||
password: std::env::var("CUSTOM_PASSWORD").unwrap_or_else(|_| "pass".to_string()),
|
password: std::env::var("CUSTOM_PASSWORD").unwrap_or_else(|_| "".to_string()),
|
||||||
server: std::env::var("CUSTOM_SERVER").unwrap_or_else(|_| "localhost".to_string()),
|
server: std::env::var("CUSTOM_SERVER").unwrap_or_else(|_| "localhost".to_string()),
|
||||||
port: std::env::var("CUSTOM_PORT")
|
port: std::env::var("CUSTOM_PORT").ok().and_then(|p| p.parse().ok()).unwrap_or(5432),
|
||||||
.ok()
|
database: std::env::var("CUSTOM_DATABASE").unwrap_or_else(|_| "botserver".to_string()),
|
||||||
.and_then(|p| p.parse().ok())
|
|
||||||
.unwrap_or(5432),
|
|
||||||
database: std::env::var("CUSTOM_DATABASE").unwrap_or_else(|_| "custom".to_string()),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let minio = DriveConfig {
|
let minio = DriveConfig {
|
||||||
server: std::env::var("DRIVE_SERVER").unwrap_or_else(|_| "localhost:9000".to_string()),
|
server: std::env::var("DRIVE_SERVER").unwrap_or_else(|_| "localhost:9000".to_string()),
|
||||||
access_key: std::env::var("DRIVE_ACCESSKEY")
|
access_key: std::env::var("DRIVE_ACCESSKEY").unwrap_or_else(|_| "minioadmin".to_string()),
|
||||||
.unwrap_or_else(|_| "minioadmin".to_string()),
|
|
||||||
secret_key: std::env::var("DRIVE_SECRET").unwrap_or_else(|_| "minioadmin".to_string()),
|
secret_key: std::env::var("DRIVE_SECRET").unwrap_or_else(|_| "minioadmin".to_string()),
|
||||||
use_ssl: std::env::var("DRIVE_USE_SSL")
|
use_ssl: std::env::var("DRIVE_USE_SSL").unwrap_or_else(|_| "false".to_string()).parse().unwrap_or(false),
|
||||||
.unwrap_or_else(|_| "false".to_string())
|
org_prefix: std::env::var("DRIVE_ORG_PREFIX").unwrap_or_else(|_| "botserver".to_string()),
|
||||||
.parse()
|
|
||||||
.unwrap_or(false),
|
|
||||||
org_prefix: std::env::var("DRIVE_ORG_PREFIX")
|
|
||||||
.unwrap_or_else(|_| "botserver".to_string()),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let email = EmailConfig {
|
let email = EmailConfig {
|
||||||
from: std::env::var("EMAIL_FROM").unwrap_or_else(|_| "noreply@example.com".to_string()),
|
from: std::env::var("EMAIL_FROM").unwrap_or_else(|_| "noreply@example.com".to_string()),
|
||||||
server: std::env::var("EMAIL_SERVER")
|
server: std::env::var("EMAIL_SERVER").unwrap_or_else(|_| "smtp.example.com".to_string()),
|
||||||
.unwrap_or_else(|_| "smtp.example.com".to_string()),
|
port: std::env::var("EMAIL_PORT").unwrap_or_else(|_| "587".to_string()).parse().unwrap_or(587),
|
||||||
port: std::env::var("EMAIL_PORT")
|
|
||||||
.unwrap_or_else(|_| "587".to_string())
|
|
||||||
.parse()
|
|
||||||
.unwrap_or(587),
|
|
||||||
username: std::env::var("EMAIL_USER").unwrap_or_else(|_| "user".to_string()),
|
username: std::env::var("EMAIL_USER").unwrap_or_else(|_| "user".to_string()),
|
||||||
password: std::env::var("EMAIL_PASS").unwrap_or_else(|_| "pass".to_string()),
|
password: std::env::var("EMAIL_PASS").unwrap_or_else(|_| "pass".to_string()),
|
||||||
};
|
};
|
||||||
|
|
@ -274,41 +238,29 @@ impl AppConfig {
|
||||||
let ai = AIConfig {
|
let ai = AIConfig {
|
||||||
instance: std::env::var("AI_INSTANCE").unwrap_or_else(|_| "gpt-4".to_string()),
|
instance: std::env::var("AI_INSTANCE").unwrap_or_else(|_| "gpt-4".to_string()),
|
||||||
key: std::env::var("AI_KEY").unwrap_or_else(|_| "".to_string()),
|
key: std::env::var("AI_KEY").unwrap_or_else(|_| "".to_string()),
|
||||||
version: std::env::var("AI_VERSION")
|
version: std::env::var("AI_VERSION").unwrap_or_else(|_| "2023-12-01-preview".to_string()),
|
||||||
.unwrap_or_else(|_| "2023-12-01-preview".to_string()),
|
endpoint: std::env::var("AI_ENDPOINT").unwrap_or_else(|_| "https://api.openai.com".to_string()),
|
||||||
endpoint: std::env::var("AI_ENDPOINT")
|
|
||||||
.unwrap_or_else(|_| "https://api.openai.com".to_string()),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
AppConfig {
|
AppConfig {
|
||||||
minio,
|
minio,
|
||||||
server: ServerConfig {
|
server: ServerConfig {
|
||||||
host: std::env::var("SERVER_HOST").unwrap_or_else(|_| "127.0.0.1".to_string()),
|
host: std::env::var("SERVER_HOST").unwrap_or_else(|_| "127.0.0.1".to_string()),
|
||||||
port: std::env::var("SERVER_PORT")
|
port: std::env::var("SERVER_PORT").ok().and_then(|p| p.parse().ok()).unwrap_or(8080),
|
||||||
.ok()
|
|
||||||
.and_then(|p| p.parse().ok())
|
|
||||||
.unwrap_or(8080),
|
|
||||||
},
|
},
|
||||||
database,
|
database,
|
||||||
database_custom,
|
database_custom,
|
||||||
email,
|
email,
|
||||||
ai,
|
ai,
|
||||||
s3_bucket: std::env::var("DRIVE_BUCKET").unwrap_or_else(|_| "default".to_string()),
|
s3_bucket: std::env::var("DRIVE_BUCKET").unwrap_or_else(|_| "default".to_string()),
|
||||||
site_path: std::env::var("SITES_ROOT")
|
site_path: std::env::var("SITES_ROOT").unwrap_or_else(|_| "./botserver-stack/sites".to_string()),
|
||||||
.unwrap_or_else(|_| "./botserver-stack/sites".to_string()),
|
|
||||||
stack_path: PathBuf::from(stack_path),
|
stack_path: PathBuf::from(stack_path),
|
||||||
db_conn: None,
|
db_conn: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_config_from_db(
|
fn load_config_from_db(conn: &mut PgConnection) -> Result<HashMap<String, ServerConfigRow>, diesel::result::Error> {
|
||||||
conn: &mut PgConnection,
|
let results = diesel::sql_query("SELECT id, config_key, config_value, config_type, is_encrypted FROM server_configuration").load::<ServerConfigRow>(conn)?;
|
||||||
) -> Result<HashMap<String, ServerConfigRow>, diesel::result::Error> {
|
|
||||||
let results = diesel::sql_query(
|
|
||||||
"SELECT id, config_key, config_value, config_type, is_encrypted
|
|
||||||
FROM server_configuration",
|
|
||||||
)
|
|
||||||
.load::<ServerConfigRow>(conn)?;
|
|
||||||
|
|
||||||
let mut map = HashMap::new();
|
let mut map = HashMap::new();
|
||||||
for row in results {
|
for row in results {
|
||||||
|
|
@ -318,27 +270,13 @@ impl AppConfig {
|
||||||
Ok(map)
|
Ok(map)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_config(
|
pub fn set_config(&self, conn: &mut PgConnection, key: &str, value: &str) -> Result<(), diesel::result::Error> {
|
||||||
&self,
|
diesel::sql_query("SELECT set_config($1, $2)").bind::<Text, _>(key).bind::<Text, _>(value).execute(conn)?;
|
||||||
conn: &mut PgConnection,
|
|
||||||
key: &str,
|
|
||||||
value: &str,
|
|
||||||
) -> Result<(), diesel::result::Error> {
|
|
||||||
diesel::sql_query("SELECT set_config($1, $2)")
|
|
||||||
.bind::<Text, _>(key)
|
|
||||||
.bind::<Text, _>(value)
|
|
||||||
.execute(conn)?;
|
|
||||||
|
|
||||||
info!("Updated configuration: {} = {}", key, value);
|
info!("Updated configuration: {} = {}", key, value);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_config(
|
pub fn get_config(&self, conn: &mut PgConnection, key: &str, fallback: Option<&str>) -> Result<String, diesel::result::Error> {
|
||||||
&self,
|
|
||||||
conn: &mut PgConnection,
|
|
||||||
key: &str,
|
|
||||||
fallback: Option<&str>,
|
|
||||||
) -> Result<String, diesel::result::Error> {
|
|
||||||
let fallback_str = fallback.unwrap_or("");
|
let fallback_str = fallback.unwrap_or("");
|
||||||
|
|
||||||
#[derive(Debug, QueryableByName)]
|
#[derive(Debug, QueryableByName)]
|
||||||
|
|
@ -347,16 +285,35 @@ impl AppConfig {
|
||||||
value: String,
|
value: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = diesel::sql_query("SELECT get_config($1, $2) as value")
|
let result = diesel::sql_query("SELECT get_config($1, $2) as value").bind::<Text, _>(key).bind::<Text, _>(fallback_str).get_result::<ConfigValue>(conn).map(|row| row.value)?;
|
||||||
.bind::<Text, _>(key)
|
|
||||||
.bind::<Text, _>(fallback_str)
|
|
||||||
.get_result::<ConfigValue>(conn)
|
|
||||||
.map(|row| row.value)?;
|
|
||||||
|
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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: Arc<Mutex<PgConnection>>,
|
conn: Arc<Mutex<PgConnection>>,
|
||||||
}
|
}
|
||||||
|
|
@ -366,25 +323,17 @@ impl ConfigManager {
|
||||||
Self { conn }
|
Self { conn }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sync_gbot_config(
|
pub fn sync_gbot_config(&self, bot_id: &uuid::Uuid, config_path: &str) -> Result<usize, String> {
|
||||||
&self,
|
|
||||||
bot_id: &uuid::Uuid,
|
|
||||||
config_path: &str,
|
|
||||||
) -> Result<usize, String> {
|
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
|
||||||
let content = fs::read_to_string(config_path)
|
let content = fs::read_to_string(config_path).map_err(|e| format!("Failed to read config file: {}", e))?;
|
||||||
.map_err(|e| format!("Failed to read config file: {}", e))?;
|
|
||||||
|
|
||||||
let mut hasher = Sha256::new();
|
let mut hasher = Sha256::new();
|
||||||
hasher.update(content.as_bytes());
|
hasher.update(content.as_bytes());
|
||||||
let file_hash = format!("{:x}", hasher.finalize());
|
let file_hash = format!("{:x}", hasher.finalize());
|
||||||
|
|
||||||
let mut conn = self
|
let mut conn = self.conn.lock().map_err(|e| format!("Failed to acquire lock: {}", e))?;
|
||||||
.conn
|
|
||||||
.lock()
|
|
||||||
.map_err(|e| format!("Failed to acquire lock: {}", e))?;
|
|
||||||
|
|
||||||
#[derive(QueryableByName)]
|
#[derive(QueryableByName)]
|
||||||
struct SyncHash {
|
struct SyncHash {
|
||||||
|
|
@ -392,13 +341,12 @@ impl ConfigManager {
|
||||||
file_hash: String,
|
file_hash: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
let last_hash: Option<String> =
|
let last_hash: Option<String> = diesel::sql_query("SELECT file_hash FROM gbot_config_sync WHERE bot_id = $1")
|
||||||
diesel::sql_query("SELECT file_hash FROM gbot_config_sync WHERE bot_id = $1")
|
.bind::<diesel::sql_types::Uuid, _>(bot_id)
|
||||||
.bind::<diesel::sql_types::Uuid, _>(bot_id)
|
.get_result::<SyncHash>(&mut *conn)
|
||||||
.get_result::<SyncHash>(&mut *conn)
|
.optional()
|
||||||
.optional()
|
.map_err(|e| format!("Database error: {}", e))?
|
||||||
.map_err(|e| format!("Database error: {}", e))?
|
.map(|row| row.file_hash);
|
||||||
.map(|row| row.file_hash);
|
|
||||||
|
|
||||||
if last_hash.as_ref() == Some(&file_hash) {
|
if last_hash.as_ref() == Some(&file_hash) {
|
||||||
info!("Config file unchanged for bot {}", bot_id);
|
info!("Config file unchanged for bot {}", bot_id);
|
||||||
|
|
@ -412,34 +360,23 @@ impl ConfigManager {
|
||||||
let key = parts[0].trim();
|
let key = parts[0].trim();
|
||||||
let value = parts[1].trim();
|
let value = parts[1].trim();
|
||||||
|
|
||||||
diesel::sql_query(
|
diesel::sql_query("INSERT INTO bot_configuration (id, bot_id, config_key, config_value, config_type) VALUES (gen_random_uuid()::text, $1, $2, $3, 'string') ON CONFLICT (bot_id, config_key) DO UPDATE SET config_value = EXCLUDED.config_value, updated_at = NOW()")
|
||||||
"INSERT INTO bot_configuration (id, bot_id, config_key, config_value, config_type)
|
.bind::<diesel::sql_types::Uuid, _>(bot_id)
|
||||||
VALUES (gen_random_uuid()::text, $1, $2, $3, 'string')
|
.bind::<diesel::sql_types::Text, _>(key)
|
||||||
ON CONFLICT (bot_id, config_key)
|
.bind::<diesel::sql_types::Text, _>(value)
|
||||||
DO UPDATE SET config_value = EXCLUDED.config_value, updated_at = NOW()"
|
.execute(&mut *conn)
|
||||||
)
|
.map_err(|e| format!("Failed to update config: {}", e))?;
|
||||||
.bind::<diesel::sql_types::Uuid, _>(bot_id)
|
|
||||||
.bind::<diesel::sql_types::Text, _>(key)
|
|
||||||
.bind::<diesel::sql_types::Text, _>(value)
|
|
||||||
.execute(&mut *conn)
|
|
||||||
.map_err(|e| format!("Failed to update config: {}", e))?;
|
|
||||||
|
|
||||||
updated += 1;
|
updated += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
diesel::sql_query(
|
diesel::sql_query("INSERT INTO gbot_config_sync (id, bot_id, config_file_path, file_hash, sync_count) VALUES (gen_random_uuid()::text, $1, $2, $3, 1) ON CONFLICT (bot_id) DO UPDATE SET last_sync_at = NOW(), file_hash = EXCLUDED.file_hash, sync_count = gbot_config_sync.sync_count + 1")
|
||||||
"INSERT INTO gbot_config_sync (id, bot_id, config_file_path, file_hash, sync_count)
|
.bind::<diesel::sql_types::Uuid, _>(bot_id)
|
||||||
VALUES (gen_random_uuid()::text, $1, $2, $3, 1)
|
.bind::<diesel::sql_types::Text, _>(config_path)
|
||||||
ON CONFLICT (bot_id)
|
.bind::<diesel::sql_types::Text, _>(&file_hash)
|
||||||
DO UPDATE SET last_sync_at = NOW(), file_hash = EXCLUDED.file_hash,
|
.execute(&mut *conn)
|
||||||
sync_count = gbot_config_sync.sync_count + 1",
|
.map_err(|e| format!("Failed to update sync record: {}", e))?;
|
||||||
)
|
|
||||||
.bind::<diesel::sql_types::Uuid, _>(bot_id)
|
|
||||||
.bind::<diesel::sql_types::Text, _>(config_path)
|
|
||||||
.bind::<diesel::sql_types::Text, _>(&file_hash)
|
|
||||||
.execute(&mut *conn)
|
|
||||||
.map_err(|e| format!("Failed to update sync record: {}", e))?;
|
|
||||||
|
|
||||||
info!("Synced {} config values for bot {} from {}", updated, bot_id, config_path);
|
info!("Synced {} config values for bot {} from {}", updated, bot_id, config_path);
|
||||||
Ok(updated)
|
Ok(updated)
|
||||||
|
|
|
||||||
73
src/main.rs
73
src/main.rs
|
|
@ -68,20 +68,14 @@ async fn main() -> std::io::Result<()> {
|
||||||
Ok(_) => return Ok(()),
|
Ok(_) => return Ok(()),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("CLI error: {}", e);
|
eprintln!("CLI error: {}", e);
|
||||||
return Err(std::io::Error::new(
|
return Err(std::io::Error::new(std::io::ErrorKind::Other, format!("CLI command failed: {}", e)));
|
||||||
std::io::ErrorKind::Other,
|
|
||||||
format!("CLI command failed: {}", e),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
eprintln!("Unknown command: {}", command);
|
eprintln!("Unknown command: {}", command);
|
||||||
eprintln!("Run 'botserver --help' for usage information");
|
eprintln!("Run 'botserver --help' for usage information");
|
||||||
return Err(std::io::Error::new(
|
return Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, format!("Unknown command: {}", command)));
|
||||||
std::io::ErrorKind::InvalidInput,
|
|
||||||
format!("Unknown command: {}", command),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -112,7 +106,7 @@ async fn main() -> std::io::Result<()> {
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::error!("Bootstrap failed: {}", e);
|
log::error!("Bootstrap failed: {}", e);
|
||||||
info!("Attempting to load configuration from database");
|
info!("Attempting to load configuration from database");
|
||||||
match diesel::Connection::establish("postgres://botserver:botserver@localhost:5432/botserver") {
|
match diesel::Connection::establish(&std::env::var("DATABASE_URL").unwrap_or_else(|_| "postgres://gbuser:@localhost:5432/botserver".to_string())) {
|
||||||
Ok(mut conn) => AppConfig::from_database(&mut conn),
|
Ok(mut conn) => AppConfig::from_database(&mut conn),
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
info!("Database not available, using environment variables as fallback");
|
info!("Database not available, using environment variables as fallback");
|
||||||
|
|
@ -130,25 +124,16 @@ async fn main() -> std::io::Result<()> {
|
||||||
Ok(conn) => Arc::new(Mutex::new(conn)),
|
Ok(conn) => Arc::new(Mutex::new(conn)),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::error!("Failed to connect to main database: {}", e);
|
log::error!("Failed to connect to main database: {}", e);
|
||||||
return Err(std::io::Error::new(
|
return Err(std::io::Error::new(std::io::ErrorKind::ConnectionRefused, format!("Database connection failed: {}", e)));
|
||||||
std::io::ErrorKind::ConnectionRefused,
|
|
||||||
format!("Database connection failed: {}", e),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let db_custom_pool = db_pool.clone();
|
let db_custom_pool = db_pool.clone();
|
||||||
|
|
||||||
info!("Initializing LLM server at {}", cfg.ai.endpoint);
|
info!("Initializing LLM server at {}", cfg.ai.endpoint);
|
||||||
ensure_llama_servers_running()
|
ensure_llama_servers_running().await.expect("Failed to initialize LLM local server");
|
||||||
.await
|
|
||||||
.expect("Failed to initialize LLM local server");
|
|
||||||
|
|
||||||
let cache_url = cfg
|
let cache_url = cfg.config_path("cache").join("redis.conf").display().to_string();
|
||||||
.config_path("cache")
|
|
||||||
.join("redis.conf")
|
|
||||||
.display()
|
|
||||||
.to_string();
|
|
||||||
let redis_client = match redis::Client::open(cache_url.as_str()) {
|
let redis_client = match redis::Client::open(cache_url.as_str()) {
|
||||||
Ok(client) => Some(Arc::new(client)),
|
Ok(client) => Some(Arc::new(client)),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
|
@ -158,28 +143,15 @@ async fn main() -> std::io::Result<()> {
|
||||||
};
|
};
|
||||||
|
|
||||||
let tool_manager = Arc::new(tools::ToolManager::new());
|
let tool_manager = Arc::new(tools::ToolManager::new());
|
||||||
let llm_provider = Arc::new(crate::llm::OpenAIClient::new(
|
let llm_provider = Arc::new(crate::llm::OpenAIClient::new("empty".to_string(), Some(cfg.ai.endpoint.clone())));
|
||||||
"empty".to_string(),
|
|
||||||
Some(cfg.ai.endpoint.clone()),
|
|
||||||
));
|
|
||||||
|
|
||||||
let web_adapter = Arc::new(WebChannelAdapter::new());
|
let web_adapter = Arc::new(WebChannelAdapter::new());
|
||||||
let voice_adapter = Arc::new(VoiceAdapter::new(
|
let voice_adapter = Arc::new(VoiceAdapter::new("https://livekit.example.com".to_string(), "api_key".to_string(), "api_secret".to_string()));
|
||||||
"https://livekit.example.com".to_string(),
|
let whatsapp_adapter = Arc::new(WhatsAppAdapter::new("whatsapp_token".to_string(), "phone_number_id".to_string(), "verify_token".to_string()));
|
||||||
"api_key".to_string(),
|
|
||||||
"api_secret".to_string(),
|
|
||||||
));
|
|
||||||
let whatsapp_adapter = Arc::new(WhatsAppAdapter::new(
|
|
||||||
"whatsapp_token".to_string(),
|
|
||||||
"phone_number_id".to_string(),
|
|
||||||
"verify_token".to_string(),
|
|
||||||
));
|
|
||||||
let tool_api = Arc::new(tools::ToolApi::new());
|
let tool_api = Arc::new(tools::ToolApi::new());
|
||||||
|
|
||||||
info!("Initializing MinIO drive at {}", cfg.minio.server);
|
info!("Initializing MinIO drive at {}", cfg.minio.server);
|
||||||
let drive = init_drive(&config.minio)
|
let drive = init_drive(&config.minio).await.expect("Failed to initialize Drive");
|
||||||
.await
|
|
||||||
.expect("Failed to initialize Drive");
|
|
||||||
|
|
||||||
let session_manager = Arc::new(tokio::sync::Mutex::new(session::SessionManager::new(
|
let session_manager = Arc::new(tokio::sync::Mutex::new(session::SessionManager::new(
|
||||||
diesel::Connection::establish(&cfg.database_url()).unwrap(),
|
diesel::Connection::establish(&cfg.database_url()).unwrap(),
|
||||||
|
|
@ -202,10 +174,7 @@ async fn main() -> std::io::Result<()> {
|
||||||
auth_service: auth_service.clone(),
|
auth_service: auth_service.clone(),
|
||||||
channels: Arc::new(Mutex::new({
|
channels: Arc::new(Mutex::new({
|
||||||
let mut map = HashMap::new();
|
let mut map = HashMap::new();
|
||||||
map.insert(
|
map.insert("web".to_string(), web_adapter.clone() as Arc<dyn crate::channels::ChannelAdapter>);
|
||||||
"web".to_string(),
|
|
||||||
web_adapter.clone() as Arc<dyn crate::channels::ChannelAdapter>,
|
|
||||||
);
|
|
||||||
map
|
map
|
||||||
})),
|
})),
|
||||||
response_channels: Arc::new(tokio::sync::Mutex::new(HashMap::new())),
|
response_channels: Arc::new(tokio::sync::Mutex::new(HashMap::new())),
|
||||||
|
|
@ -215,20 +184,12 @@ async fn main() -> std::io::Result<()> {
|
||||||
tool_api: tool_api.clone(),
|
tool_api: tool_api.clone(),
|
||||||
});
|
});
|
||||||
|
|
||||||
info!(
|
info!("Starting HTTP server on {}:{}", config.server.host, config.server.port);
|
||||||
"Starting HTTP server on {}:{}",
|
|
||||||
config.server.host, config.server.port
|
|
||||||
);
|
|
||||||
|
|
||||||
let worker_count = std::thread::available_parallelism()
|
let worker_count = std::thread::available_parallelism().map(|n| n.get()).unwrap_or(4);
|
||||||
.map(|n| n.get())
|
|
||||||
.unwrap_or(4);
|
|
||||||
|
|
||||||
let automation_state = app_state.clone();
|
let automation_state = app_state.clone();
|
||||||
let automation = AutomationService::new(
|
let automation = AutomationService::new(automation_state, "templates/announcements.gbai/announcements.gbdialog");
|
||||||
automation_state,
|
|
||||||
"templates/announcements.gbai/announcements.gbdialog",
|
|
||||||
);
|
|
||||||
let _automation_handle = automation.spawn();
|
let _automation_handle = automation.spawn();
|
||||||
|
|
||||||
let drive_state = app_state.clone();
|
let drive_state = app_state.clone();
|
||||||
|
|
@ -237,11 +198,7 @@ async fn main() -> std::io::Result<()> {
|
||||||
let _drive_handle = drive_monitor.spawn();
|
let _drive_handle = drive_monitor.spawn();
|
||||||
|
|
||||||
HttpServer::new(move || {
|
HttpServer::new(move || {
|
||||||
let cors = Cors::default()
|
let cors = Cors::default().allow_any_origin().allow_any_method().allow_any_header().max_age(3600);
|
||||||
.allow_any_origin()
|
|
||||||
.allow_any_method()
|
|
||||||
.allow_any_header()
|
|
||||||
.max_age(3600);
|
|
||||||
let app_state_clone = app_state.clone();
|
let app_state_clone = app_state.clone();
|
||||||
|
|
||||||
let mut app = App::new()
|
let mut app = App::new()
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ use crate::package_manager::OsType;
|
||||||
use crate::shared::utils;
|
use crate::shared::utils;
|
||||||
use crate::InstallMode;
|
use crate::InstallMode;
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use log::{trace, warn};
|
use log::{error, trace, warn};
|
||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
@ -543,19 +543,16 @@ impl PackageManager {
|
||||||
} else {
|
} else {
|
||||||
PathBuf::from("/opt/gbo/bin")
|
PathBuf::from("/opt/gbo/bin")
|
||||||
};
|
};
|
||||||
|
|
||||||
let data_path = if target == "local" {
|
let data_path = if target == "local" {
|
||||||
self.base_path.join("data").join(component)
|
self.base_path.join("data").join(component)
|
||||||
} else {
|
} else {
|
||||||
PathBuf::from("/opt/gbo/data")
|
PathBuf::from("/opt/gbo/data")
|
||||||
};
|
};
|
||||||
|
|
||||||
let conf_path = if target == "local" {
|
let conf_path = if target == "local" {
|
||||||
self.base_path.join("conf").join(component)
|
self.base_path.join("conf").join(component)
|
||||||
} else {
|
} else {
|
||||||
PathBuf::from("/opt/gbo/conf")
|
PathBuf::from("/opt/gbo/conf")
|
||||||
};
|
};
|
||||||
|
|
||||||
let logs_path = if target == "local" {
|
let logs_path = if target == "local" {
|
||||||
self.base_path.join("logs").join(component)
|
self.base_path.join("logs").join(component)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -571,13 +568,23 @@ impl PackageManager {
|
||||||
|
|
||||||
if target == "local" {
|
if target == "local" {
|
||||||
trace!("Executing command: {}", rendered_cmd);
|
trace!("Executing command: {}", rendered_cmd);
|
||||||
let output = Command::new("bash")
|
let mut child = Command::new("bash")
|
||||||
.current_dir(&bin_path)
|
.current_dir(&bin_path)
|
||||||
.args(&["-c", &rendered_cmd])
|
.args(&["-c", &rendered_cmd])
|
||||||
.output()?;
|
.spawn()
|
||||||
|
.with_context(|| {
|
||||||
|
format!("Failed to spawn command for component '{}'", component)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let output = child.wait_with_output().with_context(|| {
|
||||||
|
format!(
|
||||||
|
"Failed while waiting for command to finish for component '{}'",
|
||||||
|
component
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
if !output.status.success() {
|
if !output.status.success() {
|
||||||
warn!(
|
error!(
|
||||||
"Command had non-zero exit: {}",
|
"Command had non-zero exit: {}",
|
||||||
String::from_utf8_lossy(&output.stderr)
|
String::from_utf8_lossy(&output.stderr)
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ use crate::package_manager::{InstallMode, OsType};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use log::trace;
|
use log::trace;
|
||||||
use rand::distr::Alphanumeric;
|
use rand::distr::Alphanumeric;
|
||||||
use rand::Rng;
|
use rand::rng;
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
@ -63,7 +63,8 @@ impl PackageManager {
|
||||||
|
|
||||||
fn register_drive(&mut self) {
|
fn register_drive(&mut self) {
|
||||||
let drive_password = self.generate_secure_password(16);
|
let drive_password = self.generate_secure_password(16);
|
||||||
let farm_password = std::env::var("FARM_PASSWORD").unwrap();
|
let farm_password =
|
||||||
|
std::env::var("FARM_PASSWORD").unwrap_or_else(|_| self.generate_secure_password(32));
|
||||||
let encrypted_drive_password = self.encrypt_password(&drive_password, &farm_password);
|
let encrypted_drive_password = self.encrypt_password(&drive_password, &farm_password);
|
||||||
|
|
||||||
self.components.insert("drive".to_string(), ComponentConfig {
|
self.components.insert("drive".to_string(), ComponentConfig {
|
||||||
|
|
@ -108,7 +109,11 @@ impl PackageManager {
|
||||||
use diesel::pg::PgConnection;
|
use diesel::pg::PgConnection;
|
||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
if let Ok(mut conn) = PgConnection::establish(&std::env::var("DATABASE_URL")?) {
|
|
||||||
|
let database_url = std::env::var("DATABASE_URL")
|
||||||
|
.unwrap_or_else(|_| "postgres://gbuser:@localhost:5432/botserver".to_string());
|
||||||
|
|
||||||
|
if let Ok(mut conn) = PgConnection::establish(&database_url) {
|
||||||
let system_bot_id = Uuid::parse_str("00000000-0000-0000-0000-000000000000")?;
|
let system_bot_id = Uuid::parse_str("00000000-0000-0000-0000-000000000000")?;
|
||||||
diesel::update(bots)
|
diesel::update(bots)
|
||||||
.filter(bot_id.eq(system_bot_id))
|
.filter(bot_id.eq(system_bot_id))
|
||||||
|
|
@ -116,11 +121,27 @@ impl PackageManager {
|
||||||
"encrypted_drive_password": encrypted_drive_password,
|
"encrypted_drive_password": encrypted_drive_password,
|
||||||
})))
|
})))
|
||||||
.execute(&mut conn)?;
|
.execute(&mut conn)?;
|
||||||
|
trace!("Updated drive credentials in database for system bot");
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn register_tables(&mut self) {
|
fn register_tables(&mut self) {
|
||||||
|
let db_password = std::env::var("DATABASE_URL")
|
||||||
|
.ok()
|
||||||
|
.and_then(|url| {
|
||||||
|
if let Some(stripped) = url.strip_prefix("postgres://gbuser:") {
|
||||||
|
if let Some(at_pos) = stripped.find('@') {
|
||||||
|
Some(stripped[..at_pos].to_string())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|| self.generate_secure_password(16));
|
||||||
|
|
||||||
self.components.insert("tables".to_string(), ComponentConfig {
|
self.components.insert("tables".to_string(), ComponentConfig {
|
||||||
name: "tables".to_string(),
|
name: "tables".to_string(),
|
||||||
required: true,
|
required: true,
|
||||||
|
|
@ -134,17 +155,19 @@ impl PackageManager {
|
||||||
pre_install_cmds_linux: vec![],
|
pre_install_cmds_linux: vec![],
|
||||||
post_install_cmds_linux: vec![
|
post_install_cmds_linux: vec![
|
||||||
"chmod +x ./bin/*".to_string(),
|
"chmod +x ./bin/*".to_string(),
|
||||||
"if [ ! -d \"{{DATA_PATH}}/pgdata\" ]; then ./bin/initdb -D {{DATA_PATH}}/pgdata -U postgres; fi".to_string(),
|
|
||||||
"if [ ! -f \"{{CONF_PATH}}/postgresql.conf\" ]; then echo \"data_directory = '{{DATA_PATH}}/pgdata'\" > {{CONF_PATH}}/postgresql.conf; fi".to_string(),
|
format!("if [ ! -d \"{{{{DATA_PATH}}}}/pgdata\" ]; then PG_PASSWORD={} ./bin/initdb -D {{{{DATA_PATH}}}}/pgdata -U gbuser --pwfile=<(echo $PG_PASSWORD); fi", db_password).to_string(),
|
||||||
"if [ ! -f \"{{CONF_PATH}}/postgresql.conf\" ]; then echo \"hba_file = '{{CONF_PATH}}/pg_hba.conf'\" >> {{CONF_PATH}}/postgresql.conf; fi".to_string(),
|
"echo \"data_directory = '{{DATA_PATH}}/pgdata'\" > {{CONF_PATH}}/postgresql.conf".to_string(),
|
||||||
"if [ ! -f \"{{CONF_PATH}}/postgresql.conf\" ]; then echo \"ident_file = '{{CONF_PATH}}/pg_ident.conf'\" >> {{CONF_PATH}}/postgresql.conf; fi".to_string(),
|
"echo \"ident_file = '{{CONF_PATH}}/pg_ident.conf'\" >> {{CONF_PATH}}/postgresql.conf".to_string(),
|
||||||
"if [ ! -f \"{{CONF_PATH}}/postgresql.conf\" ]; then echo \"port = 5432\" >> {{CONF_PATH}}/postgresql.conf; fi".to_string(),
|
"echo \"port = 5432\" >> {{CONF_PATH}}/postgresql.conf".to_string(),
|
||||||
"if [ ! -f \"{{CONF_PATH}}/postgresql.conf\" ]; then echo \"listen_addresses = '*'\" >> {{CONF_PATH}}/postgresql.conf; fi".to_string(),
|
"echo \"listen_addresses = '*'\" >> {{CONF_PATH}}/postgresql.conf".to_string(),
|
||||||
"if [ ! -f \"{{CONF_PATH}}/postgresql.conf\" ]; then echo \"log_directory = '{{LOGS_PATH}}'\" >> {{CONF_PATH}}/postgresql.conf; fi".to_string(),
|
"echo \"log_directory = '{{LOGS_PATH}}'\" >> {{CONF_PATH}}/postgresql.conf".to_string(),
|
||||||
"if [ ! -f \"{{CONF_PATH}}/postgresql.conf\" ]; then echo \"logging_collector = on\" >> {{CONF_PATH}}/postgresql.conf; fi".to_string(),
|
"echo \"logging_collector = on\" >> {{CONF_PATH}}/postgresql.conf".to_string(),
|
||||||
"if [ ! -f \"{{CONF_PATH}}/pg_hba.conf\" ]; then echo \"host all all all md5\" > {{CONF_PATH}}/pg_hba.conf; fi".to_string(),
|
"echo \"host all all all md5\" > {{CONF_PATH}}/pg_hba.conf".to_string(),
|
||||||
"if [ ! -f \"{{CONF_PATH}}/pg_ident.conf\" ]; then touch {{CONF_PATH}}/pg_ident.conf; fi ".to_string(),
|
"touch {{CONF_PATH}}/pg_ident.conf".to_string(),
|
||||||
"if [ ! -d \"{{DATA_PATH}}/pgdata\" ]; then ./bin/pg_ctl -D {{DATA_PATH}}/pgdata -l {{LOGS_PATH}}/postgres.log start; sleep 5; ./bin/createdb -p 5432 -h localhost botserver; ./bin/createuser -p 5432 -h localhost gbuser; fi".to_string()
|
|
||||||
|
"./bin/pg_ctl -D {{DATA_PATH}}/pgdata -l {{LOGS_PATH}}/postgres.log start; while ! ./bin/pg_isready -d {{DATA_PATH}}/pgdata >/dev/null 2>&1; do sleep 15; done;".to_string(),
|
||||||
|
"./bin/psql -U gbuser -c \"CREATE DATABASE botserver WITH OWNER gbuser\" || true ".to_string()
|
||||||
],
|
],
|
||||||
pre_install_cmds_macos: vec![],
|
pre_install_cmds_macos: vec![],
|
||||||
post_install_cmds_macos: vec![
|
post_install_cmds_macos: vec![
|
||||||
|
|
@ -651,10 +674,17 @@ impl PackageManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_secure_password(&self, length: usize) -> String {
|
fn generate_secure_password(&self, length: usize) -> String {
|
||||||
let rng = rand::rng();
|
// Use the non-deprecated `rng` function to obtain a thread-local RNG.
|
||||||
rng.sample_iter(&Alphanumeric)
|
let mut rng = rand::rng();
|
||||||
.take(length)
|
|
||||||
.map(char::from)
|
// Generate `length` alphanumeric characters.
|
||||||
|
(0..length)
|
||||||
|
.map(|_| {
|
||||||
|
// `Alphanumeric` implements the `Distribution<u8>` trait.
|
||||||
|
// Use the fully qualified `rand::Rng::sample` method to avoid needing an explicit import.
|
||||||
|
let byte = rand::Rng::sample(&mut rng, Alphanumeric);
|
||||||
|
char::from(byte)
|
||||||
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue