2026-01-24 22:59:20 +00:00
|
|
|
// Archipelago Bitcoin Node OS - Native Backend
|
|
|
|
|
// Pure Archipelago implementation, no StartOS dependencies
|
|
|
|
|
|
2026-03-21 01:54:35 +00:00
|
|
|
use anyhow::{Context, Result};
|
2026-01-24 22:59:20 +00:00
|
|
|
use std::net::SocketAddr;
|
Update archipelago: API, auth, container, parmanode, performance, security
- API handler, RPC, and server updates
- Auth and coding rules
- Container data manager, dev orchestrator, health monitor, podman client
- Parmanode script runner
- Performance resource manager
- Security container policies and secrets manager
- Add build scripts and documentation
2026-01-27 22:27:17 +00:00
|
|
|
use tracing::info;
|
2026-03-11 11:11:02 +00:00
|
|
|
use tokio::signal;
|
2026-01-24 22:59:20 +00:00
|
|
|
|
|
|
|
|
mod api;
|
|
|
|
|
mod auth;
|
2026-03-02 08:34:13 +00:00
|
|
|
mod backup;
|
2026-03-21 01:54:35 +00:00
|
|
|
mod constants;
|
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>
2026-03-18 00:39:52 +00:00
|
|
|
mod bitcoin_rpc;
|
2026-01-24 22:59:20 +00:00
|
|
|
mod config;
|
2026-03-09 07:43:12 +00:00
|
|
|
mod content_server;
|
2026-03-11 11:11:02 +00:00
|
|
|
mod crash_recovery;
|
2026-03-22 03:30:21 +00:00
|
|
|
mod credentials;
|
2026-03-11 11:11:02 +00:00
|
|
|
mod disk_monitor;
|
|
|
|
|
mod health_monitor;
|
2026-02-17 15:03:34 +00:00
|
|
|
mod electrs_status;
|
2026-01-24 22:59:20 +00:00
|
|
|
mod container;
|
2026-02-17 15:03:34 +00:00
|
|
|
mod port_allocator;
|
2026-01-27 23:06:18 +00:00
|
|
|
mod data_model;
|
2026-03-11 11:11:02 +00:00
|
|
|
mod federation;
|
2026-02-17 15:03:34 +00:00
|
|
|
mod identity;
|
2026-03-09 07:43:12 +00:00
|
|
|
mod identity_manager;
|
2026-03-11 11:11:02 +00:00
|
|
|
mod marketplace;
|
|
|
|
|
mod mesh;
|
|
|
|
|
mod monitoring;
|
2026-03-17 00:03:08 +00:00
|
|
|
mod transport;
|
2026-02-17 15:03:34 +00:00
|
|
|
mod node_message;
|
|
|
|
|
mod nostr_discovery;
|
2026-03-12 00:19:30 +00:00
|
|
|
mod nostr_handshake;
|
2026-02-17 15:03:34 +00:00
|
|
|
mod peers;
|
2026-01-24 22:59:20 +00:00
|
|
|
mod server;
|
2026-03-22 03:30:21 +00:00
|
|
|
mod rate_limit;
|
2026-03-06 03:26:56 +00:00
|
|
|
mod session;
|
2026-01-27 23:06:18 +00:00
|
|
|
mod state;
|
feat: add TOTP 2FA, API key switcher, login progress bar, and alpha hardening plan
- TOTP 2FA: full setup/confirm/disable/login flow with Argon2id + ChaCha20-Poly1305
encrypted secret storage, QR code generation, and bcrypt-hashed backup codes
- API key switcher: OAuth vs personal API key toggle in AIUI chat settings with
status indicator, key validation, and help text
- Login progress bar: server startup detection with health check polling, form
disabled until server is ready
- AI quarantine docs: comprehensive HTML page documenting all 6 security layers
- Settings: AI Data Access permission toggles with per-category control
- Alpha hardening plan: 28-task overnight automation plan across 7 phases
(onboarding, login, app install, AIUI, UI polish, security, ISO build)
- Backlog: node discovery spatial map feature for alpha demo
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 12:23:57 +00:00
|
|
|
mod totp;
|
2026-03-09 07:43:12 +00:00
|
|
|
mod wallet;
|
|
|
|
|
mod names;
|
|
|
|
|
mod network;
|
2026-03-31 01:41:24 +01:00
|
|
|
pub mod seed;
|
2026-03-09 07:43:12 +00:00
|
|
|
mod nostr_relays;
|
|
|
|
|
mod update;
|
2026-03-22 03:30:21 +00:00
|
|
|
mod vpn;
|
2026-03-11 12:55:13 +00:00
|
|
|
mod webhooks;
|
2026-01-24 22:59:20 +00:00
|
|
|
|
|
|
|
|
use config::Config;
|
|
|
|
|
use server::Server;
|
|
|
|
|
|
|
|
|
|
#[tokio::main]
|
|
|
|
|
async fn main() -> Result<()> {
|
2026-03-11 11:11:02 +00:00
|
|
|
let startup_start = std::time::Instant::now();
|
2026-03-21 01:02:16 +00:00
|
|
|
crash_recovery::init_start_time();
|
2026-03-11 11:11:02 +00:00
|
|
|
|
2026-01-24 22:59:20 +00:00
|
|
|
// Initialize tracing
|
|
|
|
|
tracing_subscriber::fmt()
|
|
|
|
|
.with_env_filter(
|
|
|
|
|
tracing_subscriber::EnvFilter::try_from_default_env()
|
|
|
|
|
.unwrap_or_else(|_| "archipelago=debug,info".into()),
|
|
|
|
|
)
|
|
|
|
|
.init();
|
|
|
|
|
|
2026-03-11 11:11:02 +00:00
|
|
|
info!("Starting Archipelago Bitcoin Node OS");
|
2026-01-24 22:59:20 +00:00
|
|
|
|
|
|
|
|
// Load configuration
|
|
|
|
|
let config = Config::load().await?;
|
|
|
|
|
info!("📁 Data directory: {}", config.data_dir.display());
|
|
|
|
|
|
2026-03-14 03:44:33 +00:00
|
|
|
// Write PID marker early so we can detect crashes on next startup
|
|
|
|
|
crash_recovery::write_pid_marker(&config.data_dir).await?;
|
2026-03-11 11:11:02 +00:00
|
|
|
|
2026-03-14 03:44:33 +00:00
|
|
|
// Crash recovery runs in background so health endpoint is available immediately
|
|
|
|
|
{
|
|
|
|
|
let data_dir = config.data_dir.clone();
|
|
|
|
|
tokio::spawn(async move {
|
|
|
|
|
// Check if previous instance shut down cleanly
|
|
|
|
|
match crash_recovery::check_for_crash(&data_dir).await {
|
|
|
|
|
Ok(Some(containers)) => {
|
|
|
|
|
info!("🔧 Recovering {} containers from previous crash...", containers.len());
|
|
|
|
|
let report = crash_recovery::recover_containers(&containers).await;
|
|
|
|
|
info!(
|
|
|
|
|
"🔧 Recovery complete: {}/{} containers restarted (failed: {:?})",
|
|
|
|
|
report.recovered, report.total, report.failed
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
Ok(None) => {}
|
|
|
|
|
Err(e) => {
|
|
|
|
|
tracing::warn!("Crash recovery check failed: {}", e);
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-03-13 03:55:14 +00:00
|
|
|
|
2026-03-14 03:44:33 +00:00
|
|
|
// Start any stopped containers (handles clean reboot)
|
security+feat: v1.3.0 — pentest remediation, container reliability, UI overhaul
Security (33 pentest findings addressed):
- CRITICAL: backend binds 127.0.0.1, path traversal in tor.rs/dwn fixed
- HIGH: federation requires signatures, XSS login redirect, RBAC viewer restricted
- HIGH: tar slip prevention, S3 SSRF validation, backup ID validation
- MEDIUM: remember-me random secret, TOTP session rotation, password re-auth
- LOW: CSP unsafe-inline removed, CORS dev-only, onion/webhook validation
Container reliability:
- Memory limits on all 37 containers (OOM prevention)
- Exited vs stopped state distinction with health-aware status badges
- Crash recovery coordination (no more restart cascade)
- User-stopped tracking survives reboots
- Tiered boot recovery (databases → core → services → apps)
UI:
- Wallet TransactionsModal, health-aware app status badges
- Restart button on containers, exited/crashed red state
- Mesh view overhaul, glass button updates, BaseModal/ToggleSwitch
- Apps sticky header removed, dev faucet, mutable mock wallet
Infrastructure:
- LND REST port 8080 exposed over Tor (LND Connect fix)
- Nginx cookie_session fix, deploy script Tor config updated
- Dev environment: podman auto-start, boot mode simulation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 12:44:31 +00:00
|
|
|
// Skips user-stopped containers, uses tier ordering
|
|
|
|
|
let boot_report = crash_recovery::start_stopped_containers(&data_dir).await;
|
2026-03-14 03:44:33 +00:00
|
|
|
if boot_report.total > 0 {
|
|
|
|
|
info!(
|
|
|
|
|
"🔄 Boot startup: {}/{} containers started (failed: {:?})",
|
|
|
|
|
boot_report.recovered, boot_report.total, boot_report.failed
|
|
|
|
|
);
|
|
|
|
|
}
|
security+feat: v1.3.0 — pentest remediation, container reliability, UI overhaul
Security (33 pentest findings addressed):
- CRITICAL: backend binds 127.0.0.1, path traversal in tor.rs/dwn fixed
- HIGH: federation requires signatures, XSS login redirect, RBAC viewer restricted
- HIGH: tar slip prevention, S3 SSRF validation, backup ID validation
- MEDIUM: remember-me random secret, TOTP session rotation, password re-auth
- LOW: CSP unsafe-inline removed, CORS dev-only, onion/webhook validation
Container reliability:
- Memory limits on all 37 containers (OOM prevention)
- Exited vs stopped state distinction with health-aware status badges
- Crash recovery coordination (no more restart cascade)
- User-stopped tracking survives reboots
- Tiered boot recovery (databases → core → services → apps)
UI:
- Wallet TransactionsModal, health-aware app status badges
- Restart button on containers, exited/crashed red state
- Mesh view overhaul, glass button updates, BaseModal/ToggleSwitch
- Apps sticky header removed, dev faucet, mutable mock wallet
Infrastructure:
- LND REST port 8080 exposed over Tor (LND Connect fix)
- Nginx cookie_session fix, deploy script Tor config updated
- Dev environment: podman auto-start, boot mode simulation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 12:44:31 +00:00
|
|
|
|
|
|
|
|
// Signal to health monitor that boot recovery is done
|
|
|
|
|
crash_recovery::mark_recovery_complete();
|
2026-03-28 11:31:48 +00:00
|
|
|
|
|
|
|
|
// Reconcile containers against canonical specs (fixes config drift)
|
|
|
|
|
crash_recovery::run_boot_reconciliation().await;
|
2026-03-14 03:44:33 +00:00
|
|
|
});
|
|
|
|
|
}
|
2026-03-11 11:11:02 +00:00
|
|
|
|
2026-03-28 13:06:34 +00:00
|
|
|
// Ensure a default user exists so login works after install/onboarding.
|
|
|
|
|
// In production, the default password is "password123" (shown during install).
|
|
|
|
|
// In dev mode, the dev default password is used.
|
2026-03-29 22:44:46 +01:00
|
|
|
// Don't auto-create default user — let onboarding flow handle password setup
|
|
|
|
|
// via auth.setup RPC. The Login page detects is_setup=false and shows
|
|
|
|
|
// "Create Password" form instead of login form.
|
2026-01-27 22:37:08 +00:00
|
|
|
|
2026-01-24 22:59:20 +00:00
|
|
|
// Create server
|
|
|
|
|
let server = Server::new(config.clone()).await?;
|
|
|
|
|
|
|
|
|
|
// Start server
|
|
|
|
|
let addr: SocketAddr = format!("{}:{}", config.bind_host, config.bind_port)
|
|
|
|
|
.parse()
|
2026-03-21 01:54:35 +00:00
|
|
|
.context("Invalid bind address")?;
|
2026-01-24 22:59:20 +00:00
|
|
|
|
2026-03-11 11:11:02 +00:00
|
|
|
// Spawn background update scheduler
|
|
|
|
|
let update_data_dir = config.data_dir.clone();
|
|
|
|
|
tokio::spawn(async move {
|
|
|
|
|
update::run_update_scheduler(update_data_dir).await;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Spawn periodic container snapshot (for crash recovery)
|
|
|
|
|
crash_recovery::spawn_snapshot_task(config.data_dir.clone());
|
|
|
|
|
|
|
|
|
|
// Spawn disk space monitor (warns at 85%, auto-cleans at 90%)
|
|
|
|
|
disk_monitor::spawn_disk_monitor(config.data_dir.clone());
|
|
|
|
|
|
|
|
|
|
let startup_ms = startup_start.elapsed().as_millis();
|
|
|
|
|
info!("Server listening on http://{} (startup: {}ms)", addr, startup_ms);
|
|
|
|
|
info!("RPC API: http://{}/rpc/v1", addr);
|
|
|
|
|
info!("WebSocket: ws://{}/ws", addr);
|
|
|
|
|
|
2026-03-14 02:54:59 +00:00
|
|
|
// Notify systemd that we're ready (Type=notify)
|
2026-03-14 04:30:57 +00:00
|
|
|
// Note: first param `false` keeps NOTIFY_SOCKET so watchdog pings work
|
|
|
|
|
let _ = sd_notify::notify(false, &[sd_notify::NotifyState::Ready]);
|
2026-03-14 02:54:59 +00:00
|
|
|
|
2026-03-14 04:30:57 +00:00
|
|
|
// Spawn systemd watchdog ping (WatchdogSec=300, ping every 120s)
|
2026-03-14 02:54:59 +00:00
|
|
|
tokio::spawn(async {
|
2026-03-14 04:30:57 +00:00
|
|
|
let mut interval = tokio::time::interval(std::time::Duration::from_secs(120));
|
2026-03-14 02:54:59 +00:00
|
|
|
loop {
|
|
|
|
|
interval.tick().await;
|
|
|
|
|
let _ = sd_notify::notify(false, &[sd_notify::NotifyState::Watchdog]);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2026-03-11 11:11:02 +00:00
|
|
|
// Graceful shutdown: wait for SIGTERM or SIGINT
|
2026-03-21 01:54:35 +00:00
|
|
|
let mut sigterm = signal::unix::signal(signal::unix::SignalKind::terminate())
|
|
|
|
|
.context("Failed to register SIGTERM handler")?;
|
|
|
|
|
let shutdown = async move {
|
2026-03-11 11:11:02 +00:00
|
|
|
tokio::select! {
|
|
|
|
|
_ = signal::ctrl_c() => {
|
|
|
|
|
info!("Received SIGINT (Ctrl+C), initiating graceful shutdown...");
|
|
|
|
|
}
|
|
|
|
|
_ = sigterm.recv() => {
|
|
|
|
|
info!("Received SIGTERM, initiating graceful shutdown...");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
server.serve_with_shutdown(addr, shutdown).await?;
|
2026-01-24 22:59:20 +00:00
|
|
|
|
2026-03-11 11:11:02 +00:00
|
|
|
// Clean shutdown: remove PID marker so next startup doesn't trigger recovery
|
|
|
|
|
crash_recovery::remove_pid_marker(&config.data_dir).await;
|
2026-01-24 22:59:20 +00:00
|
|
|
|
2026-03-11 11:11:02 +00:00
|
|
|
info!("Archipelago shut down cleanly");
|
2026-01-24 22:59:20 +00:00
|
|
|
Ok(())
|
|
|
|
|
}
|