feat: add setup-env command to generate .env from vault container

This commit is contained in:
Rodrigo Rodriguez (Pragmatismo) 2026-03-15 21:15:48 -03:00
parent dfe5162f66
commit 7ef1efa047
2 changed files with 129 additions and 0 deletions

View file

@ -121,6 +121,25 @@ pub async fn run() -> Result<()> {
}
}
}
"setup-env" => {
let vault_container = args.get(2).map(|s| s.as_str()).unwrap_or("vault");
let container_name = vault_container.to_string();
println!("* Generating .env from vault container: {}", container_name);
match PackageManager::generate_env_from_vault(&container_name) {
Ok(env_vars) => {
println!("* Successfully generated .env from vault");
println!("\nGenerated configuration:");
println!("{}", env_vars);
println!("\nYou can now start botserver with: botserver start");
}
Err(e) => {
eprintln!("x Failed to generate .env: {}", e);
return Err(anyhow::anyhow!("Setup env failed: {}", e));
}
}
}
"remove" => {
if args.len() < 3 {
eprintln!("Usage: botserver remove <component> [--container] [--tenant <name>]");
@ -289,6 +308,7 @@ fn print_usage() {
println!(" start Start all installed components");
println!(" stop Stop all components");
println!(" restart Restart all components");
println!(" setup-env Generate .env from running vault container");
println!(" vault <subcommand> Manage Vault secrets");
println!(" rotate-secret <comp> Rotate a component's credentials");
println!(" (tables, drive, cache, email, directory, encryption, jwt)");
@ -299,6 +319,7 @@ fn print_usage() {
println!();
println!("Options:");
println!(" --container Use container mode (LXC)");
println!(" --container-only Create container only, don't complete installation");
println!(" --tenant <name> Specify tenant name");
println!();
println!("Security Protection (requires root):");

View file

@ -525,6 +525,114 @@ impl PackageManager {
Ok(())
}
pub fn generate_env_from_vault(container_name: &str) -> Result<String> {
let container_ip = Self::get_container_ip(container_name)?;
info!("Generating .env from vault at {}", container_ip);
let output = safe_lxc(&[
"exec",
container_name,
"--",
"bash",
"-c",
"cat /opt/gbo/data/core/raft/raft_state 2>/dev/null || echo 'not_initialized'",
]);
let initialized = match output {
Some(o) => o.status.success(),
None => false,
};
if !initialized {
return Err(anyhow::anyhow!(
"Vault in container {} is not initialized. Please initialize it first with: \
lxc exec {} -- /opt/gbo/bin/vault operator init -key-shares=1 -key-threshold=1",
container_name, container_name
));
}
let unseal_output = safe_lxc(&[
"exec",
container_name,
"--",
"bash",
"-c",
"VAULT_ADDR=http://127.0.0.1:8200 vault status -format=json",
]);
let sealed = match unseal_output {
Some(o) if o.status.success() => {
let status: serde_json::Value = serde_json::from_str(
&String::from_utf8_lossy(&o.stdout)
).unwrap_or_default();
status["sealed"].as_bool().unwrap_or(true)
}
_ => true,
};
if sealed {
return Err(anyhow::anyhow!(
"Vault in container {} is sealed. Please unseal it first with: \
lxc exec {} -- /opt/gbo/bin/vault operator unseal <key>",
container_name, container_name
));
}
let token_output = safe_lxc(&[
"exec",
container_name,
"--",
"bash",
"-c",
"cat /root/.vault-token 2>/dev/null || echo ''",
]);
let root_token = match token_output {
Some(o) => String::from_utf8_lossy(&o.stdout).trim().to_string(),
None => String::new(),
};
if root_token.is_empty() {
return Err(anyhow::anyhow!(
"Could not find root token in vault container. Please ensure vault is properly initialized."
));
}
let env_content = format!(
"# Vault Configuration (auto-generated)\nVAULT_ADDR=http://{}:8200\nVAULT_TOKEN={}\n",
container_ip, root_token
);
let env_file = PathBuf::from(".env");
if env_file.exists() {
let existing = std::fs::read_to_string(&env_file)?;
if existing.contains("VAULT_ADDR=") {
info!(".env already contains VAULT_ADDR, updating...");
let updated: String = existing
.lines()
.filter(|line| !line.starts_with("VAULT_ADDR=") && !line.starts_with("VAULT_TOKEN="))
.collect::<Vec<_>>()
.join("\n");
std::fs::write(&env_file, format!("{}\n{}", updated.trim(), env_content))?;
} else {
let mut file = std::fs::OpenOptions::new().append(true).open(&env_file)?;
use std::io::Write;
file.write_all(env_content.as_bytes())?;
}
} else {
std::fs::write(&env_file, env_content.trim_start())?;
}
info!("Generated .env with Vault config from container {}", container_name);
Ok(format!(
"VAULT_ADDR=http://{}:8200\nVAULT_TOKEN={}",
container_ip, root_token
))
}
fn generate_connection_info(
&self,
component: &str,