diff --git a/example/main.go b/example/main.go index a876dc4..2f16959 100644 --- a/example/main.go +++ b/example/main.go @@ -6,11 +6,15 @@ import ( "os" ) +var localLog service.Logger + func main() { var name = "GoServiceTest" var displayName = "Go Service Test" var desc = "This is a test Go service. It is designed to run well." - var ws, err = service.NewService(name, displayName, desc) + + var s, err = service.NewService(name, displayName, desc) + localLog = s if err != nil { fmt.Printf("%s unable to start: %s", displayName, err) @@ -22,41 +26,56 @@ func main() { verb := os.Args[1] switch verb { case "install": - err = ws.Install() + err = s.Install() if err != nil { fmt.Printf("Failed to install: %s\n", err) return } fmt.Printf("Service \"%s\" installed.\n", displayName) case "remove": - err = ws.Remove() + err = s.Remove() if err != nil { fmt.Printf("Failed to remove: %s\n", err) return } fmt.Printf("Service \"%s\" removed.\n", displayName) + case "run": + doWork() + case "start": + err = s.Start() + if err != nil { + fmt.Printf("Failed to start: %s\n", err) + return + } + fmt.Printf("Service \"%s\" started.\n", displayName) + case "stop": + err = s.Stop() + if err != nil { + fmt.Printf("Failed to stop: %s\n", err) + return + } + fmt.Printf("Service \"%s\" stopped.\n", displayName) } return } - err = ws.Run(func() error { + err = s.Run(func() error { // start go doWork() - ws.LogInfo("I'm Running!") return nil }, func() error { // stop stopWork() - ws.LogInfo("I'm Stopping!") return nil }) if err != nil { - ws.LogError(err.Error()) + s.LogError(err.Error()) } } func doWork() { - + localLog.LogInfo("I'm Running!") + select {} } func stopWork() { - + localLog.LogInfo("I'm Stopping!") } diff --git a/service.go b/service.go index e7d0c0e..29d75d5 100644 --- a/service.go +++ b/service.go @@ -1,5 +1,5 @@ // Package service provides a simple way to create a system service. -// Currently supports Windows and Linux/Upstart. +// Currently supports Windows, Linux/Upstart, OSX/Launchd. package service // Creates a new service. name is the internal name @@ -12,14 +12,14 @@ func NewService(name, displayName, description string) (Service, error) { // Represents a generic way to interact with the system's service. type Service interface { - // Installs this service on the system. May return an - // error if this service is already installed. - Install() error - - // Removes this service from the system. May return an - // error if this service is not already installed. - Remove() error + Installer + Controller + Runner + Logger +} +// A Generic way to stop and start a service. +type Runner interface { // Call quickly after initial entry point. Does not return until // service is ready to stop. onStart is called when the service is // starting, returning an error will fail to start the service. @@ -28,7 +28,31 @@ type Service interface { // an error from Run. // Both callbacks should return quickly and not block. Run(onStart, onStop func() error) error +} +// Simple install and remove commands. +type Installer interface { + // Installs this service on the system. May return an + // error if this service is already installed. + Install() error + + // Removes this service from the system. May return an + // error if this service is not already installed. + Remove() error +} + +// A service that implements ServiceController is able to +// start and stop itself. +type Controller interface { + // Starts this service on the system. + Start() error + + // Stops this service on the system. + Stop() error +} + +// A service that implements ServiceLogger can perform simple system logging. +type Logger interface { // Basic log functions in the context of the service. LogError(format string, a ...interface{}) error LogWarning(format string, a ...interface{}) error diff --git a/service_darwin.go b/service_darwin.go index 3d927c9..38076d9 100644 --- a/service_darwin.go +++ b/service_darwin.go @@ -5,8 +5,8 @@ package service #include int -GetExecPath(char* path) { - uint32_t size = 32*1024; +GetExecPath(char* path, int bufferSize) { + uint32_t size = bufferSize; if (_NSGetExecutablePath(path, &size) == 0) { // Despite Apple docs, size does NOT get set in call. return strlen(path); @@ -28,7 +28,8 @@ import ( "text/template" ) -// BUG(kardia): I have not confirmed this service works as intended on launchd. +const maxPathSize = 32 * 1024 + func newService(name, displayName, description string) (s *darwinLaunchdService, err error) { s = &darwinLaunchdService{ name: name, @@ -83,30 +84,26 @@ func (s *darwinLaunchdService) Install() error { path, } - t := template.Must(template.New("upstartScript").Parse(upstartScript)) - err = t.Execute(f, to) - - if err != nil { - return err - } - - cmd := exec.Command("launchctl", "load", confPath) - err = cmd.Run() - if err != nil { - return err - } - - return nil + t := template.Must(template.New("launchdConfig").Parse(launchdConfig)) + return t.Execute(f, to) } func (s *darwinLaunchdService) Remove() error { + s.Stop() + + confPath := s.getServiceFilePath() + return os.Remove(confPath) +} + +func (s *darwinLaunchdService) Start() error { + confPath := s.getServiceFilePath() + cmd := exec.Command("launchctl", "load", confPath) + return cmd.Run() +} +func (s *darwinLaunchdService) Stop() error { confPath := s.getServiceFilePath() cmd := exec.Command("launchctl", "unload", confPath) - err := cmd.Run() - - // Don't worry about his error if not found. - os.Remove(confPath) - return err + return cmd.Run() } func (s *darwinLaunchdService) Run(onStart, onStop func() error) error { @@ -137,8 +134,8 @@ func (s *darwinLaunchdService) LogInfo(format string, a ...interface{}) error { } func getExePath() (exePath string, err error) { - buffer := make([]byte, 32*1024) - size := C.GetExecPath((*C.char)(unsafe.Pointer(&buffer[0]))) + buffer := make([]byte, maxPathSize) + size := C.GetExecPath((*C.char)(unsafe.Pointer(&buffer[0])), maxPathSize) if size == 0 { return "", errors.New("Unable to get exec path.") } @@ -147,7 +144,7 @@ func getExePath() (exePath string, err error) { return ret, nil } -var upstartScript = ` +var launchdConfig = ` diff --git a/service_linux.go b/service_linux.go index 4bb0330..def2e39 100644 --- a/service_linux.go +++ b/service_linux.go @@ -4,8 +4,10 @@ import ( "fmt" "log/syslog" "os" + "os/exec" "os/signal" "text/template" + "path/filepath" ) func newService(name, displayName, description string) (s *linuxUpstartService, err error) { @@ -87,6 +89,16 @@ func (s *linuxUpstartService) Run(onStart, onStop func() error) error { return onStop() } +func (s *linuxUpstartService) Start() error { + cmd := exec.Command("start", s.name) + return cmd.Run() +} + +func (s *linuxUpstartService) Stop() error { + cmd := exec.Command("stop", s.name) + return cmd.Run() +} + func (s *linuxUpstartService) LogError(format string, a ...interface{}) error { return s.logger.Err(fmt.Sprintf(format, a...)) } @@ -98,7 +110,9 @@ func (s *linuxUpstartService) LogInfo(format string, a ...interface{}) error { } func getExePath() (exePath string, err error) { - return os.Readlink(`/proc/self/exe`) + exePath, err = os.Readlink(`/proc/self/exe`) + exePath = filepath.Clean(exePath) + return } var upstartScript = `# {{.Description}} diff --git a/service_windows.go b/service_windows.go index 935bca6..263c827 100644 --- a/service_windows.go +++ b/service_windows.go @@ -124,6 +124,37 @@ func (ws *windowsService) Run(onStart, onStop func() error) error { return svc.Run(ws.name, ws) } +func (ws *windowsService) Start() error { + m, err := mgr.Connect() + if err != nil { + return err + } + defer m.Disconnect() + + s, err := m.OpenService(ws.name) + if err != nil { + return err + } + defer s.Close() + return s.Start([]string{}) +} + +func (ws *windowsService) Stop() error { + m, err := mgr.Connect() + if err != nil { + return err + } + defer m.Disconnect() + + s, err := m.OpenService(ws.name) + if err != nil { + return err + } + defer s.Close() + _, err = s.Control(svc.Stop) + return err +} + func (ws *windowsService) LogError(format string, a ...interface{}) error { if ws.logger == nil { return nil