Files
ntfy/cmd/serve_windows.go
binwiederhier dfd12528f3 Manual nits
2026-01-17 14:48:32 -05:00

101 lines
2.6 KiB
Go

//go:build windows && !noserver
package cmd
import (
"fmt"
"sync"
"golang.org/x/sys/windows/svc"
"heckel.io/ntfy/v2/log"
"heckel.io/ntfy/v2/server"
)
const serviceName = "ntfy"
// sigHandlerConfigReload is a no-op on Windows since SIGHUP is not available.
// Windows users can restart the service to reload configuration.
func sigHandlerConfigReload(config string) {
log.Debug("Config hot-reload via SIGHUP is not supported on Windows")
}
// runAsWindowsService runs the ntfy server as a Windows service
func runAsWindowsService(conf *server.Config) error {
return svc.Run(serviceName, &windowsService{conf: conf})
}
// windowsService implements the svc.Handler interface
type windowsService struct {
conf *server.Config
server *server.Server
mu sync.Mutex
}
// Execute is the main entry point for the Windows service
func (s *windowsService) Execute(args []string, requests <-chan svc.ChangeRequest, status chan<- svc.Status) (bool, uint32) {
const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown
status <- svc.Status{State: svc.StartPending}
// Create and start the server
var err error
s.mu.Lock()
s.server, err = server.New(s.conf)
s.mu.Unlock()
if err != nil {
log.Error("Failed to create server: %s", err.Error())
return true, 1
}
// Start server in a goroutine
serverErrChan := make(chan error, 1)
go func() {
serverErrChan <- s.server.Run()
}()
status <- svc.Status{State: svc.Running, Accepts: cmdsAccepted}
log.Info("Windows service started")
for {
select {
case err := <-serverErrChan:
if err != nil {
log.Error("Server error: %s", err.Error())
return true, 1
}
return false, 0
case req := <-requests:
switch req.Cmd {
case svc.Interrogate:
status <- req.CurrentStatus
case svc.Stop, svc.Shutdown:
log.Info("Windows service stopping...")
status <- svc.Status{State: svc.StopPending}
s.mu.Lock()
if s.server != nil {
s.server.Stop()
}
s.mu.Unlock()
return false, 0
default:
log.Warn("Unexpected service control request: %d", req.Cmd)
}
}
}
}
// maybeRunAsService checks if the process is running as a Windows service,
// and if so, runs the server as a service. Returns true if it ran as a service.
func maybeRunAsService(conf *server.Config) (bool, error) {
isService, err := svc.IsWindowsService()
if err != nil {
return false, fmt.Errorf("failed to detect Windows service mode: %w", err)
} else if !isService {
return false, nil
}
log.Info("Running as Windows service")
if err := runAsWindowsService(conf); err != nil {
return true, fmt.Errorf("failed to run as Windows service: %w", err)
}
return true, nil
}