feat: add setup-env command to generate .env from vault container
This commit is contained in:
parent
dfe5162f66
commit
7ef1efa047
2 changed files with 129 additions and 0 deletions
|
|
@ -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):");
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue