diff --git a/Cargo.lock b/Cargo.lock index f0d5b02..2afbe43 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -118,7 +118,6 @@ version = "1.1.1-rc.2" dependencies = [ "chrono", "confy", - "dotenv", "ipnet", "log", "reqwest", @@ -178,12 +177,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "dotenv" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" - [[package]] name = "encoding_rs" version = "0.8.33" diff --git a/Cargo.toml b/Cargo.toml index f465b78..1da0418 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,5 @@ strum_macros = "^0.24.3" log = "^0.4.20" systemd-journal-logger = "^2.1.1" confy = "^0.5.1" -dotenv = "^0.15.0" ipnet = "^2.9.0" url = "2.5.0" diff --git a/src/cloudflare.rs b/src/cloudflare.rs index 44c938c..700788f 100644 --- a/src/cloudflare.rs +++ b/src/cloudflare.rs @@ -1,5 +1,4 @@ use std::collections::HashMap; -use std::env; use std::env::VarError; use std::error::Error; use std::net::{Ipv4Addr, Ipv6Addr}; @@ -11,7 +10,7 @@ use serde_derive::{Deserialize, Serialize}; use strum_macros::{Display, IntoStaticStr}; use systemd_journal_logger::connected_to_journal; use url::ParseError; -use crate::config::{ZoneConfig, ZoneEntry}; +use crate::config::{AppConfig, ZoneConfig, ZoneEntry}; const API_BASE: &str = "https://api.cloudflare.com/client/v4"; @@ -34,8 +33,8 @@ pub(crate) struct CloudflareZone { } impl CloudflareZone { - pub(crate) fn new(zone: &ZoneConfig) -> Result { - let key = env::var("CF_API_TOKEN")?; + pub(crate) fn new(zone: &ZoneConfig, config: &AppConfig) -> Result { + let key = config.cloudflare_api_token.clone(); Ok(Self { name: zone.name.clone(), email: zone.email.clone(), diff --git a/src/config.rs b/src/config.rs index e7bf385..2652cfa 100644 --- a/src/config.rs +++ b/src/config.rs @@ -140,3 +140,47 @@ impl Default for ZoneConfig { } } +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +pub(crate) struct AppConfig { + #[serde(alias="cf_api_token")] + pub(crate) cloudflare_api_token: String, + pub(crate) check_interval_seconds: Option, + pub(crate) uptime_url: Option, +} + +impl AppConfig { + pub(crate) fn load() -> Result> { + let cfg: Self = match confy::load(env!("CARGO_PKG_NAME"),"config") { + Ok(data) => data, + Err(e) => { + match connected_to_journal() { + true => error!("[ERROR] {e}"), + false => eprintln!("[ERROR] {e}") + } + return Err(Box::new(e)); + } + }; + + if cfg.cloudflare_api_token.is_empty() { + let err_msg = "Cloudflare api token not specified. The app cannot work without this"; + match connected_to_journal() { + true => error!("[ERROR] {err_msg}"), + false => eprintln!("[ERROR] {err_msg}") + } + panic!("{err_msg}"); + } + + Ok(cfg) + } +} + +impl Default for AppConfig { + fn default() -> Self { + Self { + cloudflare_api_token: "".to_owned(), + check_interval_seconds: None, + uptime_url: None + } + } +} + diff --git a/src/main.rs b/src/main.rs index 54bcc8f..f549057 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,16 +1,15 @@ /*use cloudflare_old::{Instance, CloudflareDnsType};*/ use reqwest::blocking::get; -use std::{env, thread::{sleep}}; +use std::{thread::{sleep}}; use std::error::Error; use std::net::{Ipv4Addr, Ipv6Addr}; use std::str::FromStr; use chrono::{Utc, Duration}; -use dotenv::dotenv; use log::{info, warn, error, LevelFilter}; use reqwest::StatusCode; use systemd_journal_logger::{connected_to_journal, JournalLog}; use crate::cloudflare::{CloudflareZone, DnsRecordType}; -use crate::config::{InterfaceConfig, ZoneConfig, ZoneEntry}; +use crate::config::{AppConfig, InterfaceConfig, ZoneConfig, ZoneEntry}; mod config; mod cloudflare; @@ -233,10 +232,10 @@ fn compare_zones(old_zone: &ZoneConfig, new_zone: &ZoneConfig) -> Vec { } fn main() { - dotenv().ok(); JournalLog::new().expect("Systemd-Logger crate error").install().expect("Systemd-Logger crate error"); log::set_max_level(LevelFilter::Info); + let mut config = AppConfig::load().unwrap(); let mut ifaces = InterfaceConfig::load().unwrap(); let mut zone_cfgs = ZoneConfig::load().unwrap(); @@ -248,32 +247,22 @@ fn main() { Err(e) => panic!("{}", e) }; - let reload_interval = match env::var("CHECK_INTERVAL_SECONDS") { - Ok(interval_string) => i64::from_str(&interval_string).unwrap_or_else(|e| { - let warn_msg = format!("Expected integer number, got '{interval_string}'. Defaulting to 60"); - match connected_to_journal() { - true => warn!("[WARN] {warn_msg}"), - false => println!("[WARN] {warn_msg}"), - }; - 60 - }), - Err(_) => { - let warn_msg = "Reload interval env not set, defaulting to 60"; - match connected_to_journal() { - true => warn!("[WARN] {warn_msg}"), - false => println!("[WARN] {warn_msg}"), - } - 60 - }, - }; + let reload_interval = config.check_interval_seconds.unwrap_or_else(|| { + let warn_msg = "Reload interval option not set, defaulting to 60"; + match connected_to_journal() { + true => warn!("[WARN] {warn_msg}"), + false => println!("[WARN] {warn_msg}"), + } + 60 + }) as i64; loop { now = Utc::now(); if now >= start + Duration::seconds(reload_interval) { start = now; - if let Ok(uptime_url) = env::var("UPTIME_URL") { - get(uptime_url); + if let Some(uptime_url) = &config.uptime_url { + let _ = get(uptime_url); } match InterfaceConfig::load() { @@ -353,10 +342,9 @@ fn main() { ifaces = new_cfg } - }, Err(e) => { - let err_msg = format!("Unable to load ínterfaces.toml with error: {}", e); + let err_msg = format!("Unable to load ínterfaces.toml with error: {e}"); match connected_to_journal() { true => error!("[ERROR] {err_msg}"), false => eprintln!("[ERROR] {err_msg}"), @@ -496,7 +484,72 @@ fn main() { } } Err(e) => { - let err_msg = format!("Unable to load from zones.d with error: {}", e); + let err_msg = format!("Unable to load from zones.d with error: {e}"); + match connected_to_journal() { + true => error!("[ERROR] {err_msg}"), + false => eprintln!("[ERROR] {err_msg}"), + } + } + } + + match AppConfig::load() { + Ok(new_cfg) => { + if config != new_cfg { + if config.cloudflare_api_token != new_cfg.cloudflare_api_token { + let info_msg = "API token in config.toml changed"; + match connected_to_journal() { + true => info!("[INFO] {info_msg}"), + false => println!("[INFO] {info_msg}"), + } + } + + if config.check_interval_seconds != new_cfg.check_interval_seconds { + let info_msg = match config.check_interval_seconds { + Some(old_interval) => { + match new_cfg.check_interval_seconds { + Some(new_interval) => format!("Check interval in config.toml changed from {old_interval}s to {new_interval}s"), + None => format!("Check interval in config.toml changed from {old_interval}s to 60s"), + } + }, + None => { + match new_cfg.check_interval_seconds { + Some(new_interval) => format!("Check interval in config.toml changed from 60s to {new_interval}s"), + None => "This is a unicorn error, congratulations.".to_owned(), + } + } + }; + match connected_to_journal() { + true => info!("[INFO] {info_msg}"), + false => println!("[INFO] {info_msg}"), + } + } + + if config.uptime_url != new_cfg.uptime_url { + let info_msg = match &config.uptime_url { + Some(old_url) => { + match &new_cfg.uptime_url { + Some(new_url) => format!("Uptime URL in config.toml changed from '{old_url}' to '{new_url}'"), + None => "Uptime URL in config.toml was removed".to_owned(), + } + }, + None => { + match &new_cfg.uptime_url { + Some(new_url) => format!("Uptime URL '{new_url}' was added to config.toml"), + None => "This is a unicorn error, congratulations.".to_owned(), + } + } + }; + match connected_to_journal() { + true => info!("[INFO] {info_msg}"), + false => println!("[INFO] {info_msg}"), + } + } + + config = new_cfg + } + } + Err(e) => { + let err_msg = format!("Unable to load config.toml with error: {e}"); match connected_to_journal() { true => error!("[ERROR] {err_msg}"), false => eprintln!("[ERROR] {err_msg}"), @@ -506,7 +559,7 @@ fn main() { ips.update(); for zone in &zone_cfgs { - let cf_zone = match CloudflareZone::new(zone) { + let cf_zone = match CloudflareZone::new(zone, &config) { Ok(data) => data, Err(e) => { let err_msg = format!("Cloudflare Token likely not set. Error: {}", e);