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
+2 -1
View File
@@ -77,6 +77,7 @@ impl RpcHandler {
method: &str,
params: &[serde_json::Value],
) -> Result<T> {
let (rpc_user, rpc_pass) = crate::bitcoin_rpc::bitcoin_rpc_credentials().await;
let body = serde_json::json!({
"jsonrpc": "1.0",
"id": "archy",
@@ -86,7 +87,7 @@ impl RpcHandler {
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
+18 -13
View File
@@ -193,12 +193,15 @@ impl RpcHandler {
"--restart=unless-stopped", // Auto-restart policy
];
// Read Bitcoin RPC password from secrets for container configs
let rpc_pass = crate::bitcoin_rpc::bitcoin_rpc_password().await;
// App-specific configuration (should come from manifest)
let (mut ports, mut volumes, env_vars, custom_command, mut custom_args) = {
let mut allocator = self.port_allocator.lock().map_err(|e| {
anyhow::anyhow!("Port allocator lock poisoned: {}", e)
})?;
get_app_config(package_id, &self.config.host_ip, &mut allocator)
get_app_config(package_id, &self.config.host_ip, &mut allocator, &rpc_pass)
};
// Fedimint Gateway: auto-detect LND and switch to lnd mode
@@ -222,7 +225,7 @@ impl RpcHandler {
"--network".to_string(), "bitcoin".to_string(),
"--bitcoind-url".to_string(), format!("http://{}:8332", self.config.host_ip),
"--bitcoind-username".to_string(), "archipelago".to_string(),
"--bitcoind-password".to_string(), "archipelago123".to_string(),
"--bitcoind-password".to_string(), rpc_pass.clone(),
"lnd".to_string(),
"--lnd-rpc-host".to_string(), format!("{}:10009", self.config.host_ip),
"--lnd-tls-cert".to_string(), "/lnd/tls.cert".to_string(),
@@ -305,16 +308,16 @@ impl RpcHandler {
if matches!(package_id, "bitcoin" | "bitcoin-core" | "bitcoin-knots") {
let bitcoin_dir = "/var/lib/archipelago/bitcoin";
let conf_path = format!("{}/bitcoin.conf", bitcoin_dir);
let bitcoin_conf = "\
let bitcoin_conf = format!("\
server=1\n\
prune=550\n\
rpcuser=archipelago\n\
rpcpassword=archipelago123\n\
rpcpassword={}\n\
rpcbind=0.0.0.0\n\
rpcallowip=0.0.0.0/0\n\
rpcport=8332\n\
listen=1\n\
printtoconsole=1\n";
printtoconsole=1\n", rpc_pass);
let _ = tokio::fs::create_dir_all(bitcoin_dir).await;
let _ = tokio::fs::write(&conf_path, bitcoin_conf).await;
info!("Created bitcoin.conf at {} with RPC + txindex enabled", conf_path);
@@ -347,7 +350,7 @@ printtoconsole=1\n";
run_args.push("--cpus=2");
// Health check definitions
let health_args = get_health_check_args(package_id);
let health_args = get_health_check_args(package_id, &rpc_pass);
for arg in &health_args {
run_args.push(arg);
}
@@ -1316,10 +1319,11 @@ fn is_readonly_compatible(app_id: &str) -> bool {
/// Get container health check arguments for podman run.
/// Returns (health-cmd, interval, retries) args to append to run_args.
fn get_health_check_args(app_id: &str) -> Vec<String> {
fn get_health_check_args(app_id: &str, rpc_pass: &str) -> Vec<String> {
let btc_health = format!("bitcoin-cli -rpcuser=archipelago -rpcpassword={} getblockchaininfo || exit 1", rpc_pass);
let (cmd, interval, retries) = match app_id {
"bitcoin" | "bitcoin-core" | "bitcoin-knots" => (
"bitcoin-cli -rpcuser=archipelago -rpcpassword=archipelago123 getblockchaininfo || exit 1",
btc_health.as_str(),
"30s", "3",
),
"lnd" => (
@@ -1462,6 +1466,7 @@ fn get_app_config(
app_id: &str,
host_ip: &str,
allocator: &mut PortAllocator,
rpc_pass: &str,
) -> (Vec<String>, Vec<String>, Vec<String>, Option<String>, Option<Vec<String>>) {
match app_id {
"homeassistant" | "home-assistant" => (
@@ -1495,7 +1500,7 @@ fn get_app_config(
"BTCPAY_CHAINS=btc".to_string(),
format!("BTCPAY_BTCRPCURL=http://{}:8332", host_ip),
"BTCPAY_BTCRPCUSER=archipelago".to_string(),
"BTCPAY_BTCRPCPASSWORD=archipelago123".to_string(),
format!("BTCPAY_BTCRPCPASSWORD={}", rpc_pass),
"BTCPAY_POSTGRES=User ID=btcpay;Password=btcpaypass;Host=archy-btcpay-db;Port=5432;Database=btcpay;Include Error Detail=true".to_string(),
],
None,
@@ -1519,7 +1524,7 @@ fn get_app_config(
format!("CORE_RPC_HOST={}", host_ip),
"CORE_RPC_PORT=8332".to_string(),
"CORE_RPC_USERNAME=archipelago".to_string(),
"CORE_RPC_PASSWORD=archipelago123".to_string(),
format!("CORE_RPC_PASSWORD={}", rpc_pass),
"DATABASE_ENABLED=true".to_string(),
"DATABASE_HOST=archy-mempool-db".to_string(),
"DATABASE_DATABASE=mempool".to_string(),
@@ -1536,7 +1541,7 @@ fn get_app_config(
vec!["50001:50001".to_string()],
vec!["/var/lib/archipelago/electrumx:/data".to_string()],
vec![
format!("DAEMON_URL=http://archipelago:archipelago123@{}:8332/", bitcoin_host),
format!("DAEMON_URL=http://archipelago:{}@{}:8332/", rpc_pass, bitcoin_host),
"COIN=Bitcoin".to_string(),
"DB_DIRECTORY=/data".to_string(),
"SERVICES=tcp://:50001,rpc://0.0.0.0:8000".to_string(),
@@ -1701,7 +1706,7 @@ fn get_app_config(
vec![
"FM_DATA_DIR=/data".to_string(),
"FM_BITCOIND_USERNAME=archipelago".to_string(),
"FM_BITCOIND_PASSWORD=archipelago123".to_string(),
format!("FM_BITCOIND_PASSWORD={}", rpc_pass),
"FM_BITCOIN_NETWORK=bitcoin".to_string(),
"FM_BIND_P2P=0.0.0.0:8173".to_string(),
"FM_BIND_API=0.0.0.0:8174".to_string(),
@@ -1727,7 +1732,7 @@ fn get_app_config(
"--network".to_string(), "bitcoin".to_string(),
"--bitcoind-url".to_string(), format!("http://{}:8332", host_ip),
"--bitcoind-username".to_string(), "archipelago".to_string(),
"--bitcoind-password".to_string(), "archipelago123".to_string(),
"--bitcoind-password".to_string(), rpc_pass.to_string(),
"ldk".to_string(),
"--ldk-lightning-port".to_string(), "9737".to_string(),
"--ldk-alias".to_string(), "archipelago-gateway".to_string(),