Probe port 53 after disabling Dnscache instead of assuming reboot is
needed. Skip DNS redirect when port is blocked (service does it on
first boot). Fix readiness probe: TCP connect to API port instead of
broken UDP send_to that always succeeded.
- Drop the duplicate WINDOWS_SERVICE_NAME constant; call sites use the
single source of truth at windows_service::SERVICE_NAME.
- windows_service_exe_path and service_config_path now compose from
crate::data_dir() instead of re-parsing %PROGRAMDATA% locally.
- Factor the 6× sc.exe invocation boilerplate into a run_sc helper.
- Replace the 200ms try_recv polling loop in the service dispatcher
with a recv_timeout wait — cuts shutdown latency and idle CPU.
- stop_service_scm/delete_service_scm now log warnings instead of
silently swallowing failures, so unexpected errors are visible.
Hooks the service-dispatcher scaffolding from the previous commit to
actually serve DNS, and replaces the HKLM\…\Run login-time autostart
with a proper Windows service created via sc.exe.
**Refactor**
- Extract main.rs's inline server body (~500 lines) into `numa::serve::run`
so both the interactive CLI entry and the service dispatcher drive the
same startup/serve loop. main.rs is now a thin subcommand router.
- main.rs goes sync (no #[tokio::main]); each branch that needs async
builds its own runtime and block_on's. Required so the --service path
can hand off to SCM without fighting tokio for the entry thread.
**Windows service wrapper**
- `numa::windows_service::run_service` now builds a multi-thread tokio
runtime on a dedicated thread and runs `serve::run` inside it. Stop/
Shutdown from SCM aborts the wait loop and reports SERVICE_STOPPED.
- Config path resolves to `%PROGRAMDATA%\numa\numa.toml` when running
under SCM (SYSTEM's cwd is System32, relative paths don't work).
**Install/uninstall**
- `install_windows` now copies numa.exe to a stable
`%PROGRAMDATA%\numa\bin\numa.exe` and registers it via `sc create`
with start=auto, obj=LocalSystem, and a failure policy of
restart/5000/restart/5000/restart/10000. Starts the service
immediately when no reboot is pending.
- `uninstall_windows` stops + deletes the service and removes the
binary copy before restoring DNS.
- Drops the old `register_autostart` / `remove_autostart` helpers that
wrote to `HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Run` — that
path runs at user login in the user's session with no stderr capture
and no crash-restart policy, which is why we've been flying blind in
every Windows debug session.
DNS-set bugs (netsh destructive static, IPv6 not touched, uninstall
secondary-drop) and file logging are orthogonal — tracked for follow-up.
Lets numa.exe act as a real Windows service registered with the SCM,
replacing the HKLM\...\Run login-time autostart that runs in the user
session without stderr capture.
- New `numa::windows_service` module (cfg(windows)) wraps Mullvad's
`windows-service` crate: registers with SCM, reports Running, handles
Stop/Shutdown, reports Stopped.
- `numa.exe --service` is the entry point SCM uses
(`sc create … binPath="numa.exe --service"`); interactive invocations
are unchanged.
- Dep is gated `[target.'cfg(windows)'.dependencies]` — zero impact on
macOS/Linux builds or binary size.
Scaffold only. The service currently blocks on an mpsc channel until
Stop arrives; the actual serve loop will hook in once main.rs's inline
server body is extracted into `numa::serve(config_path)` in a follow-up.
This lets `sc start Numa` / `sc stop Numa` be verified end to end today.