fix: rpcauth credentials, reboot survival, system Tor for all containers

- Bitcoin RPC: switch to rpcauth (salted hash in bitcoin.conf, no plaintext
  in config or CLI). Password stable across reboots/restarts/deploys.
- Remove daily-reboot-test.sh cron on both servers
- Enable podman-restart.service for container auto-start after reboot
- System Tor: SocksPort 0.0.0.0:9050 with SocksPolicy for container access
- LND: tor.socks=host.containers.internal:9050 (system Tor, not container)
- Bitcoin: -proxy=host.containers.internal:9050 for Tor outbound
- bitcoin_rpc.rs: reads from secrets file, cached, stable credentials
- package.rs: dynamic rpc_user/rpc_pass, rpcauth hash generation
- network.rs: fix missing send_to_peer args (mesh encryption update)
- first-boot-containers.sh: rpcauth generation, system Tor config
- deploy-to-target.sh: rpcauth credentials, LND config migration
- Mesh: encrypted channel message support (ChaCha20-Poly1305 updates)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-20 11:56:20 +00:00
parent b4d204d1d6
commit b31148a8b7
8 changed files with 278 additions and 60 deletions

View File

@@ -1,18 +1,21 @@
//! Shared Bitcoin RPC credential management.
//! Reads credentials from the per-installation secrets file, falling back to
//! environment variables, then a dev-only default.
//! Bitcoin RPC credential management.
//!
//! Uses `rpcauth` in bitcoin.conf (salted hash — no plaintext in config or CLI).
//! The actual password is stored in `/var/lib/archipelago/secrets/bitcoin-rpc-password`
//! and stays stable across reboots, restarts, and deploys.
use tokio::sync::OnceCell;
use tracing::debug;
const SECRETS_PATH: &str = "/var/lib/archipelago/secrets/bitcoin-rpc-password";
const DEFAULT_USER: &str = "archipelago";
const RPC_USER: &str = "archipelago";
static CACHED_PASSWORD: OnceCell<String> = OnceCell::const_new();
/// Read the Bitcoin RPC password from the secrets file, env var, or dev fallback.
/// Read the Bitcoin RPC password from the secrets file.
/// Falls back to env var (dev), then generates and persists a random password.
async fn read_password() -> String {
// 1. Try secrets file (production path)
// 1. Secrets file (production)
if let Ok(pass) = tokio::fs::read_to_string(SECRETS_PATH).await {
let pass = pass.trim().to_string();
if !pass.is_empty() {
@@ -21,7 +24,7 @@ async fn read_password() -> String {
}
}
// 2. Try environment variable
// 2. Environment variable (dev)
if let Ok(pass) = std::env::var("BITCOIN_RPC_PASSWORD") {
if !pass.is_empty() {
debug!("Bitcoin RPC password loaded from env var");
@@ -29,29 +32,31 @@ async fn read_password() -> String {
}
}
// 3. Generate a random password and persist it (first-boot provisioning)
// 3. Generate and persist (first boot)
let random_pass = generate_random_password();
if let Some(parent) = std::path::Path::new(SECRETS_PATH).parent() {
let _ = tokio::fs::create_dir_all(parent).await;
}
match tokio::fs::write(SECRETS_PATH, &random_pass).await {
Ok(_) => {
// Restrict permissions to owner-only
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let _ = std::fs::set_permissions(SECRETS_PATH, std::fs::Permissions::from_mode(0o600));
let _ = std::fs::set_permissions(
SECRETS_PATH,
std::fs::Permissions::from_mode(0o600),
);
}
debug!("Bitcoin RPC password: generated and saved to secrets file");
debug!("Bitcoin RPC password generated and saved");
}
Err(e) => {
tracing::warn!("Failed to save generated Bitcoin RPC password: {} — using ephemeral", e);
tracing::warn!("Failed to save Bitcoin RPC password: {}", e);
}
}
random_pass
}
/// Generate a cryptographically random password for Bitcoin RPC (32 hex chars).
/// Generate a cryptographically random password (32 hex chars).
fn generate_random_password() -> String {
let bytes: [u8; 16] = rand::random();
hex::encode(bytes)
@@ -62,11 +67,16 @@ pub async fn bitcoin_rpc_credentials() -> (String, String) {
let pass = CACHED_PASSWORD
.get_or_init(|| async { read_password().await })
.await;
(DEFAULT_USER.to_string(), pass.clone())
(RPC_USER.to_string(), pass.clone())
}
/// Get the Bitcoin RPC password as a plain string (for config generation).
/// Get the Bitcoin RPC password (for container config generation).
pub async fn bitcoin_rpc_password() -> String {
let (_, pass) = bitcoin_rpc_credentials().await;
pass
}
/// Get the Bitcoin RPC username.
pub async fn bitcoin_rpc_username() -> String {
RPC_USER.to_string()
}