171 lines
5.3 KiB
Rust
Raw Normal View History

2026-01-24 22:59:20 +00:00
// 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;
2026-01-24 22:59:20 +00:00
mod api;
mod auth;
mod backup;
2026-01-24 22:59:20 +00:00
mod config;
mod content_server;
mod crash_recovery;
mod credentials;
mod disk_monitor;
mod health_monitor;
mod electrs_status;
2026-01-24 22:59:20 +00:00
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;
2026-01-24 22:59:20 +00:00
mod server;
mod session;
mod state;
mod totp;
mod wallet;
mod names;
mod network;
mod nostr_relays;
mod update;
mod vpn;
mod webhooks;
2026-01-24 22:59:20 +00:00
2026-01-27 22:37:08 +00:00
use auth::AuthManager;
2026-01-24 22:59:20 +00:00
use config::Config;
use server::Server;
2026-01-27 22:37:08 +00:00
/// Default dev password when auto-creating a user (matches mock-backend).
const DEV_DEFAULT_PASSWORD: &str = "password123";
2026-01-24 22:59:20 +00:00
#[tokio::main]
async fn main() -> Result<()> {
let startup_start = std::time::Instant::now();
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();
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());
// 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
);
}
});
}
2026-01-27 22:37:08 +00:00
// 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);
}
}
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()
.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?;
2026-01-24 22:59:20 +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
info!("Archipelago shut down cleanly");
2026-01-24 22:59:20 +00:00
Ok(())
}