| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100 |
- //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
- }
|