use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};
pub const DEFAULT_MACHINES_DB_PATH: &str = "machines.json";
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct Config {
#[serde(default)]
pub server: ServerConfig,
#[serde(default)]
pub wol: WolConfig,
#[serde(default)]
pub network: NetworkConfig,
#[serde(default)]
pub storage: StorageConfig,
#[serde(default)]
pub health: HealthConfig,
}
impl Config {
#[allow(dead_code)]
pub fn from_env() -> Result<Self, config::ConfigError> {
config::Config::builder()
.add_source(
config::Environment::with_prefix("WAKEZILLA")
.separator("__")
.try_parsing(true),
)
.build()?
.try_deserialize()
}
}
pub fn config_dir() -> PathBuf {
#[cfg(target_os = "linux")]
{
PathBuf::from("/etc/wakezilla")
}
#[cfg(target_os = "macos")]
{
PathBuf::from("/Library/Application Support/wakezilla")
}
#[cfg(target_os = "windows")]
{
let base = std::env::var("ProgramData").unwrap_or_else(|_| "C:\\ProgramData".to_string());
PathBuf::from(base).join("wakezilla")
}
#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
{
PathBuf::from("/etc/wakezilla")
}
}
pub fn config_path() -> PathBuf {
config_dir().join("config.toml")
}
impl Config {
pub fn save_to(&self, path: &Path) -> Result<(), anyhow::Error> {
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)?;
}
let toml_str = toml::to_string_pretty(self)?;
std::fs::write(path, toml_str)?;
Ok(())
}
pub fn load_from(path: &Path) -> Result<Self, config::ConfigError> {
config::Config::builder()
.add_source(config::File::from(path).required(false))
.add_source(
config::Environment::with_prefix("WAKEZILLA")
.separator("__")
.try_parsing(true),
)
.build()?
.try_deserialize()
}
pub fn load() -> Self {
let path = config_path();
Self::load_from(&path).unwrap_or_else(|e| {
tracing::warn!(
"Failed to load configuration from {}: {} - using defaults",
path.display(),
e
);
Self::default()
})
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerConfig {
#[serde(default = "default_proxy_port")]
pub proxy_port: u16,
#[serde(default = "default_client_port")]
pub client_port: u16,
#[serde(default = "default_health_timeout_secs")]
pub health_timeout_secs: u64,
}
impl Default for ServerConfig {
fn default() -> Self {
Self {
proxy_port: default_proxy_port(),
client_port: default_client_port(),
health_timeout_secs: default_health_timeout_secs(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WolConfig {
#[serde(default = "default_wol_port")]
pub default_port: u16,
#[serde(default = "default_broadcast_ip")]
pub default_broadcast_ip: String,
#[serde(default = "default_wol_packet_count")]
pub default_packet_count: u32,
#[serde(default = "default_wol_packet_sleeptime_ms")]
pub packet_sleeptime_ms: u64,
#[serde(default = "default_wol_wait_secs")]
pub default_wait_secs: u64,
#[serde(default = "default_wol_poll_interval_ms")]
pub default_poll_interval_ms: u64,
#[serde(default = "default_wol_connect_timeout_ms")]
pub default_connect_timeout_ms: u64,
}
impl Default for WolConfig {
fn default() -> Self {
Self {
default_port: default_wol_port(),
default_broadcast_ip: default_broadcast_ip(),
default_packet_count: default_wol_packet_count(),
packet_sleeptime_ms: default_wol_packet_sleeptime_ms(),
default_wait_secs: default_wol_wait_secs(),
default_poll_interval_ms: default_wol_poll_interval_ms(),
default_connect_timeout_ms: default_wol_connect_timeout_ms(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NetworkConfig {
#[serde(default = "default_network_scan_duration_secs")]
pub scan_duration_secs: u64,
#[serde(default = "default_network_read_timeout_secs")]
pub read_timeout_secs: u64,
}
impl Default for NetworkConfig {
fn default() -> Self {
Self {
scan_duration_secs: default_network_scan_duration_secs(),
read_timeout_secs: default_network_read_timeout_secs(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StorageConfig {
#[serde(default = "default_machines_db_path")]
pub machines_db_path: String,
#[serde(default = "default_max_access_records")]
pub max_access_records: usize,
}
impl Default for StorageConfig {
fn default() -> Self {
Self {
machines_db_path: default_machines_db_path(),
max_access_records: default_max_access_records(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HealthConfig {
#[serde(default = "default_health_check_interval_ms")]
pub check_interval_ms: u64,
#[serde(default = "default_proxy_connect_timeout_ms")]
pub proxy_connect_timeout_ms: u64,
#[serde(default = "default_proxy_wol_wait_secs")]
pub proxy_wol_wait_secs: u64,
#[serde(default = "default_system_shutdown_sleep_secs")]
pub system_shutdown_sleep_secs: u64,
#[serde(default = "default_rate_limit_sample_interval_secs")]
pub rate_limit_sample_interval_secs: u64,
}
impl Default for HealthConfig {
fn default() -> Self {
Self {
check_interval_ms: default_health_check_interval_ms(),
proxy_connect_timeout_ms: default_proxy_connect_timeout_ms(),
proxy_wol_wait_secs: default_proxy_wol_wait_secs(),
system_shutdown_sleep_secs: default_system_shutdown_sleep_secs(),
rate_limit_sample_interval_secs: default_rate_limit_sample_interval_secs(),
}
}
}
fn default_proxy_port() -> u16 {
3000
}
fn default_client_port() -> u16 {
3001
}
fn default_health_timeout_secs() -> u64 {
5
}
fn default_wol_port() -> u16 {
9
}
fn default_broadcast_ip() -> String {
"255.255.255.255".into()
}
fn default_wol_packet_count() -> u32 {
3
}
fn default_wol_packet_sleeptime_ms() -> u64 {
50
}
fn default_wol_wait_secs() -> u64 {
90
}
fn default_wol_poll_interval_ms() -> u64 {
1000
}
fn default_wol_connect_timeout_ms() -> u64 {
700
}
fn default_network_scan_duration_secs() -> u64 {
5
}
fn default_network_read_timeout_secs() -> u64 {
2
}
fn default_machines_db_path() -> String {
DEFAULT_MACHINES_DB_PATH.into()
}
fn default_max_access_records() -> usize {
2000
}
fn default_health_check_interval_ms() -> u64 {
30000
}
fn default_proxy_connect_timeout_ms() -> u64 {
1000
}
fn default_proxy_wol_wait_secs() -> u64 {
60
}
fn default_system_shutdown_sleep_secs() -> u64 {
5
}
fn default_rate_limit_sample_interval_secs() -> u64 {
1
}
#[allow(dead_code)]
impl Config {
pub fn get_default_broadcast_addr(&self) -> std::net::Ipv4Addr {
self.wol
.default_broadcast_ip
.parse()
.unwrap_or_else(|_| std::net::Ipv4Addr::new(255, 255, 255, 255))
}
pub fn proxy_connect_timeout(&self) -> std::time::Duration {
std::time::Duration::from_millis(self.health.proxy_connect_timeout_ms)
}
pub fn wol_packet_sleeptime(&self) -> std::time::Duration {
std::time::Duration::from_millis(self.wol.packet_sleeptime_ms)
}
pub fn network_scan_duration(&self) -> std::time::Duration {
std::time::Duration::from_secs(self.network.scan_duration_secs)
}
pub fn network_read_timeout(&self) -> std::time::Duration {
std::time::Duration::from_secs(self.network.read_timeout_secs)
}
pub fn health_check_interval(&self) -> std::time::Duration {
std::time::Duration::from_millis(self.health.check_interval_ms)
}
pub fn system_shutdown_sleep_duration(&self) -> std::time::Duration {
std::time::Duration::from_secs(self.health.system_shutdown_sleep_secs)
}
}