service will install / un-install, start / stop, and run a program as a service (daemon). Currently supports Windows XP+, Linux/(systemd | Upstart | SysV), and OSX/Launchd.
Windows controls services by setting up callbacks that is non-trivial. This is very different then other systems. This package provides the same API despite the substantial differences. It also can be used to detect how a program is called, from an interactive terminal or from a service manager.
- Dependencies field is not implemented for Linux systems and Launchd.
- OS X when running as a UserService Interactive will not be accurate.
package main
import (
"flag"
"fmt"
"log"
"os"
"time"
"github.com/morfien101/service"
)
var (
logger service.Logger
version = "0.0.1"
svcFlag = flag.String("service", "", "Control the system service.")
versionCheck = flag.Bool("v", false, "Outputs the version of the program.")
helpFlag = flag.Bool("h", false, "Shows the help menu")
)
func main() {
digestFlags()
svcConfig := &service.Config{
Name: "test_restarter",
DisplayName: "Test Restarter",
Description: "Service that just waits to be restarted.",
}
prg := &program{}
serviceController, err := service.New(prg, svcConfig)
if err != nil {
log.Fatal(err)
}
// Create a channel to house the errors from the service.
// These are then printed out on the go func below.
errsChan := make(chan error, 5)
logger, err = serviceController.Logger(errsChan)
if err != nil {
log.Fatal(err)
}
go func() {
for {
err := <-errsChan
if err != nil {
log.Print(err)
}
}
}()
// Look for the --service flag and do what we need to here.
if len(*svcFlag) != 0 {
err := service.Control(serviceController, *svcFlag)
if err != nil {
log.Printf("Valid actions: %q\n", service.ControlAction)
log.Fatal(err)
}
return
}
// Run() will call prg.Start() and then wait.
// Stop() is called when a signal to stop is received.
// Only if Start sends an error or Stop finishes will Run return.
err = serviceController.Run()
if err != nil {
logger.Error(err)
}
}
func digestFlags() {
// Parse the flags to get the state we need to run in.
flag.Parse()
if *versionCheck {
fmt.Println(version)
os.Exit(0)
}
if *helpFlag {
flag.PrintDefaults()
os.Exit(0)
}
}
type program struct {
exit chan bool
finshed chan bool
}
func (p *program) Start(s service.Service) error {
// Errors would relate to looking for config files.
// To be added later.
// This channel is used in the run section to block.
p.exit = make(chan bool, 1)
p.finshed = make(chan bool, 1)
// Start the service in a async go routine
go p.run()
// return any errors.
return nil
}
func (p *program) Stop(s service.Service) error {
// This section is to shutdown the app gracefully.
// return any errors relating to the above.
// Send your shutdown signals to any other go routines or work flows from here.
// This channel is used in the running section to block.
p.exit <- true
close(p.exit)
// Wait till everything is shutdown
<-p.finshed
logger.Info("Example service is Stopping.")
return nil
}
func (p *program) run() error {
// This section is where your work happens.
// In this case we just log some messages, but really this is where you would start your work flows
// go routines, etc...
defer func() {
logger.Info("Example service got a signal to stop.")
}()
ticker := time.NewTicker(time.Second * 5)
logger.Info("Example service is Started and waiting.")
for {
select {
case <-p.exit:
ticker.Stop()
p.finshed <- true
return nil
case <-ticker.C:
logger.Infof("Example service periodic message.")
}
}
}