Add per-scanner enable/disable configuration

Each scanner (ARP, mDNS, SSDP/UPnP, DHCP) can now be turned off via an
`enabled` flag in its config section, defaulting to true so existing
deployments are unchanged. A disabled scanner's entry function returns
early and never starts.

Documented in the README options table (also fixing the stale `timings.*`
key names to the actual `arp_scanner.*` keys), and added to the TOML
samples and the NixOS module.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
rzuasti
2026-06-01 19:00:56 -04:00
co-authored by Claude Opus 4.8
parent 4e975598f9
commit 08ee4fc0ea
9 changed files with 160 additions and 30 deletions
+98
View File
@@ -6,6 +6,10 @@ use serde::Deserialize;
// -----------------------------------------------------------
// Configuration structure
fn default_true() -> bool {
true
}
#[derive(Debug, Deserialize, Clone)]
pub struct Database {
pub path: String,
@@ -23,6 +27,8 @@ pub struct Log {
#[derive(Debug, Deserialize, Clone)]
pub struct ArpScanner {
#[serde(default = "default_true")]
pub enabled: bool,
pub wait_between_scans: DurationString,
pub sender_timeout: DurationString,
pub scan_duration: DurationString,
@@ -30,12 +36,15 @@ pub struct ArpScanner {
#[derive(Debug, Deserialize, Clone)]
pub struct MdnsScanner {
#[serde(default = "default_true")]
pub enabled: bool,
pub probe_timeout: DurationString,
}
impl Default for MdnsScanner {
fn default() -> Self {
MdnsScanner {
enabled: true,
probe_timeout: DurationString::try_from("2s".to_string()).unwrap(),
}
}
@@ -43,17 +52,32 @@ impl Default for MdnsScanner {
#[derive(Debug, Deserialize, Clone)]
pub struct SsdpScanner {
#[serde(default = "default_true")]
pub enabled: bool,
pub probe_timeout: DurationString,
}
impl Default for SsdpScanner {
fn default() -> Self {
SsdpScanner {
enabled: true,
probe_timeout: DurationString::try_from("2s".to_string()).unwrap(),
}
}
}
#[derive(Debug, Deserialize, Clone)]
pub struct DhcpScanner {
#[serde(default = "default_true")]
pub enabled: bool,
}
impl Default for DhcpScanner {
fn default() -> Self {
DhcpScanner { enabled: true }
}
}
#[derive(Debug, Deserialize, Clone)]
pub struct Pushover {
pub token: String,
@@ -101,6 +125,8 @@ pub struct Settings {
pub mdns_scanner: MdnsScanner,
#[serde(default)]
pub ssdp_scanner: SsdpScanner,
#[serde(default)]
pub dhcp_scanner: DhcpScanner,
}
// End configuration structure
// -----------------------------------------------------------
@@ -138,3 +164,75 @@ pub fn get_settings() -> &'static Settings {
pub fn init(config_path: String) {
let _ = SETTINGS.set(Settings::new(config_path).unwrap());
}
#[cfg(test)]
mod tests {
use super::*;
use config::{Config, FileFormat};
fn parse(toml: &str) -> Settings {
Config::builder()
.add_source(config::File::from_str(toml, FileFormat::Toml))
.build()
.unwrap()
.try_deserialize()
.unwrap()
}
const BASE_CONFIG: &str = r#"
[database]
path = "./oott.db"
[networking]
[log]
level = "info"
[arp_scanner]
wait_between_scans = "1m"
sender_timeout = "1m"
scan_duration = "2m"
[notifications]
method = "none"
notify_when_not_seen_for = "1w"
[notifications.pushover]
token = ""
user_key = ""
[web_server]
ip_address = "0.0.0.0"
port = 3000
api_key = "test"
"#;
#[test]
fn scanners_enabled_by_default_when_flag_omitted() {
let settings = parse(BASE_CONFIG);
assert!(settings.arp_scanner.enabled);
assert!(settings.mdns_scanner.enabled);
assert!(settings.ssdp_scanner.enabled);
assert!(settings.dhcp_scanner.enabled);
}
#[test]
fn scanners_can_be_disabled() {
let toml = format!(
"{BASE_CONFIG}
[mdns_scanner]
enabled = false
probe_timeout = \"2s\"
[ssdp_scanner]
enabled = false
probe_timeout = \"2s\"
[dhcp_scanner]
enabled = false
"
);
// Disable the ARP scanner via its existing section too.
let toml = toml.replace(
"wait_between_scans = \"1m\"",
"enabled = false\n wait_between_scans = \"1m\"",
);
let settings = parse(&toml);
assert!(!settings.arp_scanner.enabled);
assert!(!settings.mdns_scanner.enabled);
assert!(!settings.ssdp_scanner.enabled);
assert!(!settings.dhcp_scanner.enabled);
}
}