fix(install): auto-clean stuck OTHER-variant bitcoin container
If bitcoin-core was installed but never started (e.g. port 8332 already bound by bitcoin-knots), the container sticks in `created` state forever. The old conflict check refused EVERY future bitcoin install — including re-install of the running variant — leaving no UI path to recovery. Now the check distinguishes states: - missing → no conflict, continue - running → real conflict, refuse install - created/exited/configured/... → stuck; auto-remove and continue Volumes are untouched; only the dead container record goes away. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1799,40 +1799,64 @@ async fn check_bitcoin_implementation_conflict(package_id: &str) -> Result<()> {
|
||||
_ => return Ok(()),
|
||||
};
|
||||
|
||||
let output = tokio::process::Command::new("podman")
|
||||
.args([
|
||||
"ps",
|
||||
"-a",
|
||||
"--format",
|
||||
"{{.Names}}",
|
||||
"--filter",
|
||||
&format!("name=^{}$", other),
|
||||
])
|
||||
// Three cases for the OTHER variant:
|
||||
// - missing → no conflict, continue
|
||||
// - running → real conflict, refuse install
|
||||
// - any other state (created/exited/configured/...) → stuck from a
|
||||
// prior failed install. Auto-remove so reinstall is reachable
|
||||
// without a manual `podman rm`. This is what unblocks the .198
|
||||
// "bitcoin-core stuck in created, port 8332 held by bitcoin-knots"
|
||||
// deadlock that no UI path could exit.
|
||||
let inspect = tokio::process::Command::new("podman")
|
||||
.args(["inspect", other, "--format", "{{.State.Status}}"])
|
||||
.output()
|
||||
.await
|
||||
.context("Failed to check existing Bitcoin node containers")?;
|
||||
|
||||
if String::from_utf8_lossy(&output.stdout).trim().is_empty() {
|
||||
.context("Failed to inspect conflicting Bitcoin container")?;
|
||||
if !inspect.status.success() {
|
||||
return Ok(());
|
||||
}
|
||||
let state = String::from_utf8_lossy(&inspect.stdout).trim().to_string();
|
||||
|
||||
let current = match other {
|
||||
if state == "running" {
|
||||
let current = pretty_bitcoin_name(other);
|
||||
let requested = pretty_bitcoin_name(package_id);
|
||||
return Err(anyhow::anyhow!(
|
||||
"{} is currently running. Stop and uninstall {} before installing {}; both implementations use the same Bitcoin data directory and ports.",
|
||||
current, current, requested
|
||||
));
|
||||
}
|
||||
|
||||
info!(
|
||||
"Removing stuck {} container (state={}) before installing {}",
|
||||
other, state, package_id
|
||||
);
|
||||
install_log(&format!(
|
||||
"INSTALL UNSTUCK: removing {} (state={}) before installing {}",
|
||||
other, state, package_id
|
||||
))
|
||||
.await;
|
||||
let rm = tokio::process::Command::new("podman")
|
||||
.args(["rm", "-f", other])
|
||||
.output()
|
||||
.await
|
||||
.context("Failed to remove stuck Bitcoin container")?;
|
||||
if !rm.status.success() {
|
||||
let stderr = String::from_utf8_lossy(&rm.stderr);
|
||||
return Err(anyhow::anyhow!(
|
||||
"Failed to remove stuck {} container: {}",
|
||||
other,
|
||||
stderr.trim()
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn pretty_bitcoin_name(id: &str) -> &'static str {
|
||||
match id {
|
||||
"bitcoin-core" => "Bitcoin Core",
|
||||
"bitcoin-knots" => "Bitcoin Knots",
|
||||
_ => "another Bitcoin node",
|
||||
};
|
||||
let requested = match package_id {
|
||||
"bitcoin-core" => "Bitcoin Core",
|
||||
"bitcoin-knots" => "Bitcoin Knots",
|
||||
_ => "the requested Bitcoin node",
|
||||
};
|
||||
|
||||
Err(anyhow::anyhow!(
|
||||
"{} is already installed. Stop and uninstall {} before installing {}; both implementations use the same Bitcoin data directory and ports.",
|
||||
current,
|
||||
current,
|
||||
requested
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn orchestrator_install_app_id(package_id: &str) -> &str {
|
||||
|
||||
Reference in New Issue
Block a user