use crate::devices::device::{DeviceId, DeviceRelayID}; use clap::{Parser, Subcommand}; use std::path::{Path, PathBuf}; #[derive(Copy, Clone, Debug)] pub enum ConsumptionHistoryType { GridConsumption, RelayConsumption, } /// Electrical consumption fetcher backend #[derive(Subcommand, Debug, Clone)] pub enum ConsumptionBackend { /// Constant consumption value Constant { /// The constant value to use #[clap(short, long, default_value_t = 500)] value: i32, }, /// Generate random consumption value Random { /// Minimum acceptable generated value #[clap(long, default_value_t = -5000)] min: i32, /// Maximum acceptable generated value #[clap(long, default_value_t = 20000)] max: i32, }, /// Read consumption value in a file, on the filesystem File { /// The path to the file that will be read to process consumption values #[clap(short, long, default_value = "/dev/shm/consumption.txt")] path: String, }, } /// Solar system central backend #[derive(Parser, Debug)] #[command(version, about, long_about = None)] pub struct AppConfig { /// Proxy IP, might end with a star "*" #[clap(short, long, env)] pub proxy_ip: Option, /// Secret key, used to sign some resources. Must be randomly generated #[clap(short = 'S', long, env, default_value = "")] secret: String, /// Specify whether the cookie should be transmitted only over secure connections /// /// This should be always true when running in production mode #[clap(long, env)] pub cookie_secure: bool, /// Unsecure : for development, bypass authentication #[clap(long, env)] pub unsecure_disable_login: bool, /// Admin username #[clap(long, env, default_value = "admin")] pub admin_username: String, /// Admin password #[clap(long, env, default_value = "admin")] pub admin_password: String, /// The port the server will listen to (using HTTPS) #[arg(short, long, env, default_value = "0.0.0.0:8443")] pub listen_address: String, /// The port the server will listen to (using HTTP, for unsecure connections) #[arg(short, long, env, default_value = "0.0.0.0:8080")] pub unsecure_listen_address: String, /// Public server hostname (assuming that the ports used are the same for listen address) #[arg(short('H'), long, env, default_value = "localhost")] pub hostname: String, /// Server storage path #[arg(short, long, env, default_value = "storage")] storage: String, /// The minimal production that must be excluded when selecting relays to turn on #[arg(short('m'), long, env, default_value_t = -500)] pub production_margin: i32, /// Energy refresh operations interval, in seconds #[arg(short('i'), long, env, default_value_t = 25)] pub refresh_interval: u64, /// Energy refresh operations interval, in seconds #[arg(short('f'), long, env, default_value_t = 5)] pub energy_fetch_interval: u64, /// Consumption backend provider #[clap(subcommand)] pub consumption_backend: Option, } lazy_static::lazy_static! { static ref ARGS: AppConfig = { AppConfig::parse() }; } impl AppConfig { /// Get parsed command line arguments pub fn get() -> &'static AppConfig { &ARGS } /// Get app secret pub fn secret(&self) -> &str { let mut secret = self.secret.as_str(); if cfg!(debug_assertions) && secret.is_empty() { secret = "DEBUGKEYDEBUGKEYDEBUGKEYDEBUGKEYDEBUGKEYDEBUGKEYDEBUGKEYDEBUGKEY"; } if secret.is_empty() { panic!("SECRET is undefined or too short (min 64 chars)!") } secret } /// URL for unsecure connections pub fn unsecure_origin(&self) -> String { format!( "http://{}:{}", self.hostname, self.unsecure_listen_address.split_once(':').unwrap().1 ) } /// URL for secure connections pub fn secure_origin(&self) -> String { format!( "https://{}:{}", self.hostname, self.listen_address.split_once(':').unwrap().1 ) } /// Get auth cookie domain pub fn cookie_domain(&self) -> Option { if cfg!(debug_assertions) { let domain = self.secure_origin().split_once("://")?.1.to_string(); Some( domain .split_once(':') .map(|s| s.0) .unwrap_or(&domain) .to_string(), ) } else { // In release mode, the web app is hosted on the same origin as the API None } } /// Get storage path pub fn storage_path(&self) -> PathBuf { Path::new(&self.storage).to_path_buf() } /// Get PKI storage path pub fn pki_path(&self) -> PathBuf { self.storage_path().join("pki") } /// Get PKI root CA cert path pub fn root_ca_cert_path(&self) -> PathBuf { self.pki_path().join("root_ca.crt") } /// Get PKI root CA CRL path pub fn root_ca_crl_path(&self) -> PathBuf { self.pki_path().join("root_ca.crl") } /// Get PKI root CA private key path pub fn root_ca_priv_key_path(&self) -> PathBuf { self.pki_path().join("root_ca.key") } /// Get PKI web CA cert path pub fn web_ca_cert_path(&self) -> PathBuf { self.pki_path().join("web_ca.crt") } /// Get PKI web CA CRL path pub fn web_ca_crl_path(&self) -> PathBuf { self.pki_path().join("web_ca.crl") } /// Get PKI web CA private key path pub fn web_ca_priv_key_path(&self) -> PathBuf { self.pki_path().join("web_ca.key") } /// Get PKI devices CA cert path pub fn devices_ca_cert_path(&self) -> PathBuf { self.pki_path().join("devices_ca.crt") } /// Get PKI devices CA CRL path pub fn devices_ca_crl_path(&self) -> PathBuf { self.pki_path().join("devices_ca.crl") } /// Get PKI devices CA private key path pub fn devices_ca_priv_key_path(&self) -> PathBuf { self.pki_path().join("devices_ca.key") } /// Get PKI server cert path pub fn server_cert_path(&self) -> PathBuf { self.pki_path().join("server.crt") } /// Get PKI server private key path pub fn server_priv_key_path(&self) -> PathBuf { self.pki_path().join("server.key") } /// Get devices configuration storage path pub fn devices_config_path(&self) -> PathBuf { self.storage_path().join("devices") } /// Get device configuration path pub fn device_config_path(&self, id: &DeviceId) -> PathBuf { self.devices_config_path().join(format!("{}.conf", id.0)) } /// Get device certificate path pub fn device_cert_path(&self, id: &DeviceId) -> PathBuf { self.devices_config_path().join(format!("{}.crt", id.0)) } /// Get device CSR path pub fn device_csr_path(&self, id: &DeviceId) -> PathBuf { self.devices_config_path().join(format!("{}.csr", id.0)) } /// Get relays runtime storage path pub fn relays_runtime_stats_storage_path(&self) -> PathBuf { self.storage_path().join("relays_runtime") } /// Get relay runtime stats path for a given relay pub fn relay_runtime_stats_dir(&self, relay_id: DeviceRelayID) -> PathBuf { self.relays_runtime_stats_storage_path() .join(relay_id.0.to_string()) } /// Get relay runtime stats path for a given relay for a given day pub fn relay_runtime_day_file_path(&self, relay_id: DeviceRelayID, day: u64) -> PathBuf { self.relay_runtime_stats_dir(relay_id).join(day.to_string()) } /// Get energy consumption history path pub fn energy_consumption_history(&self) -> PathBuf { self.storage_path().join("consumption_history") } /// Get energy consumption history file path for a given day pub fn energy_consumption_history_day( &self, number: u64, r#type: ConsumptionHistoryType, ) -> PathBuf { self.storage_path() .join("consumption_history") .join(format!( "{number}-{}", match r#type { ConsumptionHistoryType::GridConsumption => "grid", ConsumptionHistoryType::RelayConsumption => "relay-consumption", } )) } /// Get logs directory pub fn logs_dir(&self) -> PathBuf { self.storage_path().join("logs") } /// Get the logs for a given day pub fn log_of_day(&self, day: u64) -> PathBuf { self.logs_dir().join(format!("{day}.log")) } } #[cfg(test)] mod test { use crate::app_config::AppConfig; #[test] fn verify_cli() { use clap::CommandFactory; AppConfig::command().debug_assert() } }