From 8883b4d98d523cebda84fc13dff7cc606e0493fd Mon Sep 17 00:00:00 2001 From: louzt Date: Sat, 27 Jun 2026 22:43:50 -0600 Subject: [PATCH] feat(install): Windows Service wrapper for headless DNS failover Add netboozt-service Windows binary using windows-service crate. - Add windows-service = "0.6" dependency (Windows-only) - Add netboozt-service binary entry in Cargo.toml - Add windows_service/ module with ServiceMain handler - Add install-windows-service.ps1 and uninstall-windows-service.ps1 - Update README with Windows Service instructions (EN + ES) Binary compiles on Linux (stub main), builds on Windows for actual use. --- README.es.md | 18 +++++ README.md | 18 +++++ .../tauri/scripts/install-windows-service.ps1 | 72 +++++++++++++++++ .../scripts/uninstall-windows-service.ps1 | 49 ++++++++++++ platforms/tauri/src-tauri/Cargo.toml | 6 ++ .../src-tauri/src/bin/netboozt-service.rs | 22 ++++++ .../src-tauri/src/bin/windows_service/mod.rs | 79 +++++++++++++++++++ 7 files changed, 264 insertions(+) create mode 100644 platforms/tauri/scripts/install-windows-service.ps1 create mode 100644 platforms/tauri/scripts/uninstall-windows-service.ps1 create mode 100644 platforms/tauri/src-tauri/src/bin/netboozt-service.rs create mode 100644 platforms/tauri/src-tauri/src/bin/windows_service/mod.rs diff --git a/README.es.md b/README.es.md index 87e2710..648eefb 100644 --- a/README.es.md +++ b/README.es.md @@ -150,6 +150,24 @@ journalctl --user -u netboozt-dns.service ./uninstall-systemd.sh ``` +### Servicio DNS en Windows + +Ejecuta el failover DNS como servicio de Windows — sobrevive reinicios y corre sin usuario logueado: + +```powershell +# Compilar (en Windows, con Rust toolchain) +cargo build --release --bin netboozt-service + +# Instalar (como Administrador) +.\platforms\tauri\scripts\install-windows-service.ps1 + +# Estado +Get-Service netboozt-dns + +# Desinstalar (como Administrador) +.\platforms\tauri\scripts\uninstall-windows-service.ps1 +``` + --- ## 🎯 Perfiles diff --git a/README.md b/README.md index e79363f..16943a8 100644 --- a/README.md +++ b/README.md @@ -136,6 +136,24 @@ journalctl --user -u netboozt-dns.service ./uninstall-systemd.sh ``` +### Windows DNS Failover Service + +Run DNS failover as a Windows Service — survives restarts and runs without a logged-in user: + +```powershell +# Build (on Windows, with Rust toolchain) +cargo build --release --bin netboozt-service + +# Install (run as Administrator) +.\platforms\tauri\scripts\install-windows-service.ps1 + +# Status +Get-Service netboozt-dns + +# Uninstall (run as Administrator) +.\platforms\tauri\scripts\uninstall-windows-service.ps1 +``` + --- ## 🎯 Profiles diff --git a/platforms/tauri/scripts/install-windows-service.ps1 b/platforms/tauri/scripts/install-windows-service.ps1 new file mode 100644 index 0000000..ee59ab5 --- /dev/null +++ b/platforms/tauri/scripts/install-windows-service.ps1 @@ -0,0 +1,72 @@ +# install-windows-service.ps1 — Install NetBoozt DNS failover as a Windows Service. +# +# Usage: .\install-windows-service.ps1 +# Requires: Administrator privileges, netboozt-service.exe built. +# +# By LOUST (www.loust.pro) + +param( + [string]$BinaryPath = "$PWD\target\release\netboozt-service.exe" +) + +$ErrorActionPreference = 'Stop' + +Write-Host "==> NetBoozt DNS Failover — Windows Service installer" -ForegroundColor Cyan + +# Check for admin +$isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) +if (-not $isAdmin) { + Write-Error "This script requires Administrator privileges. Run as Admin." + exit 1 +} + +# Find binary +if (-not (Test-Path $BinaryPath)) { + Write-Error "Binary not found at: $BinaryPath" + Write-Host "Build it first: cargo build --release --bin netboozt-service" -ForegroundColor Yellow + exit 1 +} + +$serviceName = "netboozt-dns" + +# Check if already installed +$existing = Get-Service -Name $serviceName -ErrorAction SilentlyContinue +if ($existing) { + Write-Host "Service '$serviceName' already exists. Stopping and removing..." -ForegroundColor Yellow + Stop-Service -Name $serviceName -Force -ErrorAction SilentlyContinue + sc.exe delete $serviceName | Out-Null + Start-Sleep -Seconds 2 +} + +# Create the service +Write-Host "Creating service '$serviceName'..." +$createResult = sc.exe create $serviceName binPath= "$BinaryPath" DisplayName= "NetBoozt DNS Failover" start= auto type= own +if ($LASTEXITCODE -ne 0) { + Write-Error "Failed to create service: $createResult" + exit 1 +} + +# Set recovery options +Write-Host "Configuring recovery options..." +sc.exe failure $serviceName reset= 86400 actions= restart/10000/restart/10000/restart/10000 | Out-Null + +# Start the service +Write-Host "Starting service..." +Start-Service -Name $serviceName -ErrorAction SilentlyContinue + +# Verify +$svc = Get-Service -Name $serviceName +if ($svc.Status -eq 'Running') { + Write-Host "" + Write-Host "✅ Service '$serviceName' is running." -ForegroundColor Green + Write-Host "" + Write-Host "Useful commands:" + Write-Host " Get-Service $serviceName # view status" + Write-Host " sc.exe query $serviceName # detailed status" + Write-Host " Stop-Service $serviceName # stop" + Write-Host " sc.exe delete $serviceName # remove (use uninstall script instead)" +} else { + Write-Host "" + Write-Warning "Service started but may not be fully running yet. Status: $($svc.Status)" + Write-Host "Check: Get-Service $serviceName" +} diff --git a/platforms/tauri/scripts/uninstall-windows-service.ps1 b/platforms/tauri/scripts/uninstall-windows-service.ps1 new file mode 100644 index 0000000..f4d0a39 --- /dev/null +++ b/platforms/tauri/scripts/uninstall-windows-service.ps1 @@ -0,0 +1,49 @@ +# uninstall-windows-service.ps1 — Remove the NetBoozt DNS failover Windows Service. +# +# Usage: .\uninstall-windows-service.ps1 +# Requires: Administrator privileges. +# +# By LOUST (www.loust.pro) + +param( + [switch]$Force +) + +$ErrorActionPreference = 'Stop' + +Write-Host "==> NetBoozt DNS Failover — Windows Service uninstaller" -ForegroundColor Cyan + +$isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) +if (-not $isAdmin) { + Write-Error "This script requires Administrator privileges. Run as Admin." + exit 1 +} + +$serviceName = "netboozt-dns" + +# Check if service exists +$svc = Get-Service -Name $serviceName -ErrorAction SilentlyContinue +if (-not $svc) { + Write-Host "Service '$serviceName' not found. Nothing to uninstall." -ForegroundColor Yellow + exit 0 +} + +# Stop +if ($svc.Status -eq 'Running') { + Write-Host "Stopping service..." + Stop-Service -Name $serviceName -Force -ErrorAction SilentlyContinue + Start-Sleep -Seconds 2 +} + +# Delete +Write-Host "Deleting service..." +$deleteResult = sc.exe delete $serviceName +if ($LASTEXITCODE -ne 0) { + Write-Error "Failed to delete service: $deleteResult" + exit 1 +} + +Write-Host "" +Write-Host "✅ Service '$serviceName' uninstalled." -ForegroundColor Green +Write-Host "" +Write-Host "Note: The netboozt-service.exe binary was not modified." diff --git a/platforms/tauri/src-tauri/Cargo.toml b/platforms/tauri/src-tauri/Cargo.toml index 25662f6..cb31c09 100644 --- a/platforms/tauri/src-tauri/Cargo.toml +++ b/platforms/tauri/src-tauri/Cargo.toml @@ -39,7 +39,9 @@ windows = { version = "0.52", features = [ "Win32_System_Registry", "Win32_NetworkManagement_IpHelper", "Win32_Networking_WinSock", + "Win32_System_Services", ] } +windows-service = "0.6" [features] # This feature is used for production builds or when a dev server is not specified @@ -48,3 +50,7 @@ custom-protocol = ["tauri/custom-protocol"] [[bin]] name = "netboozt-headless" path = "src/bin/netboozt-headless.rs" + +[[bin]] +name = "netboozt-service" +path = "src/bin/netboozt-service.rs" diff --git a/platforms/tauri/src-tauri/src/bin/netboozt-service.rs b/platforms/tauri/src-tauri/src/bin/netboozt-service.rs new file mode 100644 index 0000000..d0809fe --- /dev/null +++ b/platforms/tauri/src-tauri/src/bin/netboozt-service.rs @@ -0,0 +1,22 @@ +//! `netboozt-service` — Windows Service wrapper for DNS failover. +//! +//! Usage (after install): +//! sc start netboozt-dns +//! sc stop netboozt-dns +//! +//! Build on Windows with: +//! cargo build --release --bin netboozt-service + +fn main() { + #[cfg(windows)] + { + netboozt_service_win::run_service(); + } + + #[cfg(not(windows))] + { + eprintln!("netboozt-service is only available on Windows."); + eprintln!("On Linux, use netboozt-headless with systemd instead."); + std::process::exit(1); + } +} diff --git a/platforms/tauri/src-tauri/src/bin/windows_service/mod.rs b/platforms/tauri/src-tauri/src/bin/windows_service/mod.rs new file mode 100644 index 0000000..0ceed53 --- /dev/null +++ b/platforms/tauri/src-tauri/src/bin/windows_service/mod.rs @@ -0,0 +1,79 @@ +//! Windows Service implementation — only compiles on Windows. + +#[cfg(windows)] +pub fn run_service() { + use std::sync::atomic::{AtomicBool, Ordering}; + use std::time::Duration; + + use windows_service::service::{ + ServiceControl, ServiceControlAccept, ServiceExitCode, ServiceState, ServiceStatus, + ServiceType, + }; + use windows_service::service_control_handler::{self, ServiceControlHandlerResult}; + + static STOP_EVENT: AtomicBool = AtomicBool::new(false); + + fn service_main(_arguments: Vec) { + let status_handle = + service_control_handler::register("netboozt-dns", |event| match event { + ServiceControl::Stop => { + STOP_EVENT.store(true, Ordering::SeqCst); + ServiceControlHandlerResult::NoError + } + ServiceControl::Interrogate => ServiceControlHandlerResult::NoError, + _ => ServiceControlHandlerResult::Default, + }) + .expect("Failed to register service control handler"); + + status_handle + .set_service_status(ServiceStatus { + service_type: ServiceType::OWN_PROCESS, + current_state: ServiceState::Running, + controls_accepted: ServiceControlAccept::STOP, + exit_code: ServiceExitCode::Win32(0), + checkpoint: 0, + wait_hint: Duration::default(), + }) + .expect("Failed to set service status"); + + env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")) + .format_timestamp_millis() + .init(); + + log::info!( + "netboozt-service v{} starting as Windows Service", + env!("CARGO_PKG_VERSION") + ); + + std::thread::spawn(|| { + netboozt::start_dns_intelligence(); + }); + + while !STOP_EVENT.load(Ordering::SeqCst) { + std::thread::sleep(Duration::from_secs(1)); + } + + status_handle + .set_service_status(ServiceStatus { + service_type: ServiceType::OWN_PROCESS, + current_state: ServiceState::StopPending, + controls_accepted: ServiceControlAccept::empty(), + exit_code: ServiceExitCode::Win32(0), + checkpoint: 0, + wait_hint: Duration::default(), + }) + .ok(); + + log::info!("Stopping DNS Intelligence service..."); + netboozt::stop_dns_intelligence(); + log::info!("netboozt-service stopped"); + } + + windows_service::define_windows_service!(ffi_service_main, service_main); +} + +#[cfg(not(windows))] +pub fn run_service() { + // Stub: this function is never called on non-Windows platforms. + // The main binary already exits early on non-Windows. +}