serve_windows.go 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. //go:build windows && !noserver
  2. package cmd
  3. import (
  4. "fmt"
  5. "sync"
  6. "golang.org/x/sys/windows/svc"
  7. "heckel.io/ntfy/v2/log"
  8. "heckel.io/ntfy/v2/server"
  9. )
  10. const serviceName = "ntfy"
  11. // sigHandlerConfigReload is a no-op on Windows since SIGHUP is not available.
  12. // Windows users can restart the service to reload configuration.
  13. func sigHandlerConfigReload(config string) {
  14. log.Debug("Config hot-reload via SIGHUP is not supported on Windows")
  15. }
  16. // runAsWindowsService runs the ntfy server as a Windows service
  17. func runAsWindowsService(conf *server.Config) error {
  18. return svc.Run(serviceName, &windowsService{conf: conf})
  19. }
  20. // windowsService implements the svc.Handler interface
  21. type windowsService struct {
  22. conf *server.Config
  23. server *server.Server
  24. mu sync.Mutex
  25. }
  26. // Execute is the main entry point for the Windows service
  27. func (s *windowsService) Execute(args []string, requests <-chan svc.ChangeRequest, status chan<- svc.Status) (bool, uint32) {
  28. const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown
  29. status <- svc.Status{State: svc.StartPending}
  30. // Create and start the server
  31. var err error
  32. s.mu.Lock()
  33. s.server, err = server.New(s.conf)
  34. s.mu.Unlock()
  35. if err != nil {
  36. log.Error("Failed to create server: %s", err.Error())
  37. return true, 1
  38. }
  39. // Start server in a goroutine
  40. serverErrChan := make(chan error, 1)
  41. go func() {
  42. serverErrChan <- s.server.Run()
  43. }()
  44. status <- svc.Status{State: svc.Running, Accepts: cmdsAccepted}
  45. log.Info("Windows service started")
  46. for {
  47. select {
  48. case err := <-serverErrChan:
  49. if err != nil {
  50. log.Error("Server error: %s", err.Error())
  51. return true, 1
  52. }
  53. return false, 0
  54. case req := <-requests:
  55. switch req.Cmd {
  56. case svc.Interrogate:
  57. status <- req.CurrentStatus
  58. case svc.Stop, svc.Shutdown:
  59. log.Info("Windows service stopping...")
  60. status <- svc.Status{State: svc.StopPending}
  61. s.mu.Lock()
  62. if s.server != nil {
  63. s.server.Stop()
  64. }
  65. s.mu.Unlock()
  66. return false, 0
  67. default:
  68. log.Warn("Unexpected service control request: %d", req.Cmd)
  69. }
  70. }
  71. }
  72. }
  73. // maybeRunAsService checks if the process is running as a Windows service,
  74. // and if so, runs the server as a service. Returns true if it ran as a service.
  75. func maybeRunAsService(conf *server.Config) (bool, error) {
  76. isService, err := svc.IsWindowsService()
  77. if err != nil {
  78. return false, fmt.Errorf("failed to detect Windows service mode: %w", err)
  79. } else if !isService {
  80. return false, nil
  81. }
  82. log.Info("Running as Windows service")
  83. if err := runAsWindowsService(conf); err != nil {
  84. return true, fmt.Errorf("failed to run as Windows service: %w", err)
  85. }
  86. return true, nil
  87. }