feat: Phase 1 — per-installation credential generation, eliminate hardcoded passwords

Generate unique random passwords at first boot for Bitcoin RPC, all database
services (mempool, btcpay, immich, penpot, mysql-root), and Fedimint gateway.
Credentials stored in /var/lib/archipelago/secrets/ with 600 permissions.

Scripts: first-boot-containers.sh, deploy-to-target.sh, deploy-bitcoin-knots.sh,
container-doctor.sh all read from secrets files instead of hardcoded values.

Rust backend: new bitcoin_rpc module reads password from secrets file, env var,
or dev fallback. All .basic_auth() calls and container config strings now use
the shared credential reader instead of hardcoded "archipelago123".

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-18 00:39:52 +00:00
co-authored by Claude Opus 4.6
parent f273816405
commit 809a976960
12 changed files with 1804 additions and 251 deletions
+6 -3
View File
@@ -1301,6 +1301,8 @@ async fn handle_tx_relay_broadcast(
}
};
let (rpc_user, rpc_pass) = crate::bitcoin_rpc::bitcoin_rpc_credentials().await;
// Pre-flight: check if Bitcoin Core is reachable and synced
let preflight_body = serde_json::json!({
"jsonrpc": "1.0",
@@ -1311,7 +1313,7 @@ async fn handle_tx_relay_broadcast(
match client
.post("http://127.0.0.1:8332/")
.basic_auth("archipelago", Some("archipelago123"))
.basic_auth(&rpc_user, Some(&rpc_pass))
.json(&preflight_body)
.send()
.await
@@ -1364,7 +1366,7 @@ async fn handle_tx_relay_broadcast(
let txid = match client
.post("http://127.0.0.1:8332/")
.basic_auth("archipelago", Some("archipelago123"))
.basic_auth(&rpc_user, Some(&rpc_pass))
.json(&body)
.send()
.await
@@ -1522,9 +1524,10 @@ async fn check_tx_confirmations(client: &reqwest::Client, txid: &str) -> anyhow:
"method": "gettransaction",
"params": [txid]
});
let (rpc_user, rpc_pass) = crate::bitcoin_rpc::bitcoin_rpc_credentials().await;
let resp = client
.post("http://127.0.0.1:8332/")
.basic_auth("archipelago", Some("archipelago123"))
.basic_auth(&rpc_user, Some(&rpc_pass))
.json(&body)
.send()
.await?;
+5 -3
View File
@@ -602,12 +602,13 @@ struct BlockHeaderInfo {
}
async fn bitcoin_rpc_getblockcount(client: &reqwest::Client) -> Result<u64> {
let (rpc_user, rpc_pass) = crate::bitcoin_rpc::bitcoin_rpc_credentials().await;
let body = serde_json::json!({
"jsonrpc": "1.0", "id": "mesh", "method": "getblockcount", "params": []
});
let resp: BitcoinRpcResponse<u64> = client
.post("http://127.0.0.1:8332/")
.basic_auth("archipelago", Some("archipelago123"))
.basic_auth(&rpc_user, Some(&rpc_pass))
.json(&body)
.send()
.await
@@ -625,13 +626,14 @@ async fn bitcoin_rpc_getblockheader_by_height(
client: &reqwest::Client,
height: u64,
) -> Result<BlockHeaderInfo> {
let (rpc_user, rpc_pass) = crate::bitcoin_rpc::bitcoin_rpc_credentials().await;
// First get block hash for this height
let body = serde_json::json!({
"jsonrpc": "1.0", "id": "mesh", "method": "getblockhash", "params": [height]
});
let resp: BitcoinRpcResponse<String> = client
.post("http://127.0.0.1:8332/")
.basic_auth("archipelago", Some("archipelago123"))
.basic_auth(&rpc_user, Some(&rpc_pass))
.json(&body)
.send()
.await?
@@ -645,7 +647,7 @@ async fn bitcoin_rpc_getblockheader_by_height(
});
let resp: BitcoinRpcResponse<serde_json::Value> = client
.post("http://127.0.0.1:8332/")
.basic_auth("archipelago", Some("archipelago123"))
.basic_auth(&rpc_user, Some(&rpc_pass))
.json(&body)
.send()
.await?