use anyhow::{Context, Result}; use serde::{Deserialize, Serialize}; use std::path::{Path, PathBuf}; use tokio::fs; #[derive(Debug, Clone, Serialize, Deserialize)] pub enum ContainerRuntime { Podman, Docker, Auto, } impl ContainerRuntime { pub fn from_str(s: &str) -> Self { match s.to_lowercase().as_str() { "podman" => ContainerRuntime::Podman, "docker" => ContainerRuntime::Docker, "auto" | _ => ContainerRuntime::Auto, } } } #[derive(Debug, Clone, Serialize, Deserialize)] pub enum BitcoinSimulation { Mock, Testnet, Mainnet, None, } impl BitcoinSimulation { pub fn from_str(s: &str) -> Self { match s.to_lowercase().as_str() { "mock" => BitcoinSimulation::Mock, "testnet" => BitcoinSimulation::Testnet, "mainnet" => BitcoinSimulation::Mainnet, "none" | _ => BitcoinSimulation::None, } } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Config { pub data_dir: PathBuf, pub bind_host: String, pub bind_port: u16, pub log_level: String, // Dev mode configuration pub dev_mode: bool, pub container_runtime: ContainerRuntime, pub port_offset: u16, pub bitcoin_simulation: BitcoinSimulation, pub dev_data_dir: PathBuf, } impl Config { pub async fn load() -> Result { // Default configuration let mut config = Self::default(); // Try to load from config file let config_path = Path::new("/etc/archipelago/config.toml"); if config_path.exists() { let content = fs::read_to_string(config_path).await .context("Failed to read config file")?; let file_config: Config = toml::de::from_str(&content) .context("Failed to parse config file")?; config = file_config; } // Override with environment variables if let Ok(data_dir) = std::env::var("ARCHIPELAGO_DATA_DIR") { config.data_dir = PathBuf::from(data_dir); } if let Ok(bind) = std::env::var("ARCHIPELAGO_BIND") { let parts: Vec<&str> = bind.split(':').collect(); if parts.len() == 2 { config.bind_host = parts[0].to_string(); config.bind_port = parts[1].parse() .context("Invalid port in ARCHIPELAGO_BIND")?; } } if let Ok(level) = std::env::var("ARCHIPELAGO_LOG_LEVEL") { config.log_level = level; } // Dev mode configuration if let Ok(dev_mode) = std::env::var("ARCHIPELAGO_DEV_MODE") { config.dev_mode = dev_mode.parse().unwrap_or(false); } if let Ok(runtime) = std::env::var("ARCHIPELAGO_CONTAINER_RUNTIME") { config.container_runtime = ContainerRuntime::from_str(&runtime); } if let Ok(offset) = std::env::var("ARCHIPELAGO_PORT_OFFSET") { config.port_offset = offset.parse() .context("Invalid port offset in ARCHIPELAGO_PORT_OFFSET")?; } if let Ok(sim) = std::env::var("ARCHIPELAGO_BITCOIN_SIMULATION") { config.bitcoin_simulation = BitcoinSimulation::from_str(&sim); } if let Ok(dev_data_dir) = std::env::var("ARCHIPELAGO_DEV_DATA_DIR") { config.dev_data_dir = PathBuf::from(dev_data_dir); } // Ensure data directory exists fs::create_dir_all(&config.data_dir).await .context("Failed to create data directory")?; // Ensure dev data directory exists if in dev mode if config.dev_mode { fs::create_dir_all(&config.dev_data_dir).await .context("Failed to create dev data directory")?; } Ok(config) } } impl Default for Config { fn default() -> Self { Self { data_dir: PathBuf::from("/var/lib/archipelago"), bind_host: "127.0.0.1".to_string(), bind_port: 5959, log_level: "info".to_string(), dev_mode: false, container_runtime: ContainerRuntime::Auto, port_offset: 10000, bitcoin_simulation: BitcoinSimulation::Mock, dev_data_dir: PathBuf::from("/tmp/archipelago-dev"), } } }