Add interactive vault put - prompt for secrets instead of CLI args
All checks were successful
BotServer CI / build (push) Successful in 10m50s
All checks were successful
BotServer CI / build (push) Successful in 10m50s
This commit is contained in:
parent
c0b619b58f
commit
35b793d29c
4 changed files with 88 additions and 27 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -13,4 +13,5 @@ botserver-stack
|
|||
docs/book
|
||||
*.rdb
|
||||
botserver-installers
|
||||
.git-rewrite
|
||||
.git-rewrite
|
||||
vault-unseal-keys
|
||||
|
|
@ -252,8 +252,9 @@ pub async fn run() -> Result<()> {
|
|||
vault_migrate(env_file).await?;
|
||||
}
|
||||
"put" => {
|
||||
if args.len() < 5 {
|
||||
eprintln!("Usage: botserver vault put <path> <key=value> [key=value...]");
|
||||
if args.len() < 4 {
|
||||
eprintln!("Usage: botserver vault put <path> [key=value] [key=value...]");
|
||||
eprintln!(" botserver vault put <path> (interactive mode - prompts for keys)");
|
||||
return Ok(());
|
||||
}
|
||||
let path = &args[3];
|
||||
|
|
@ -610,20 +611,59 @@ async fn vault_put(path: &str, kvs: &[&str]) -> Result<()> {
|
|||
}
|
||||
|
||||
let mut data: HashMap<String, String> = HashMap::new();
|
||||
for kv in kvs {
|
||||
if let Some((k, v)) = kv.split_once('=') {
|
||||
data.insert(k.to_string(), v.to_string());
|
||||
} else {
|
||||
eprintln!("Invalid key=value pair: {}", kv);
|
||||
|
||||
// If no key=value provided, enter interactive mode
|
||||
if kvs.is_empty() {
|
||||
println!("\n=== Interactive Vault Store ===");
|
||||
println!("Path: {}", path);
|
||||
println!("Enter values (press Enter with empty key to finish):\n");
|
||||
|
||||
loop {
|
||||
print!("Key: ");
|
||||
std::io::Write::flush(&mut std::io::stdout())?;
|
||||
let mut key = String::new();
|
||||
std::io::stdin().read_line(&mut key)?;
|
||||
let key = key.trim().to_string();
|
||||
|
||||
if key.is_empty() {
|
||||
break;
|
||||
}
|
||||
|
||||
print!("Value: ");
|
||||
std::io::Write::flush(&mut std::io::stdout())?;
|
||||
let mut value = String::new();
|
||||
std::io::stdin().read_line(&mut value)?;
|
||||
let value = value.trim().to_string();
|
||||
|
||||
if value.is_empty() {
|
||||
eprintln!("Value cannot be empty, skipping '{}'", key);
|
||||
continue;
|
||||
}
|
||||
|
||||
data.insert(key.clone(), value);
|
||||
println!("* Saved '{}'\n", key);
|
||||
}
|
||||
|
||||
if data.is_empty() {
|
||||
return Err(anyhow::anyhow!("No values provided"));
|
||||
}
|
||||
} else {
|
||||
// Command line mode
|
||||
for kv in kvs {
|
||||
if let Some((k, v)) = kv.split_once('=') {
|
||||
data.insert(k.to_string(), v.to_string());
|
||||
} else {
|
||||
eprintln!("Invalid key=value pair: {}", kv);
|
||||
}
|
||||
}
|
||||
|
||||
if data.is_empty() {
|
||||
return Err(anyhow::anyhow!("No valid key=value pairs provided"));
|
||||
}
|
||||
}
|
||||
|
||||
if data.is_empty() {
|
||||
return Err(anyhow::anyhow!("No valid key=value pairs provided"));
|
||||
}
|
||||
|
||||
manager.put_secret(path, data).await?;
|
||||
println!("* Stored {} key(s) at {}", kvs.len(), path);
|
||||
manager.put_secret(path, data.clone()).await?;
|
||||
println!("\n✓ Stored {} key(s) at {}", data.len(), path);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -409,27 +409,52 @@ impl PackageManager {
|
|||
}
|
||||
|
||||
fn assign_static_ip(container_name: &str) -> Result<String> {
|
||||
// Pick a deterministic IP from the last 2 bytes of the MAC address to avoid collisions.
|
||||
// Discover the host bridge gateway and subnet dynamically from the container's default route.
|
||||
// The container already has IPv6 + a link on eth0, so we can read the bridge from the host.
|
||||
let bridge_info = std::process::Command::new("ip")
|
||||
.args(["-4", "addr", "show", "lxdbr0"])
|
||||
.output()
|
||||
.ok()
|
||||
.and_then(|o| {
|
||||
let out = String::from_utf8_lossy(&o.stdout).to_string();
|
||||
// Extract "10.x.x.x/prefix" from "inet 10.x.x.x/24 ..."
|
||||
out.lines()
|
||||
.find(|l| l.contains("inet "))
|
||||
.and_then(|l| l.split_whitespace().nth(1))
|
||||
.map(|s| s.to_string())
|
||||
});
|
||||
|
||||
let (gateway, prefix) = match bridge_info.as_deref().and_then(|cidr| {
|
||||
let (ip, pfx_str) = cidr.split_once('/')?;
|
||||
let parts: Vec<&str> = ip.split('.').collect();
|
||||
let base = format!("{}.{}.{}.", parts[0], parts[1], parts[2]);
|
||||
let pfx = pfx_str.parse::<u8>().ok()?;
|
||||
Some((ip.to_string(), format!("{base}{{octet}}/{pfx}")))
|
||||
}) {
|
||||
Some(pair) => pair,
|
||||
None => return Err(anyhow::anyhow!("Cannot determine lxdbr0 subnet")),
|
||||
};
|
||||
|
||||
// Derive last octet from MAC to avoid collisions (range 100-249).
|
||||
let mac_out = safe_lxc(&["exec", container_name, "--", "cat", "/sys/class/net/eth0/address"]);
|
||||
let last_octet = mac_out
|
||||
let octet = mac_out
|
||||
.and_then(|o| o.status.success().then(|| String::from_utf8_lossy(&o.stdout).trim().to_string()))
|
||||
.and_then(|mac| {
|
||||
let parts: Vec<&str> = mac.split(':').collect();
|
||||
let a = u8::from_str_radix(parts.get(4)?, 16).ok()?;
|
||||
let b = u8::from_str_radix(parts.get(5)?, 16).ok()?;
|
||||
// Map into 100-250 range to avoid gateway (.1) and broadcast (.255)
|
||||
Some(100u16 + ((u16::from(a) * 256 + u16::from(b)) % 150) as u16)
|
||||
Some(100u16 + (u16::from(a) * 256 + u16::from(b)) % 150)
|
||||
})
|
||||
.unwrap_or(100);
|
||||
|
||||
let ip = format!("10.43.228.{last_octet}");
|
||||
let cidr = format!("{ip}/24");
|
||||
let cidr = prefix.replace("{octet}", &octet.to_string());
|
||||
let ip = cidr.split('/').next().unwrap_or("").to_string();
|
||||
|
||||
safe_lxc(&["exec", container_name, "--", "ip", "addr", "add", &cidr, "dev", "eth0"]);
|
||||
safe_lxc(&["exec", container_name, "--", "ip", "route", "add", "default", "via", "10.43.228.1"]);
|
||||
safe_lxc(&["exec", container_name, "--", "ip", "route", "add", "default", "via", &gateway]);
|
||||
safe_lxc(&["exec", container_name, "--", "bash", "-c",
|
||||
&format!("printf 'nameserver 8.8.8.8\\nnameserver 8.8.4.4\\n' > /etc/resolv.conf && \
|
||||
printf '#!/bin/sh\\nip addr add {cidr} dev eth0 2>/dev/null||true\\nip route add default via 10.43.228.1 2>/dev/null||true\\nexit 0\\n' > /etc/rc.local && \
|
||||
printf '#!/bin/sh\\nip addr add {cidr} dev eth0 2>/dev/null||true\\nip route add default via {gateway} 2>/dev/null||true\\nexit 0\\n' > /etc/rc.local && \
|
||||
chmod +x /etc/rc.local && systemctl enable rc-local 2>/dev/null||true")]);
|
||||
|
||||
info!("Assigned static IP {} to container '{}'", ip, container_name);
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
Unseal Key 1: KNMpbHTx0zYKG+P8oKoFNe7KKK7gEFCJ/IeoyXmHJpcb
|
||||
Unseal Key 2: XoTPcUpbE4j5u6AeIjVhzWd1ku4U5NCANlPLf8pJMA3y
|
||||
Unseal Key 3: sGEbMnQxDmS5itqsdNacia+glPKBIzLk2/9jntEkAIwo
|
||||
Unseal Key 4: 9jPawRkzt0nmuR4V+KeecMns2in3pj+fQFqLfsfyimN1
|
||||
Unseal Key 5: JO0Bi3UXibXdBMTcCPNmmghXhLNcV14035KkZhc3kU1j
|
||||
Loading…
Add table
Reference in a new issue