// Archipelago Bitcoin Node OS - Native Backend // Pure Archipelago implementation, no StartOS dependencies use anyhow::Result; use std::net::SocketAddr; use tracing::info; use tokio::signal; mod api; mod auth; mod backup; mod config; mod content_server; mod crash_recovery; mod credentials; mod disk_monitor; mod health_monitor; mod electrs_status; mod container; mod port_allocator; mod data_model; mod federation; mod identity; mod identity_manager; mod marketplace; mod mesh; mod monitoring; mod node_message; mod nostr_discovery; mod nostr_handshake; mod peers; mod server; mod session; mod state; mod totp; mod wallet; mod names; mod network; mod nostr_relays; mod update; mod vpn; mod webhooks; use auth::AuthManager; use config::Config; use server::Server; /// Default dev password when auto-creating a user (matches mock-backend). const DEV_DEFAULT_PASSWORD: &str = "password123"; #[tokio::main] async fn main() -> Result<()> { let startup_start = std::time::Instant::now(); // Initialize tracing tracing_subscriber::fmt() .with_env_filter( tracing_subscriber::EnvFilter::try_from_default_env() .unwrap_or_else(|_| "archipelago=debug,info".into()), ) .init(); info!("Starting Archipelago Bitcoin Node OS"); // Load configuration let config = Config::load().await?; info!("📁 Data directory: {}", config.data_dir.display()); // Write PID marker early so we can detect crashes on next startup crash_recovery::write_pid_marker(&config.data_dir).await?; // 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); } } // Start any stopped containers (handles clean reboot) let boot_report = crash_recovery::start_stopped_containers().await; if boot_report.total > 0 { info!( "🔄 Boot startup: {}/{} containers started (failed: {:?})", boot_report.recovered, boot_report.total, boot_report.failed ); } }); } // In dev mode, ensure a default user exists so login works without manual setup if config.dev_mode { let auth = AuthManager::new(config.data_dir.clone()); if !auth.is_setup().await? { auth.setup_user(DEV_DEFAULT_PASSWORD).await?; info!("👤 Created default dev user (password: {})", DEV_DEFAULT_PASSWORD); } } // Create server let server = Server::new(config.clone()).await?; // Start server let addr: SocketAddr = format!("{}:{}", config.bind_host, config.bind_port) .parse() .expect("Invalid bind address"); // 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); // Notify systemd that we're ready (Type=notify) let _ = sd_notify::notify(true, &[sd_notify::NotifyState::Ready]); // Spawn systemd watchdog ping (WatchdogSec=60, ping every 30s) tokio::spawn(async { let mut interval = tokio::time::interval(std::time::Duration::from_secs(30)); loop { interval.tick().await; let _ = sd_notify::notify(false, &[sd_notify::NotifyState::Watchdog]); } }); // Graceful shutdown: wait for SIGTERM or SIGINT let shutdown = async { let mut sigterm = signal::unix::signal(signal::unix::SignalKind::terminate()) .expect("Failed to register SIGTERM handler"); 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?; // Clean shutdown: remove PID marker so next startup doesn't trigger recovery crash_recovery::remove_pid_marker(&config.data_dir).await; info!("Archipelago shut down cleanly"); Ok(()) }