generalbots/src/core/package_manager/mod.rs
Rodrigo Rodriguez (Pragmatismo) fb2e5242da
All checks were successful
BotServer CI/CD / build (push) Successful in 55m52s
fix: Vault seeding, service health checks, and restart idempotency
- Replace hardcoded passwords with generate_random_string() for all Vault-seeded services
- Add valkey-cli, nc to SafeCommand allowlist; fix PATH in all 4 execution methods
- Fix empty Vault KV values ('none' placeholder) preventing 'Failed to parse K=V' errors
- Fix special chars in generated passwords triggering shell injection false positives
- Add ALM app.ini creation with absolute paths for Forgejo CLI
- Increase Qdrant timeout 15s→45s, ALM wait 5s→20s
- Persist file_states and kb_states to disk for .bas/KB idempotency across restarts
- Add duplicate check to use_website registration (debug log for existing)
- Remove dead code (SERVER_START_EPOCH, server_epoch)
- Add generate_random_string() to shared mod.rs, remove duplicates
2026-04-01 12:22:57 -03:00

161 lines
6.6 KiB
Rust

pub mod cache;
pub mod component;
pub mod installer;
pub mod os;
pub mod setup;
pub mod alm_setup;
pub use cache::{CacheResult, DownloadCache};
pub use installer::PackageManager;
pub mod cli;
pub mod facade;
use serde::{Serialize, Deserialize};
use rand::Rng;
/// Generate a cryptographically strong random string for passwords, tokens, etc.
pub fn generate_random_string(length: usize) -> String {
let charset = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
let mut rng = rand::rng();
(0..length)
.map(|_| {
let idx = rng.random_range(0..charset.len());
charset[idx] as char
})
.collect()
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum InstallMode {
Local,
Container,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum OsType {
Linux,
MacOS,
Windows,
}
#[derive(Debug)]
pub struct ComponentInfo {
pub name: &'static str,
pub termination_command: &'static str,
}
pub fn get_all_components() -> Vec<ComponentInfo> {
vec![
ComponentInfo {
name: "tables",
termination_command: "postgres",
},
ComponentInfo {
name: "cache",
termination_command: "redis-server",
},
ComponentInfo {
name: "drive",
termination_command: "minio",
},
ComponentInfo {
name: "llm",
termination_command: "llama-server",
},
]
}
pub use alm_setup::setup_alm;
/// Initialize Directory (Zitadel) with default admin user and OAuth application
/// This should be called after Zitadel has started and is responding
#[cfg(feature = "directory")]
pub async fn setup_directory() -> anyhow::Result<crate::core::package_manager::setup::DirectoryConfig> {
use std::path::PathBuf;
use std::collections::HashMap;
let stack_path = std::env::var("BOTSERVER_STACK_PATH")
.unwrap_or_else(|_| "./botserver-stack".to_string());
let base_url = "http://localhost:8300".to_string();
let config_path = PathBuf::from(&stack_path).join("conf/system/directory_config.json");
// Check if config already exists in Vault first
if let Ok(secrets_manager) = crate::core::secrets::SecretsManager::from_env() {
if secrets_manager.is_enabled() {
if let Ok(secrets) = secrets_manager.get_secret(crate::core::secrets::SecretPaths::DIRECTORY).await {
if let (Some(client_id), Some(client_secret)) = (secrets.get("client_id"), secrets.get("client_secret")) {
// Validate that credentials are real, not placeholders
let is_valid = !client_id.is_empty()
&& !client_secret.is_empty()
&& client_secret != "..."
&& client_id.contains('@') // OAuth client IDs contain @
&& client_secret.len() > 10; // Real secrets are longer than placeholders
if is_valid {
log::info!("Directory already configured with OAuth client in Vault");
// Reconstruct config from Vault
let config = crate::core::package_manager::setup::DirectoryConfig {
base_url: base_url.clone(),
issuer_url: secrets.get("issuer_url").cloned().unwrap_or_else(|| base_url.clone()),
issuer: secrets.get("issuer").cloned().unwrap_or_else(|| base_url.clone()),
client_id: client_id.clone(),
client_secret: client_secret.clone(),
redirect_uri: secrets.get("redirect_uri").cloned().unwrap_or_else(|| "http://localhost:3000/auth/callback".to_string()),
project_id: secrets.get("project_id").cloned().unwrap_or_default(),
api_url: secrets.get("api_url").cloned().unwrap_or_else(|| base_url.clone()),
service_account_key: secrets.get("service_account_key").cloned(),
};
return Ok(config);
}
}
}
}
}
// Check if config already exists with valid OAuth client in file
if config_path.exists() {
if let Ok(content) = std::fs::read_to_string(&config_path) {
if let Ok(config) = serde_json::from_str::<crate::core::package_manager::setup::DirectoryConfig>(&content) {
// Validate that credentials are real, not placeholders
let is_valid = !config.client_id.is_empty()
&& !config.client_secret.is_empty()
&& config.client_secret != "..."
&& config.client_id.contains('@')
&& config.client_secret.len() > 10;
if is_valid {
log::info!("Directory already configured with OAuth client");
return Ok(config);
}
}
}
}
// Initialize directory with default credentials
let mut directory_setup = crate::core::package_manager::setup::DirectorySetup::new(base_url.clone(), config_path.clone());
let config = directory_setup.initialize().await
.map_err(|e| anyhow::anyhow!("Failed to initialize directory: {}", e))?;
// Store credentials in Vault
if let Ok(secrets_manager) = crate::core::secrets::SecretsManager::from_env() {
if secrets_manager.is_enabled() {
let mut secrets = HashMap::new();
secrets.insert("url".to_string(), config.base_url.clone());
secrets.insert("issuer_url".to_string(), config.issuer_url.clone());
secrets.insert("issuer".to_string(), config.issuer.clone());
secrets.insert("client_id".to_string(), config.client_id.clone());
secrets.insert("client_secret".to_string(), config.client_secret.clone());
secrets.insert("redirect_uri".to_string(), config.redirect_uri.clone());
secrets.insert("project_id".to_string(), config.project_id.clone());
secrets.insert("api_url".to_string(), config.api_url.clone());
if let Some(key) = &config.service_account_key {
secrets.insert("service_account_key".to_string(), key.clone());
}
match secrets_manager.put_secret(crate::core::secrets::SecretPaths::DIRECTORY, secrets).await {
Ok(_) => log::info!("Directory credentials stored in Vault"),
Err(e) => log::warn!("Failed to store directory credentials in Vault: {}", e),
}
}
}
Ok(config)
}