inits/cmd/dkl-system-init/service.go
2018-07-06 19:07:37 +11:00

152 lines
2.3 KiB
Go

package main
import (
"os/exec"
"syscall"
"time"
"novit.nc/direktil/pkg/log"
)
var (
// StdRestart is a wait duration between restarts of a service, if you have no inspiration.
StdRestart = 1 * time.Second
killDelay = 30 * time.Second
)
// Service represents a service to run as part of the init process.
type Service interface {
GetName() string
CanStart() bool
Run(notify func()) error
Stop()
}
type CommandService struct {
Name string
Command []string
Restart time.Duration
Needs []string
Provides []string
PreExec func() error
log *log.Log
stop bool
command *exec.Cmd
}
var _ Service = &CommandService{}
func (s *CommandService) GetName() string {
return s.Name
}
// CanStart is part of the Service interface
func (s *CommandService) CanStart() bool {
return services.HasFlag(s.Needs...)
}
func (s *CommandService) Stop() {
stopped := false
s.stop = true
c := s.command
if c == nil {
return
}
c.Process.Signal(syscall.SIGTERM)
go func() {
time.Sleep(killDelay)
if !stopped {
c.Process.Signal(syscall.SIGKILL)
}
}()
c.Wait()
stopped = true
}
func (s *CommandService) Run(notify func()) error {
s.stop = false
if s.log == nil {
s.log = log.Get(s.Name)
}
isOneshot := s.Restart == time.Duration(0)
myNotify := func() {
for _, provide := range s.Provides {
services.SetFlag(provide)
}
notify()
}
if s.PreExec != nil {
if err := s.PreExec(); err != nil {
return err
}
}
// Starting
var err error
retry:
if isOneshot {
// oneshot services are only active after exit
err = s.exec(func() {})
} else {
err = s.exec(myNotify)
}
if s.stop {
return err
}
if isOneshot {
myNotify()
} else {
// auto-restart service
services.Set(s.Name, Failed)
time.Sleep(s.Restart)
s.log.Taintf(log.Warning, "-- restarting --")
services.Set(s.Name, Starting)
goto retry
}
return err
}
func (s *CommandService) exec(notify func()) error {
c := exec.Command(s.Command[0], s.Command[1:]...)
s.command = c
defer func() {
s.command = nil
}()
c.Stdout = s.log
c.Stderr = s.log
if err := c.Start(); err != nil {
s.log.Taintf(log.Error, "failed to start: %v", err)
return err
}
notify()
if err := c.Wait(); err != nil {
s.log.Taintf(log.Error, "failed: %v", err)
return err
}
return nil
}