fix(install): generate bitcoin RPC password before orchestrator install
Bitcoin containers were exiting in ms after start because the orchestrator install path skipped the credential-materialisation step the legacy path did. resolve_secret_env then failed to read /var/lib/archipelago/secrets/bitcoin-rpc-password, the container started with no password, and bitcoind crashed before logs were useful. Two changes: 1. install.rs — call bitcoin_rpc_credentials() for bitcoin/bitcoin-core/ bitcoin-knots before any install branch runs. The function generates + persists on first call (OnceCell-cached), so this is idempotent. 2. manifest.rs::resolve_secret_env — return ManifestError::Invalid when a resolved secret trims to empty, instead of silently producing `KEY=` env vars that crash auth. Adds a unit test for the empty-secret rejection. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -619,6 +619,14 @@ impl ContainerConfig {
|
||||
let mut out = Vec::with_capacity(self.secret_env.len());
|
||||
for e in &self.secret_env {
|
||||
let v = provider.read(&e.secret_file)?;
|
||||
// An empty secret produces e.g. `-rpcpassword=` and crashes
|
||||
// the container on auth before logs are useful. Fail loud.
|
||||
if v.trim().is_empty() {
|
||||
return Err(ManifestError::Invalid(format!(
|
||||
"secret_env {} resolved to empty value (file: {})",
|
||||
e.key, e.secret_file
|
||||
)));
|
||||
}
|
||||
out.push(format!("{}={}", e.key, v));
|
||||
}
|
||||
Ok(out)
|
||||
@@ -1051,6 +1059,36 @@ app:
|
||||
assert_eq!(out[1], "FM_GATEWAY_PASSWORD=supersecret2");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_secret_env_rejects_empty_value() {
|
||||
let c = ContainerConfig {
|
||||
image: Some("x:latest".to_string()),
|
||||
image_signature: None,
|
||||
pull_policy: "if-not-present".to_string(),
|
||||
build: None,
|
||||
network: None,
|
||||
custom_args: vec![],
|
||||
entrypoint: None,
|
||||
derived_env: vec![],
|
||||
secret_env: vec![SecretEnv {
|
||||
key: "BITCOIN_RPC_PASS".to_string(),
|
||||
secret_file: "bitcoin-rpc-password".to_string(),
|
||||
}],
|
||||
data_uid: None,
|
||||
};
|
||||
let p = MapSecretsProvider {
|
||||
data: HashMap::from([("bitcoin-rpc-password".to_string(), " \n".to_string())]),
|
||||
};
|
||||
let err = c.resolve_secret_env(&p).unwrap_err();
|
||||
match err {
|
||||
ManifestError::Invalid(msg) => assert!(
|
||||
msg.contains("BITCOIN_RPC_PASS") && msg.contains("bitcoin-rpc-password"),
|
||||
"msg should name the env key + file: {msg}"
|
||||
),
|
||||
other => panic!("expected Invalid, got {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_every_real_manifest() {
|
||||
let app_manifests = list_repo_manifests();
|
||||
|
||||
Reference in New Issue
Block a user