generalbots/src/core/package_manager/alm_setup.rs
Rodrigo Rodriguez (Pragmatismo) 7d8f141fc2
Some checks failed
BotServer CI/CD / build (push) Failing after 1m28s
refactor: Replace all hardcoded ./botserver-stack paths with get_stack_path()/get_work_path()
- Adds get_stack_path() helper: returns /opt/gbo in production (.env without botserver-stack), ./botserver-stack in dev
- Adds get_work_path() helper: returns /opt/gbo/work in production, ./botserver-stack/data/system/work in dev
- Updated 35+ files to use dynamic path resolution
- Production system container no longer needs botserver-stack directory
- Work files go to /opt/gbo/work instead of /opt/gbo/bin/botserver-stack
2026-04-04 09:24:44 -03:00

178 lines
6.3 KiB
Rust

use std::collections::HashMap;
use std::path::PathBuf;
use log::{info, warn};
use super::generate_random_string;
use crate::core::shared::utils::get_stack_path;
pub async fn setup_alm() -> anyhow::Result<()> {
let stack_path_raw = get_stack_path();
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
generate_random_string(40)
}
};
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(&register_output.stderr);
if !err.contains("already registered") && !err.is_empty() {
return Err(anyhow::anyhow!("Runner registration failed: {}", err));
}
}
Ok(())
}