fix(bitcoin): size prune to the data volume — never full-archive a small disk

write_bitcoin_conf silently produced an unpruned (~810GB and growing)
config regardless of disk: the framework node got a full-archive chain
on a 205GB volume. Volumes under 1.2TB now get prune = 25% of the
volume, clamped to [550MB, 100GB]; bigger volumes keep full archive;
unknown df results stay unpruned (don't surprise big iron). Framework
node switched live to prune=50000.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
archipelago 2026-07-14 10:02:16 -04:00
parent ec6172d7df
commit 9e2350a368

View File

@ -1395,6 +1395,24 @@ impl RpcHandler {
// the bridge get connection refused (fresh-install LND crash-loop +
// bitcoin-rpc 502, seen on the 1.7.99 ISO). The port publish stays
// 127.0.0.1-only on the host, so exposure is unchanged.
// Prune sized to the data volume. A full archive needs ~810 GB and
// grows; silently writing an unpruned config onto a small disk fills
// it mid-IBD (framework node 2026-07-14: unpruned mainnet on a 205 GB
// volume). Volumes with real archival headroom (≥1.2 TB) stay full
// archive; smaller ones get prune = 25% of the volume, clamped to
// [550 MB, 100 GB], leaving room for LND/apps sharing the disk.
let prune_line = match bitcoin_data_volume_gb().await {
Some(total_gb) if total_gb > 0 && total_gb < 1200 => {
let prune_mb = ((total_gb as f64 * 0.25 * 1024.0) as u64).clamp(550, 100_000);
info!(
volume_gb = total_gb,
prune_mb, "Data volume below archival size — enabling sized bitcoin prune"
);
format!("prune={}\n", prune_mb)
}
_ => String::new(),
};
let bitcoin_conf = format!(
"\
# rpcauth: salted hash only - no plaintext password in config or CLI\n\
@ -1405,8 +1423,9 @@ rpcallowip=0.0.0.0/0\n\
listen=1\n\
rpcthreads=16\n\
rpcworkqueue=256\n\
printtoconsole=0\n",
rpcauth_line
printtoconsole=0\n\
{}",
rpcauth_line, prune_line
);
tokio::fs::create_dir_all(bitcoin_dir)
.await
@ -2491,6 +2510,29 @@ async fn wait_for_adopted_container(package_id: &str, container_name: &str) -> R
))
}
/// Total size (GB) of the filesystem holding the bitcoin data dir, via
/// `df -k`. None when df fails (containers, exotic mounts) — callers treat
/// unknown as "don't prune" to preserve archival defaults on big iron.
async fn bitcoin_data_volume_gb() -> Option<u64> {
let target = if std::path::Path::new("/var/lib/archipelago").exists() {
"/var/lib/archipelago"
} else {
"/"
};
let output = tokio::process::Command::new("df")
.args(["-k", target])
.output()
.await
.ok()?;
if !output.status.success() {
return None;
}
let stdout = String::from_utf8_lossy(&output.stdout);
let line = stdout.lines().nth(1)?;
let kb: u64 = line.split_whitespace().nth(1)?.parse().ok()?;
Some(kb / 1024 / 1024)
}
/// One-shot probe: does bitcoind answer an authenticated getblockchaininfo?
/// Works during IBD (the call answers with progress while syncing). Goes via
/// the host-published RPC port, which fails in exactly the same conditions