diff --git a/platforms/tauri/scripts/install-systemd.sh b/platforms/tauri/scripts/install-systemd.sh new file mode 100755 index 0000000..3868d63 --- /dev/null +++ b/platforms/tauri/scripts/install-systemd.sh @@ -0,0 +1,61 @@ +#!/bin/bash +# install-systemd.sh — Install NetBoozt headless DNS failover as a systemd user service. +# +# Usage: ./install-systemd.sh +# Requires: systemd user session, NetBoozt binary at ~/.local/bin/NetBoozt +# +# By LOUST (www.loust.pro) + +set -e + +BIN_PATH="${HOME}/.local/bin/NetBoozt" +SERVICE_FILE="${HOME}/.config/systemd/user/netboozt-dns.service" + +echo "==> NetBoozt DNS Failover — systemd user service installer" + +# Check binary exists +if [[ ! -x "$BIN_PATH" ]]; then + echo "ERROR: NetBoozt binary not found at $BIN_PATH" + echo "Build it first: cargo build --release --bin netboozt-headless" + exit 1 +fi + +# Check binary is actually the headless one (basic sanity) +if ! "$BIN_PATH" --help >/dev/null 2>&1; then + echo "WARNING: Binary at $BIN_PATH does not respond to --help" +fi + +# Ensure systemd user dir exists +mkdir -p "${HOME}/.config/systemd/user" + +# Install unit file +echo "Installing $SERVICE_FILE ..." +cp "$(dirname "$0")/netboozt-dns.service" "$SERVICE_FILE" +chmod 644 "$SERVICE_FILE" + +# Reload systemd, enable and start +echo "==> Reloading systemd user daemon..." +systemctl --user daemon-reload + +echo "==> Enabling netboozt-dns.service ..." +systemctl --user enable netboozt-dns.service + +echo "==> Starting netboozt-dns.service ..." +systemctl --user start netboozt-dns.service + +# Verify +if systemctl --user is-active --quiet netboozt-dns.service; then + echo "" + echo "✅ netboozt-dns.service is active." + echo "" + echo "Useful commands:" + echo " systemctl --user status netboozt-dns.service # view status" + echo " journalctl --user -u netboozt-dns.service # view logs" + echo " systemctl --user stop netboozt-dns.service # stop" + echo " systemctl --user restart netboozt-dns.service # restart" +else + echo "" + echo "⚠️ Service started but may not be fully active yet. Check:" + echo " systemctl --user status netboozt-dns.service" + echo " journalctl --user -u netboozt-dns.service" +fi diff --git a/platforms/tauri/scripts/netboozt-dns.service b/platforms/tauri/scripts/netboozt-dns.service new file mode 100644 index 0000000..37ab2ac --- /dev/null +++ b/platforms/tauri/scripts/netboozt-dns.service @@ -0,0 +1,27 @@ +[Unit] +Description=NetBoozt DNS Failover — headless DNS intelligence daemon +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +# Runs the GUI binary in headless mode (DNS intelligence only, no window) +ExecStart=/home/lou/.local/bin/NetBoozt --headless +Restart=on-failure +RestartSec=10 +# Graceful stop +TimeoutStopSec=30 + +# Security: run as user, no new privileges +NoNewPrivileges=true + +# Environment +Environment="RUST_LOG=info" + +# Logging +StandardOutput=journal +StandardError=journal +SyslogIdentifier=netboozt-dns + +[Install] +WantedBy=default.target diff --git a/platforms/tauri/scripts/uninstall-systemd.sh b/platforms/tauri/scripts/uninstall-systemd.sh new file mode 100755 index 0000000..d908c36 --- /dev/null +++ b/platforms/tauri/scripts/uninstall-systemd.sh @@ -0,0 +1,36 @@ +#!/bin/bash +# uninstall-systemd.sh — Remove the NetBoozt headless DNS failover systemd user service. +# +# Usage: ./uninstall-systemd.sh +# +# By LOUST (www.loust.pro) + +set -e + +SERVICE_FILE="${HOME}/.config/systemd/user/netboozt-dns.service" + +echo "==> NetBoozt DNS Failover — systemd user service uninstaller" + +# Stop and disable +echo "==> Stopping netboozt-dns.service ..." +systemctl --user stop netboozt-dns.service 2>/dev/null || true + +echo "==> Disabling netboozt-dns.service ..." +systemctl --user disable netboozt-dns.service 2>/dev/null || true + +# Reload systemd +echo "==> Reloading systemd user daemon ..." +systemctl --user daemon-reload + +# Remove unit file +if [[ -f "$SERVICE_FILE" ]]; then + echo "Removing $SERVICE_FILE ..." + rm -f "$SERVICE_FILE" +else + echo "Service file not found at $SERVICE_FILE (already removed or never installed)" +fi + +echo "" +echo "✅ netboozt-dns.service uninstalled." +echo "" +echo "Note: The NetBoozt binary at ~/.local/bin/NetBoozt was not modified." diff --git a/platforms/tauri/src-tauri/Cargo.toml b/platforms/tauri/src-tauri/Cargo.toml index 20297fc..25662f6 100644 --- a/platforms/tauri/src-tauri/Cargo.toml +++ b/platforms/tauri/src-tauri/Cargo.toml @@ -29,6 +29,7 @@ tauri-plugin-fs = "2" tauri-plugin-dialog = "2" tauri-plugin-shell = "2" tauri-plugin-notification = "2" +signal-hook = "0.3" # Windows-specific [target.'cfg(windows)'.dependencies] @@ -43,3 +44,7 @@ windows = { version = "0.52", features = [ [features] # This feature is used for production builds or when a dev server is not specified custom-protocol = ["tauri/custom-protocol"] + +[[bin]] +name = "netboozt-headless" +path = "src/bin/netboozt-headless.rs" diff --git a/platforms/tauri/src-tauri/src/bin/netboozt-headless.rs b/platforms/tauri/src-tauri/src/bin/netboozt-headless.rs new file mode 100644 index 0000000..1bccebe --- /dev/null +++ b/platforms/tauri/src-tauri/src/bin/netboozt-headless.rs @@ -0,0 +1,50 @@ +//! `netboozt-headless` — headless DNS failover daemon. +//! +//! Starts the DNS Intelligence background service and waits for SIGINT/SIGTERM +//! to shut down gracefully. Designed to run as a systemd user service +//! (`netboozt-dns.service`) so DNS failover survives lid-close / logout on Linux. +//! +//! Usage: +//! netboozt-headless +//! +//! The binary reads no configuration — it uses `DnsIntelConfig::default()`. +//! Override env vars (future): `NETBOOZT_CHECK_INTERVAL`, `NETBOOZT_PARALLEL_WORKERS`. + +use std::process; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; +use std::time::Duration; + +fn main() { + // Initialize logger + env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")) + .format_timestamp_millis() + .init(); + + log::info!("netboozt-headless v{} starting", env!("CARGO_PKG_VERSION")); + + // Shared flag for graceful shutdown — Arc allows both handlers to reference it + let running = Arc::new(AtomicBool::new(true)); + let running_sigint = running.clone(); + let running_sigterm = running.clone(); + + // Start DNS Intelligence service + netboozt::start_dns_intelligence(); + + // Register signal handlers for graceful shutdown using signal-hook + signal_hook::flag::register(signal_hook::consts::SIGINT, running_sigint) + .expect("Failed to register SIGINT handler"); + signal_hook::flag::register(signal_hook::consts::SIGTERM, running_sigterm) + .expect("Failed to register SIGTERM handler"); + + // Wait until signaled to stop + while running.load(Ordering::SeqCst) { + std::thread::sleep(Duration::from_secs(1)); + } + + // Graceful shutdown + log::info!("Stopping DNS Intelligence service..."); + netboozt::stop_dns_intelligence(); + log::info!("netboozt-headless stopped"); + process::exit(0); +} diff --git a/platforms/tauri/src-tauri/src/lib.rs b/platforms/tauri/src-tauri/src/lib.rs new file mode 100644 index 0000000..a8f5a0f --- /dev/null +++ b/platforms/tauri/src-tauri/src/lib.rs @@ -0,0 +1,12 @@ +//! NetBoozt Library +//! +//! Re-exports services for use by binaries (main app + headless daemon). +//! +//! By LOUST (www.loust.pro) + +mod services; + +pub use services::{ + diagnostics, dns, dns_intelligence, get_dns_intelligence, notifications, + start_dns_intelligence, stop_dns_intelligence, DnsIntelSummary, DnsMetrics, FailoverEvent, +}; diff --git a/platforms/tauri/src-tauri/src/services/dns_intelligence.rs b/platforms/tauri/src-tauri/src/services/dns_intelligence.rs index 7cba65d..59d2a18 100644 --- a/platforms/tauri/src-tauri/src/services/dns_intelligence.rs +++ b/platforms/tauri/src-tauri/src/services/dns_intelligence.rs @@ -295,11 +295,7 @@ impl DnsIntelligence { s.spawn(move || { chunk .iter() -<<<<<<< HEAD - .map(|(addr, _)| { -======= .map(|(addr, _, _)| { ->>>>>>> 1863ed6 (feat(dns): add tier labels and parallel Workers config to DNS metrics) let (success, ping_ms, resolve_ms) = Self::check_single_dns(addr); (addr.to_string(), success, ping_ms, resolve_ms) }) @@ -888,11 +884,7 @@ mod tests { let results: Vec = DNS_SERVERS .chunks(chunk_size) -<<<<<<< HEAD - .flat_map(|chunk| chunk.iter().map(|(addr, _)| addr.to_string())) -======= .flat_map(|chunk| chunk.iter().map(|(addr, _, _)| addr.to_string())) ->>>>>>> 1863ed6 (feat(dns): add tier labels and parallel Workers config to DNS metrics) .collect(); assert_eq!(results.len(), DNS_SERVERS.len()); @@ -904,11 +896,7 @@ mod tests { let metrics: Arc>> = Arc::new(RwLock::new( DNS_SERVERS .iter() -<<<<<<< HEAD - .map(|(addr, name)| (addr.to_string(), DnsMetrics::new(addr, name))) -======= .map(|(addr, name, tier)| (addr.to_string(), DnsMetrics::new(addr, name, *tier))) ->>>>>>> 1863ed6 (feat(dns): add tier labels and parallel Workers config to DNS metrics) .collect(), )); let history: Arc>> = Arc::new(Mutex::new(Vec::new())); @@ -926,11 +914,7 @@ mod tests { let metrics: Arc>> = Arc::new(RwLock::new( DNS_SERVERS .iter() -<<<<<<< HEAD - .map(|(addr, name)| (addr.to_string(), DnsMetrics::new(addr, name))) -======= .map(|(addr, name, tier)| (addr.to_string(), DnsMetrics::new(addr, name, *tier))) ->>>>>>> 1863ed6 (feat(dns): add tier labels and parallel Workers config to DNS metrics) .collect(), )); let history: Arc>> = Arc::new(Mutex::new(Vec::new())); @@ -967,11 +951,7 @@ mod tests { /// Verify that consecutive_failures increments on failure and resets on success. #[test] fn test_consecutive_failures_behavior() { -<<<<<<< HEAD - let mut metrics = DnsMetrics::new("9.9.9.9", "Quad9"); -======= let mut metrics = DnsMetrics::new("9.9.9.9", "Quad9", 3); ->>>>>>> 1863ed6 (feat(dns): add tier labels and parallel Workers config to DNS metrics) assert_eq!(metrics.consecutive_failures, 0); // Simulate a failed check @@ -993,14 +973,8 @@ mod tests { #[test] fn test_dns_servers_count() { assert_eq!(DNS_SERVERS.len(), 11); -<<<<<<< HEAD - // AdGuard is present - assert!(DNS_SERVERS.contains(&("94.140.14.14", "AdGuard"))); - assert!(DNS_SERVERS.contains(&("94.140.15.15", "AdGuard Secondary"))); -======= // AdGuard is present (3-tuple: addr, name, tier=5) assert!(DNS_SERVERS.contains(&("94.140.14.14", "AdGuard", 5))); assert!(DNS_SERVERS.contains(&("94.140.15.15", "AdGuard Secondary", 5))); ->>>>>>> 1863ed6 (feat(dns): add tier labels and parallel Workers config to DNS metrics) } }