From 409a0721354354a2d13e5c110be0f7f03363652b Mon Sep 17 00:00:00 2001 From: EINIER FREYRE CORONA Date: Fri, 12 Dec 2025 12:56:30 +0000 Subject: [PATCH] notify: implement windows service status and error notifications (#7389) * implement service status and error notifications * adjust return of Error function * configure accepts on status * align windows with linux semantics --- notify/notify_windows.go | 69 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 64 insertions(+), 5 deletions(-) diff --git a/notify/notify_windows.go b/notify/notify_windows.go index 5666a4c22..33f947565 100644 --- a/notify/notify_windows.go +++ b/notify/notify_windows.go @@ -14,16 +14,26 @@ package notify -import "golang.org/x/sys/windows/svc" +import ( + "log" + "strings" + + "golang.org/x/sys/windows/svc" +) // globalStatus store windows service status, it can be // use to notify caddy status. var globalStatus chan<- svc.Status +// SetGlobalStatus assigns the channel through which status updates +// will be sent to the SCM. This is typically provided by the service +// handler when the service starts. func SetGlobalStatus(status chan<- svc.Status) { globalStatus = status } +// Ready notifies the SCM that the service is fully running and ready +// to accept stop or shutdown control requests. func Ready() error { if globalStatus != nil { globalStatus <- svc.Status{ @@ -34,6 +44,8 @@ func Ready() error { return nil } +// Reloading notifies the SCM that the service is entering a transitional +// state. func Reloading() error { if globalStatus != nil { globalStatus <- svc.Status{State: svc.StartPending} @@ -41,6 +53,8 @@ func Reloading() error { return nil } +// Stopping notifies the SCM that the service is in the process of stopping. +// This allows Windows to track the shutdown transition properly. func Stopping() error { if globalStatus != nil { globalStatus <- svc.Status{State: svc.StopPending} @@ -48,8 +62,53 @@ func Stopping() error { return nil } -// TODO: not implemented -func Status(_ string) error { return nil } +// Status sends an arbitrary service state to the SCM based on a string +// identifier of [svc.State]. +// The unknown states will be logged. +func Status(name string) error { + if globalStatus == nil { + return nil + } -// TODO: not implemented -func Error(_ error, _ int) error { return nil } + var state svc.State + var accepts svc.Accepted + accepts = 0 + + switch strings.ToLower(name) { + case "stopped": + state = svc.Stopped + case "start_pending": + state = svc.StartPending + case "stop_pending": + state = svc.StopPending + case "running": + state = svc.Running + accepts = svc.AcceptStop | svc.AcceptShutdown + case "continue_pending": + state = svc.ContinuePending + case "pause_pending": + state = svc.PausePending + case "paused": + state = svc.Paused + accepts = svc.AcceptStop | svc.AcceptShutdown | svc.AcceptPauseAndContinue + default: + log.Printf("unknown state: %s", name) + return nil + } + + globalStatus <- svc.Status{State: state, Accepts: accepts} + return nil +} + +// Error notifies the SCM that the service is stopping due to a failure, +// including a service-specific exit code. +func Error(err error, code int) error { + if globalStatus != nil { + globalStatus <- svc.Status{ + State: svc.StopPending, + ServiceSpecificExitCode: uint32(code), + } + } + + return nil +}