152 lines
2.3 KiB
Go
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
|
||
|
}
|