use std::process::ExitCode;
use anyhow::{Context, Result};
use clap::{Parser, Subcommand};
use running_process::broker::manifest;
use running_process::cleanup::{
actions_json, instances, list, parse_duration_secs, prune, uninstall, verify_basic,
};
#[derive(Parser)]
#[command(
name = "running-process-cleanup",
about = "Inspect and clean running-process v1 CacheManifest registries"
)]
struct Cli {
#[arg(long, global = true)]
registry_dir: Option<std::path::PathBuf>,
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
List {
#[arg(long)]
json: bool,
},
Prune {
#[arg(long)]
dormant_after: Option<String>,
#[arg(long)]
keep_current: bool,
#[arg(long)]
keep_last: Option<usize>,
#[arg(long)]
service: Option<String>,
#[arg(long)]
version: Option<String>,
#[arg(long)]
confirm: bool,
#[arg(long)]
json: bool,
},
Uninstall {
service: String,
#[arg(long)]
keep_config: bool,
#[arg(long)]
confirm: bool,
#[arg(long)]
json: bool,
},
Verify {
#[arg(long)]
json: bool,
},
Instances {
#[arg(long)]
status: bool,
#[arg(long)]
json: bool,
},
}
fn main() -> ExitCode {
match run() {
Ok(()) => ExitCode::SUCCESS,
Err(err) => {
eprintln!("error: {err:#}");
ExitCode::from(1)
}
}
}
fn run() -> Result<()> {
let cli = Cli::parse();
let registry_dir = cli
.registry_dir
.unwrap_or_else(manifest::central_registry_dir);
match cli.command {
Commands::List { json } => {
let manifests = list::list(®istry_dir);
if json {
println!("{}", list::render_json(&manifests));
} else if manifests.is_empty() {
println!("no manifests found in {}", registry_dir.display());
} else {
for manifest in manifests {
println!(
"{} {} roots={} last_active_unix_ms={}",
manifest.service_name,
manifest.service_version,
manifest.roots.len(),
manifest.last_active_unix_ms
);
}
}
}
Commands::Prune {
dormant_after,
keep_current,
keep_last,
service,
version,
confirm,
json,
} => {
let dormant_after_secs = dormant_after
.as_deref()
.map(parse_duration_secs)
.transpose()
.context("invalid --dormant-after")?;
let options = prune::PruneOptions {
dormant_after_secs,
keep_current,
keep_last,
service,
version,
confirm,
};
let actions = prune::run(®istry_dir, &options)?;
if json {
println!("{}", actions_json(1, &actions));
} else {
print_actions(&actions, confirm);
}
}
Commands::Uninstall {
service,
keep_config,
confirm,
json,
} => {
let actions = uninstall::run(®istry_dir, &service, keep_config, confirm)?;
if json {
println!("{}", actions_json(1, &actions));
} else {
print_actions(&actions, confirm);
}
}
Commands::Verify { json } => {
let report = verify_basic::run(®istry_dir);
if json {
println!("{}", verify_basic::render_json(&report));
} else if report.findings.is_empty() {
println!("verified {} manifest(s); no findings", report.scanned);
} else {
for finding in &report.findings {
println!(
"{}: {}: {}",
finding.severity,
finding.path.display(),
finding.message
);
}
}
}
Commands::Instances { status, json } => {
let found = instances::list();
if json {
println!("{}", instances::render_json(&found));
} else if found.is_empty() {
if status {
println!(
"no broker instances found; status aggregation requires Phase 4 broker"
);
} else {
println!("no broker instances found");
}
} else {
for instance in found {
println!("{}", instance.path);
}
}
}
}
Ok(())
}
fn print_actions(actions: &[running_process::cleanup::CleanupAction], confirm: bool) {
if actions.is_empty() {
println!("no matching cache roots");
return;
}
for action in actions {
let verb = if action.skipped {
"skip"
} else if confirm {
"deleted"
} else {
"would delete"
};
if let Some(reason) = &action.skip_reason {
println!("{verb}: {} ({reason})", action.path.display());
} else {
println!("{verb}: {}", action.path.display());
}
}
}