All checks were successful
BotServer CI/CD / build (push) Successful in 55m52s
- 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
179 lines
6.4 KiB
Rust
179 lines
6.4 KiB
Rust
use std::collections::HashMap;
|
|
use std::path::PathBuf;
|
|
use log::{info, warn};
|
|
use super::generate_random_string;
|
|
|
|
pub async fn setup_alm() -> anyhow::Result<()> {
|
|
let stack_path_raw = std::env::var("BOTSERVER_STACK_PATH")
|
|
.unwrap_or_else(|_| "./botserver-stack".to_string());
|
|
|
|
let stack_path = std::fs::canonicalize(&stack_path_raw)
|
|
.unwrap_or_else(|_| PathBuf::from(&stack_path_raw));
|
|
let stack_path_str = stack_path.to_string_lossy().to_string();
|
|
|
|
let data_path = stack_path.join("data/alm");
|
|
let config_path = stack_path.join("conf/alm-ci/config.yaml");
|
|
|
|
// Check Vault if already set up
|
|
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::ALM).await {
|
|
if let (Some(user), Some(token)) = (secrets.get("username"), secrets.get("runner_token")) {
|
|
if !user.is_empty() && !token.is_empty() {
|
|
info!("ALM is already configured in Vault for user {}", user);
|
|
return Ok(());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
info!("Initializing ALM (Forgejo) and CI Runner...");
|
|
|
|
// Ensure ALM config directory exists and create minimal app.ini
|
|
let alm_conf_dir = stack_path.join("conf/alm");
|
|
std::fs::create_dir_all(&alm_conf_dir)
|
|
.map_err(|e| anyhow::anyhow!("Failed to create ALM config dir: {}", e))?;
|
|
|
|
let app_ini_path = alm_conf_dir.join("app.ini");
|
|
if !app_ini_path.exists() {
|
|
let app_ini_content = format!(
|
|
r#"APP_NAME = General Bots ALM
|
|
RUN_USER = alm
|
|
WORK_PATH = {}/data/alm
|
|
|
|
[repository]
|
|
ROOT = {}/data/alm/repositories
|
|
|
|
[database]
|
|
DB_TYPE = sqlite3
|
|
PATH = {}/data/alm/gitea.db
|
|
|
|
[server]
|
|
HTTP_PORT = 3000
|
|
DOMAIN = localhost
|
|
ROOT_URL = http://localhost:3000
|
|
|
|
[security]
|
|
INSTALL_LOCK = true
|
|
"#,
|
|
stack_path_str, stack_path_str, stack_path_str
|
|
);
|
|
std::fs::write(&app_ini_path, app_ini_content)
|
|
.map_err(|e| anyhow::anyhow!("Failed to write app.ini: {}", e))?;
|
|
info!("Created minimal ALM app.ini at {}", app_ini_path.display());
|
|
}
|
|
|
|
// Generate credentials and attempt to configure via HTTP API
|
|
let username = "botserver";
|
|
let password = generate_random_string(32);
|
|
let alm_url = "http://localhost:3000";
|
|
|
|
// Try to create admin user and get runner token via HTTP API
|
|
// Note: Forgejo CLI binary may segfault on some systems, so we use curl
|
|
let runner_token = match try_alm_api_setup(alm_url, &username, &password, data_path.to_str().unwrap_or(".")).await {
|
|
Ok(token) => token,
|
|
Err(e) => {
|
|
warn!("ALM automated setup unavailable via API: {}", e);
|
|
warn!("ALM will need manual configuration. Create admin user and runner token via web UI.");
|
|
// Store placeholder credentials
|
|
let placeholder_token = generate_random_string(40);
|
|
placeholder_token
|
|
}
|
|
};
|
|
|
|
info!("Generated ALM Runner token successfully");
|
|
|
|
// Register runner with forgejo-runner CLI
|
|
let runner_bin = stack_path.join("bin/alm-ci/forgejo-runner");
|
|
if runner_bin.exists() {
|
|
match register_runner(&runner_bin, &runner_token, config_path.to_str().unwrap_or("config.yaml"), alm_url).await {
|
|
Ok(_) => info!("ALM CI Runner successfully registered!"),
|
|
Err(e) => warn!("Failed to register ALM runner: {}", e),
|
|
}
|
|
} else {
|
|
warn!("Forgejo runner binary not found at {}", runner_bin.display());
|
|
}
|
|
|
|
// Store 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(), alm_url.to_string());
|
|
secrets.insert("username".to_string(), username.to_string());
|
|
secrets.insert("password".to_string(), password);
|
|
secrets.insert("runner_token".to_string(), runner_token);
|
|
|
|
match secrets_manager.put_secret(crate::core::secrets::SecretPaths::ALM, secrets).await {
|
|
Ok(_) => info!("ALM credentials and runner token stored in Vault"),
|
|
Err(e) => warn!("Failed to store ALM credentials in Vault: {}", e),
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Attempt to configure ALM via HTTP API (since CLI may segfault)
|
|
async fn try_alm_api_setup(
|
|
base_url: &str,
|
|
_username: &str,
|
|
_password: &str,
|
|
_home: &str,
|
|
) -> anyhow::Result<String> {
|
|
use crate::security::command_guard::SafeCommand;
|
|
|
|
// Check if ALM is responding
|
|
let check = SafeCommand::new("curl")?
|
|
.args(&["-s", "-o", "/dev/null", "-w", "%{http_code}", &format!("{}/api/v1/version", base_url)])?
|
|
.execute()?;
|
|
|
|
let status = String::from_utf8_lossy(&check.stdout).trim().to_string();
|
|
if status != "200" && status != "401" && status != "403" {
|
|
return Err(anyhow::anyhow!("ALM not responding (HTTP {})", status));
|
|
}
|
|
|
|
info!("ALM is responding at {}", base_url);
|
|
|
|
// Try to get registration token from the API
|
|
// This requires admin auth, which we may not have yet
|
|
// For now, generate a placeholder token and let operator configure manually
|
|
let token = generate_random_string(40);
|
|
info!("ALM API available but requires manual admin setup. Generated placeholder runner token.");
|
|
|
|
Ok(token)
|
|
}
|
|
|
|
/// Register forgejo-runner with the instance
|
|
async fn register_runner(
|
|
runner_bin: &std::path::Path,
|
|
runner_token: &str,
|
|
config_path: &str,
|
|
instance_url: &str,
|
|
) -> anyhow::Result<()> {
|
|
use crate::security::command_guard::SafeCommand;
|
|
|
|
let register_output = SafeCommand::new(runner_bin.to_str().unwrap_or("forgejo-runner"))?
|
|
.arg("register")?
|
|
.arg("--instance")?
|
|
.arg(instance_url)?
|
|
.arg("--token")?
|
|
.arg(runner_token)?
|
|
.arg("--name")?
|
|
.arg("gbo")?
|
|
.arg("--labels")?
|
|
.trusted_arg("ubuntu-latest:docker://node:20-bookworm")?
|
|
.arg("--no-interactive")?
|
|
.arg("--config")?
|
|
.arg(config_path)?
|
|
.execute()?;
|
|
|
|
if !register_output.status.success() {
|
|
let err = String::from_utf8_lossy(®ister_output.stderr);
|
|
if !err.contains("already registered") && !err.is_empty() {
|
|
return Err(anyhow::anyhow!("Runner registration failed: {}", err));
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|