use crate::output::CommandSpinner;
use anyhow::{Result, anyhow};
use clap::Args;
use console::style;
use dialoguer::Confirm;
use opencode_cloud_core::config::load_config_or_default;
use opencode_cloud_core::config::paths::{get_config_dir, get_data_dir};
use opencode_cloud_core::docker::{
CONTAINER_NAME, DockerClient, DockerError, container_is_running, remove_all_volumes,
stop_container,
};
use opencode_cloud_core::platform::{get_service_manager, is_service_registration_supported};
#[derive(Args)]
pub struct UninstallArgs {
#[arg(long)]
volumes: bool,
#[arg(long)]
force: bool,
}
pub async fn cmd_uninstall(args: &UninstallArgs, quiet: bool, _verbose: u8) -> Result<()> {
if args.volumes && !args.force {
return Err(anyhow!(
"The --volumes flag requires --force to confirm data deletion.\n\
Run: occ uninstall --volumes --force"
));
}
if !is_service_registration_supported() {
return Err(anyhow!(
"Service registration not supported on this platform.\n\
Supported platforms: Linux (systemd), macOS (launchd)"
));
}
let config = load_config_or_default()?;
let manager = get_service_manager(&config.boot_mode)?;
if !manager.is_installed()? {
if !quiet {
println!("{}", style("Service not installed.").dim());
}
return Ok(()); }
if !args.force {
let confirm = Confirm::new()
.with_prompt("This will remove the service registration. Continue?")
.default(false)
.interact()
.unwrap_or(false);
if !confirm {
if !quiet {
println!("Cancelled.");
}
return Ok(());
}
}
let spinner = CommandSpinner::new_maybe("Stopping service...", quiet);
let _ = stop_container_if_running().await;
spinner.success("Service stopped");
let spinner = CommandSpinner::new_maybe("Removing service registration...", quiet);
let service_file = manager.service_file_path();
manager.uninstall()?;
spinner.success("Service registration removed");
if args.volumes {
let spinner = CommandSpinner::new_maybe("Removing Docker volumes...", quiet);
remove_volumes().await?;
spinner.success("Docker volumes removed");
}
if !quiet {
println!();
println!("Removed: {}", style(service_file.display()).dim());
if args.volumes {
println!("Removed: Docker volumes (all data deleted)");
}
println!();
println!("Service will no longer start automatically.");
let config_dir = get_config_dir()
.map(|p| p.display().to_string())
.unwrap_or_else(|| "~/.config/opencode-cloud".to_string());
let data_dir = get_data_dir()
.map(|p| p.display().to_string())
.unwrap_or_else(|| "~/.local/share/opencode-cloud".to_string());
println!();
println!("Files retained (for reinstall):");
println!(" Config: {}", style(&config_dir).dim());
println!(" Data: {}", style(&data_dir).dim());
println!();
println!("To completely remove all files:");
println!(" rm -rf {config_dir} {data_dir}");
}
Ok(())
}
async fn stop_container_if_running() -> Result<()> {
let client = match DockerClient::new() {
Ok(c) => c,
Err(_) => return Ok(()), };
if client.verify_connection().await.is_err() {
return Ok(()); }
match container_is_running(&client, CONTAINER_NAME).await {
Ok(true) => {
match stop_container(&client, CONTAINER_NAME, Some(30)).await {
Ok(()) => Ok(()),
Err(DockerError::Container(msg)) if msg.contains("is not running") => Ok(()),
Err(e) => Err(e.into()),
}
}
Ok(false) => Ok(()), Err(_) => Ok(()), }
}
async fn remove_volumes() -> Result<()> {
let client = DockerClient::new()?;
client.verify_connection().await?;
remove_all_volumes(&client).await?;
Ok(())
}