feat(windows): run as a real SCM service, not a Run-key autostart #107

Merged
razvandimescu merged 16 commits from feat/windows-service into main 2026-04-17 07:02:43 +08:00
Showing only changes of commit 9f08d8b489 - Show all commits

View File

@@ -679,15 +679,15 @@ fn install_windows() -> Result<(), String> {
std::fs::write(&path, json).map_err(|e| format!("failed to write backup: {}", e))?; std::fs::write(&path, json).map_err(|e| format!("failed to write backup: {}", e))?;
} }
let needs_reboot = disable_dnscache()?;
// On re-install, stop the running service first so the binary can be // On re-install, stop the running service first so the binary can be
// overwritten (SCM holds a handle to the exe while it's running). // overwritten and port 53 is released for the Dnscache probe.
let reinstall = is_service_registered(); if is_service_registered() {
if reinstall { eprintln!(" Stopping existing service...");
stop_service_scm(); stop_service_scm();
} }
let needs_reboot = disable_dnscache()?;
// Copy the binary to a stable path under ProgramData and register it // Copy the binary to a stable path under ProgramData and register it
// as a real Windows service (SCM-managed, boot-time, auto-restart). // as a real Windows service (SCM-managed, boot-time, auto-restart).
let service_exe = install_service_binary()?; let service_exe = install_service_binary()?;
@@ -880,14 +880,24 @@ fn start_service_scm() -> Result<(), String> {
Ok(()) Ok(())
} }
/// Stop the service. Idempotent — already-stopped or missing service logs /// Stop the service and wait for it to fully exit. Idempotent —
/// a warning but doesn't error, since both callers (install re-run, /// already-stopped or missing service is not an error.
/// uninstall) want best-effort cleanup rather than hard failure.
#[cfg(windows)] #[cfg(windows)]
fn stop_service_scm() { fn stop_service_scm() {
if let Err(e) = run_sc(&["stop", crate::windows_service::SERVICE_NAME]) { let name = crate::windows_service::SERVICE_NAME;
log::warn!("sc stop failed: {}", e); let _ = run_sc(&["stop", name]);
// Wait up to 10s for the service to reach STOPPED state so the
// binary file handle is released before we try to overwrite it.
for _ in 0..20 {
if let Ok(out) = run_sc(&["query", name]) {
let text = String::from_utf8_lossy(&out.stdout);
if text.contains("STOPPED") || text.contains("1060") {
return;
} }
}
std::thread::sleep(std::time::Duration::from_millis(500));
}
eprintln!(" warning: service did not stop within 10s");
} }
/// Remove the service from SCM. Idempotent — see `stop_service_scm`. /// Remove the service from SCM. Idempotent — see `stop_service_scm`.