feat(install): add --container-only flag to install command

- Add --container-only flag to create container without completing full installation
- Exit immediately after container creation
- Useful for manual setup or debugging installation issues
This commit is contained in:
Rodrigo Rodriguez (Pragmatismo) 2026-03-15 20:49:08 -03:00
parent d1cb6b758c
commit dfe5162f66
2 changed files with 88 additions and 4 deletions

View file

@ -84,7 +84,7 @@ pub async fn run() -> Result<()> {
}
"install" => {
if args.len() < 3 {
eprintln!("Usage: botserver install <component> [--container] [--tenant <name>]");
eprintln!("Usage: botserver install <component> [--container] [--container-only] [--tenant <name>]");
return Ok(());
}
let component = &args[2];
@ -99,17 +99,26 @@ pub async fn run() -> Result<()> {
} else {
InstallMode::Local
};
let container_only = args.contains(&"--container-only".to_string());
let tenant = if let Some(idx) = args.iter().position(|a| a == "--tenant") {
args.get(idx + 1).cloned()
} else {
None
};
let pm = PackageManager::new(mode, tenant)?;
let result = pm.install(component).await?;
println!("* Component '{}' installed successfully", component);
let pm = PackageManager::new(mode.clone(), tenant)?;
let result = if container_only && mode == InstallMode::Container {
Some(pm.install_container_only(component)?)
} else {
pm.install(component).await?
};
if let Some(install_result) = result {
install_result.print();
if container_only {
println!("\n* Container created successfully (--container-only mode)");
println!("* Run without --container-only to complete installation");
}
}
}
"remove" => {

View file

@ -163,6 +163,72 @@ impl PackageManager {
self.run_commands(post_cmds, "local", &component.name)?;
Ok(())
}
pub fn install_container_only(&self, component_name: &str) -> Result<InstallResult> {
let container_name = format!("{}-{}", self.tenant, component_name);
let _ = safe_lxd(&["init", "--auto"]);
let images = [
"ubuntu:24.04",
"ubuntu:22.04",
"images:debian/12",
"images:debian/11",
];
let mut last_error = String::new();
let mut success = false;
for image in &images {
info!("Attempting to create container with image: {}", image);
let output = safe_lxc(&[
"launch",
image,
&container_name,
"-c",
"security.privileged=true",
]);
let output = match output {
Some(o) => o,
None => continue,
};
if output.status.success() {
info!("Successfully created container with image: {}", image);
success = true;
break;
}
last_error = String::from_utf8_lossy(&output.stderr).to_string();
warn!("Failed to create container with {}: {}", image, last_error);
let _ = safe_lxc(&["delete", &container_name, "--force"]);
}
if !success {
return Err(anyhow::anyhow!(
"LXC container creation failed with all images. Last error: {}",
last_error
));
}
std::thread::sleep(std::time::Duration::from_secs(15));
let container_ip = Self::get_container_ip(&container_name)?;
info!("Container '{}' created successfully at IP: {}", container_name, container_ip);
Ok(InstallResult {
component: component_name.to_string(),
container_name: container_name.clone(),
container_ip: container_ip.clone(),
ports: vec![],
env_vars: std::collections::HashMap::new(),
connection_info: format!(
"Container '{}' created successfully at IP: {}\nRun without --container-only to complete installation.",
container_name, container_ip
),
})
}
pub fn install_container(&self, component: &ComponentConfig) -> Result<InstallResult> {
let container_name = format!("{}-{}", self.tenant, component.name);
@ -216,6 +282,15 @@ impl PackageManager {
"mkdir -p /opt/gbo/bin /opt/gbo/data /opt/gbo/conf /opt/gbo/logs",
)?;
self.exec_in_container(
&container_name,
"echo 'nameserver 8.8.8.8' > /etc/resolv.conf",
)?;
self.exec_in_container(
&container_name,
"echo 'nameserver 8.8.4.4' >> /etc/resolv.conf",
)?;
self.exec_in_container(&container_name, "apt-get update -qq")?;
self.exec_in_container(
&container_name,