service: break out each linux service into its own file.
Breaking apart each service reduces the overall number of branches in the code, but increases duplication. Duplication can be taken care of in follow-up commits while still keeping the clarity of linear code. Also expiriment with code that would allow the user to add or remove systems. Still expirimental and not exposed in the API.
This commit is contained in:
+84
-405
@@ -8,157 +8,105 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/kardianos/osext"
|
||||
)
|
||||
|
||||
const (
|
||||
initSystemV = initFlavor(iota)
|
||||
initUpstart
|
||||
initSystemd
|
||||
)
|
||||
type newServiceFunc func(i Interface, c *Config) (Service, error)
|
||||
|
||||
func getFlavor() initFlavor {
|
||||
flavor := initSystemV
|
||||
if isSystemd() {
|
||||
flavor = initSystemd
|
||||
} else if isUpstart() {
|
||||
flavor = initUpstart
|
||||
}
|
||||
return flavor
|
||||
type linuxSystem struct {
|
||||
interactive bool
|
||||
selectedName string
|
||||
selectedNew newServiceFunc
|
||||
}
|
||||
|
||||
func isUpstart() bool {
|
||||
if _, err := os.Stat("/sbin/upstart-udev-bridge"); err == nil {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isSystemd() bool {
|
||||
if _, err := os.Stat("/run/systemd/system"); err == nil {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type linuxService struct {
|
||||
i Interface
|
||||
*Config
|
||||
|
||||
interactive bool
|
||||
}
|
||||
|
||||
var flavor = getFlavor()
|
||||
|
||||
type linuxSystem struct{}
|
||||
|
||||
func (ls linuxSystem) String() string {
|
||||
return fmt.Sprintf("Linux %s", flavor.String())
|
||||
return fmt.Sprintf("Linux %s", ls.selectedName)
|
||||
}
|
||||
|
||||
func (ls linuxSystem) Interactive() bool {
|
||||
return interactive
|
||||
return ls.interactive
|
||||
}
|
||||
|
||||
var system = linuxSystem{}
|
||||
type systemChoice interface {
|
||||
Name() string
|
||||
Detect() bool
|
||||
Interactive() bool
|
||||
New(i Interface, c *Config) (Service, error)
|
||||
}
|
||||
|
||||
type linuxSystemChoice struct {
|
||||
name string
|
||||
detect func() bool
|
||||
interactive func() bool
|
||||
new func(i Interface, c *Config) (Service, error)
|
||||
}
|
||||
|
||||
func (sc linuxSystemChoice) Name() string {
|
||||
return sc.name
|
||||
}
|
||||
func (sc linuxSystemChoice) Detect() bool {
|
||||
return sc.detect()
|
||||
}
|
||||
func (sc linuxSystemChoice) Interactive() bool {
|
||||
return sc.interactive()
|
||||
}
|
||||
func (sc linuxSystemChoice) New(i Interface, c *Config) (Service, error) {
|
||||
return sc.new(i, c)
|
||||
}
|
||||
|
||||
var systemRegistry = []systemChoice{
|
||||
linuxSystemChoice{
|
||||
name: "systemd",
|
||||
detect: isSystemd,
|
||||
interactive: func() bool {
|
||||
is, _ := isInteractive()
|
||||
return is
|
||||
},
|
||||
new: newSystemdService,
|
||||
},
|
||||
linuxSystemChoice{
|
||||
name: "Upstart",
|
||||
detect: isUpstart,
|
||||
interactive: func() bool {
|
||||
is, _ := isInteractive()
|
||||
return is
|
||||
},
|
||||
new: newUpstartService,
|
||||
},
|
||||
linuxSystemChoice{
|
||||
name: "System-V",
|
||||
detect: func() bool { return true },
|
||||
interactive: func() bool {
|
||||
is, _ := isInteractive()
|
||||
return is
|
||||
},
|
||||
new: newSystemVService,
|
||||
},
|
||||
}
|
||||
|
||||
func newLinuxSystem() linuxSystem {
|
||||
for _, choice := range systemRegistry {
|
||||
if choice.Detect() == false {
|
||||
continue
|
||||
}
|
||||
return linuxSystem{
|
||||
interactive: choice.Interactive(),
|
||||
selectedName: choice.Name(),
|
||||
selectedNew: choice.New,
|
||||
}
|
||||
}
|
||||
return linuxSystem{}
|
||||
}
|
||||
|
||||
var system = newLinuxSystem()
|
||||
|
||||
var errNoServiceSystemDetected = errors.New("No service system detected.")
|
||||
|
||||
func newService(i Interface, c *Config) (Service, error) {
|
||||
s := &linuxService{
|
||||
i: i,
|
||||
Config: c,
|
||||
}
|
||||
var err error
|
||||
s.interactive, err = isInteractive()
|
||||
|
||||
return s, err
|
||||
}
|
||||
|
||||
func (s *linuxService) String() string {
|
||||
if len(s.DisplayName) > 0 {
|
||||
return s.DisplayName
|
||||
}
|
||||
return s.Name
|
||||
}
|
||||
|
||||
type initFlavor uint8
|
||||
|
||||
func (f initFlavor) String() string {
|
||||
switch f {
|
||||
case initSystemV:
|
||||
return "System-V"
|
||||
case initUpstart:
|
||||
return "Upstart"
|
||||
case initSystemd:
|
||||
return "systemd"
|
||||
default:
|
||||
panic("Invalid flavor")
|
||||
}
|
||||
}
|
||||
|
||||
// Systemd services should be supported, but are not currently.
|
||||
var errNoUserServiceSystemd = errors.New("User services are not supported on systemd.")
|
||||
|
||||
var errNoUserServiceSystemV = errors.New("User services are not supported on SystemV.")
|
||||
|
||||
// Upstart has some support for user services in graphical sessions.
|
||||
// Due to the mix of actual support for user services over versions, just don't bother.
|
||||
// Upstart will be replaced by systemd in most cases anyway.
|
||||
var errNoUserServiceUpstart = errors.New("User services are not supported on Upstart.")
|
||||
|
||||
func (f initFlavor) ConfigPath(name string, c *Config) (cp string, err error) {
|
||||
if c.UserService {
|
||||
switch f {
|
||||
case initSystemd:
|
||||
err = errNoUserServiceSystemd
|
||||
case initSystemV:
|
||||
err = errNoUserServiceSystemV
|
||||
case initUpstart:
|
||||
err = errNoUserServiceUpstart
|
||||
default:
|
||||
panic("Invalid flavor")
|
||||
}
|
||||
return
|
||||
}
|
||||
switch f {
|
||||
case initSystemd:
|
||||
cp = "/etc/systemd/system/" + name + ".service"
|
||||
case initSystemV:
|
||||
cp = "/etc/init.d/" + name
|
||||
case initUpstart:
|
||||
cp = "/etc/init/" + name + ".conf"
|
||||
default:
|
||||
panic("Invalid flavor")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (f initFlavor) Template() *template.Template {
|
||||
var templ string
|
||||
switch f {
|
||||
case initSystemd:
|
||||
templ = systemdScript
|
||||
case initSystemV:
|
||||
templ = systemVScript
|
||||
case initUpstart:
|
||||
templ = upstartScript
|
||||
}
|
||||
return template.Must(template.New(f.String() + "Script").Funcs(tf).Parse(templ))
|
||||
}
|
||||
|
||||
var interactive = false
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
interactive, err = isInteractive()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
if system.selectedNew == nil {
|
||||
return nil, errNoServiceSystemDetected
|
||||
}
|
||||
return system.selectedNew(i, c)
|
||||
}
|
||||
|
||||
func isInteractive() (bool, error) {
|
||||
@@ -166,277 +114,8 @@ func isInteractive() (bool, error) {
|
||||
return os.Getppid() != 1, nil
|
||||
}
|
||||
|
||||
func (s *linuxService) Install() error {
|
||||
confPath, err := flavor.ConfigPath(s.Name, s.Config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = os.Stat(confPath)
|
||||
if err == nil {
|
||||
return fmt.Errorf("Init already exists: %s", confPath)
|
||||
}
|
||||
|
||||
f, err := os.Create(confPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
path, err := osext.Executable()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var to = &struct {
|
||||
*Config
|
||||
Path string
|
||||
}{
|
||||
s.Config,
|
||||
path,
|
||||
}
|
||||
|
||||
err = flavor.Template().Execute(f, to)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if flavor == initSystemV {
|
||||
if err = os.Chmod(confPath, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, i := range [...]string{"2", "3", "4", "5"} {
|
||||
if err = os.Symlink(confPath, "/etc/rc"+i+".d/S50"+s.Name); err != nil {
|
||||
continue
|
||||
}
|
||||
}
|
||||
for _, i := range [...]string{"0", "1", "6"} {
|
||||
if err = os.Symlink(confPath, "/etc/rc"+i+".d/K02"+s.Name); err != nil {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if flavor == initSystemd {
|
||||
err = exec.Command("systemctl", "enable", s.Name+".service").Run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return exec.Command("systemctl", "daemon-reload").Run()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *linuxService) Uninstall() error {
|
||||
if flavor == initSystemd {
|
||||
exec.Command("systemctl", "disable", s.Name+".service").Run()
|
||||
}
|
||||
cp, err := flavor.ConfigPath(s.Name, s.Config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.Remove(cp); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *linuxService) Logger(errs chan<- error) (Logger, error) {
|
||||
if s.interactive {
|
||||
return ConsoleLogger, nil
|
||||
}
|
||||
return s.SystemLogger(errs)
|
||||
}
|
||||
func (s *linuxService) SystemLogger(errs chan<- error) (Logger, error) {
|
||||
return newSysLogger(s.Name, errs)
|
||||
}
|
||||
|
||||
func (s *linuxService) Run() (err error) {
|
||||
err = s.i.Start(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sigChan := make(chan os.Signal, 3)
|
||||
|
||||
signal.Notify(sigChan, os.Interrupt, os.Kill)
|
||||
|
||||
<-sigChan
|
||||
|
||||
return s.i.Stop(s)
|
||||
}
|
||||
|
||||
func (s *linuxService) Start() error {
|
||||
switch flavor {
|
||||
case initSystemd:
|
||||
return exec.Command("systemctl", "start", s.Name+".service").Run()
|
||||
case initUpstart:
|
||||
return exec.Command("initctl", "start", s.Name).Run()
|
||||
default:
|
||||
return exec.Command("service", s.Name, "start").Run()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *linuxService) Stop() error {
|
||||
switch flavor {
|
||||
case initSystemd:
|
||||
return exec.Command("systemctl", "stop", s.Name+".service").Run()
|
||||
case initUpstart:
|
||||
return exec.Command("initctl", "stop", s.Name).Run()
|
||||
default:
|
||||
return exec.Command("service", s.Name, "stop").Run()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *linuxService) Restart() error {
|
||||
err := s.Stop()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
return s.Start()
|
||||
}
|
||||
|
||||
var tf = map[string]interface{}{
|
||||
"cmd": func(s string) string {
|
||||
return `"` + strings.Replace(s, `"`, `\"`, -1) + `"`
|
||||
},
|
||||
}
|
||||
|
||||
const systemVScript = `#!/bin/sh
|
||||
# For RedHat and cousins:
|
||||
# chkconfig: - 99 01
|
||||
# description: {{.Description}}
|
||||
# processname: {{.Path}}
|
||||
|
||||
### BEGIN INIT INFO
|
||||
# Provides: {{.Path}}
|
||||
# Required-Start:
|
||||
# Required-Stop:
|
||||
# Default-Start: 2 3 4 5
|
||||
# Default-Stop: 0 1 6
|
||||
# Short-Description: {{.DisplayName}}
|
||||
# Description: {{.Description}}
|
||||
### END INIT INFO
|
||||
|
||||
cmd="{{.Path}}{{range .Arguments}} {{.|cmd}}{{end}}"
|
||||
|
||||
name=$(basename $0)
|
||||
pid_file="/var/run/$name.pid"
|
||||
stdout_log="/var/log/$name.log"
|
||||
stderr_log="/var/log/$name.err"
|
||||
|
||||
get_pid() {
|
||||
cat "$pid_file"
|
||||
}
|
||||
|
||||
is_running() {
|
||||
[ -f "$pid_file" ] && ps $(get_pid) > /dev/null 2>&1
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
if is_running; then
|
||||
echo "Already started"
|
||||
else
|
||||
echo "Starting $name"
|
||||
$cmd >> "$stdout_log" 2>> "$stderr_log" &
|
||||
echo $! > "$pid_file"
|
||||
if ! is_running; then
|
||||
echo "Unable to start, see $stdout_log and $stderr_log"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
stop)
|
||||
if is_running; then
|
||||
echo -n "Stopping $name.."
|
||||
kill $(get_pid)
|
||||
for i in {1..10}
|
||||
do
|
||||
if ! is_running; then
|
||||
break
|
||||
fi
|
||||
echo -n "."
|
||||
sleep 1
|
||||
done
|
||||
echo
|
||||
if is_running; then
|
||||
echo "Not stopped; may still be shutting down or shutdown may have failed"
|
||||
exit 1
|
||||
else
|
||||
echo "Stopped"
|
||||
if [ -f "$pid_file" ]; then
|
||||
rm "$pid_file"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
echo "Not running"
|
||||
fi
|
||||
;;
|
||||
restart)
|
||||
$0 stop
|
||||
if is_running; then
|
||||
echo "Unable to stop, will not attempt to start"
|
||||
exit 1
|
||||
fi
|
||||
$0 start
|
||||
;;
|
||||
status)
|
||||
if is_running; then
|
||||
echo "Running"
|
||||
else
|
||||
echo "Stopped"
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 {start|stop|restart|status}"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
exit 0`
|
||||
|
||||
// The upstart script should stop with an INT or the Go runtime will terminate
|
||||
// the program before the Stop handler can run.
|
||||
const upstartScript = `# {{.Description}}
|
||||
|
||||
{{if .DisplayName}}description "{{.DisplayName}}"{{end}}
|
||||
|
||||
kill signal INT
|
||||
start on filesystem or runlevel [2345]
|
||||
stop on runlevel [!2345]
|
||||
|
||||
#setuid username
|
||||
|
||||
respawn
|
||||
respawn limit 10 5
|
||||
umask 022
|
||||
|
||||
console none
|
||||
|
||||
pre-start script
|
||||
test -x {{.Path}} || { stop; exit 0; }
|
||||
end script
|
||||
|
||||
# Start
|
||||
exec {{.Path}}{{range .Arguments}} {{.|cmd}}{{end}}
|
||||
`
|
||||
|
||||
const systemdScript = `[Unit]
|
||||
Description={{.Description}}
|
||||
ConditionFileIsExecutable={{.Path|cmd}}
|
||||
|
||||
[Service]
|
||||
StartLimitInterval=5
|
||||
StartLimitBurst=10
|
||||
ExecStart={{.Path|cmd}}{{range .Arguments}} {{.|cmd}}{{end}}
|
||||
{{if .ChRoot}}RootDirectory={{.ChRoot|cmd}}{{end}}
|
||||
{{if .WorkingDirectory}}WorkingDirectory={{.WorkingDirectory|cmd}}{{end}}
|
||||
{{if .UserName}}User={{.UserName}}{{end}}
|
||||
Restart=always
|
||||
RestartSec=120
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
`
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
// Copyright 2015 Daniel Theophanes.
|
||||
// Use of this source code is governed by a zlib-style
|
||||
// license that can be found in the LICENSE file.package service
|
||||
|
||||
package service
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/kardianos/osext"
|
||||
)
|
||||
|
||||
func isSystemd() bool {
|
||||
if _, err := os.Stat("/run/systemd/system"); err == nil {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type systemd struct {
|
||||
i Interface
|
||||
*Config
|
||||
}
|
||||
|
||||
func newSystemdService(i Interface, c *Config) (Service, error) {
|
||||
s := &systemd{
|
||||
i: i,
|
||||
Config: c,
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (s *systemd) String() string {
|
||||
if len(s.DisplayName) > 0 {
|
||||
return s.DisplayName
|
||||
}
|
||||
return s.Name
|
||||
}
|
||||
|
||||
// Systemd services should be supported, but are not currently.
|
||||
var errNoUserServiceSystemd = errors.New("User services are not supported on systemd.")
|
||||
|
||||
func (s *systemd) configPath() (cp string, err error) {
|
||||
if s.Config.UserService {
|
||||
err = errNoUserServiceSystemd
|
||||
return
|
||||
}
|
||||
cp = "/etc/systemd/system/" + s.Config.Name + ".service"
|
||||
return
|
||||
}
|
||||
func (s *systemd) template() *template.Template {
|
||||
return template.Must(template.New("").Funcs(tf).Parse(systemdScript))
|
||||
}
|
||||
|
||||
func (s *systemd) Install() error {
|
||||
confPath, err := s.configPath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = os.Stat(confPath)
|
||||
if err == nil {
|
||||
return fmt.Errorf("Init already exists: %s", confPath)
|
||||
}
|
||||
|
||||
f, err := os.Create(confPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
path, err := osext.Executable()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var to = &struct {
|
||||
*Config
|
||||
Path string
|
||||
}{
|
||||
s.Config,
|
||||
path,
|
||||
}
|
||||
|
||||
err = s.template().Execute(f, to)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = exec.Command("systemctl", "enable", s.Name+".service").Run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return exec.Command("systemctl", "daemon-reload").Run()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *systemd) Uninstall() error {
|
||||
exec.Command("systemctl", "disable", s.Name+".service").Run()
|
||||
cp, err := s.configPath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.Remove(cp); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *systemd) Logger(errs chan<- error) (Logger, error) {
|
||||
if system.Interactive() {
|
||||
return ConsoleLogger, nil
|
||||
}
|
||||
return s.SystemLogger(errs)
|
||||
}
|
||||
func (s *systemd) SystemLogger(errs chan<- error) (Logger, error) {
|
||||
return newSysLogger(s.Name, errs)
|
||||
}
|
||||
|
||||
func (s *systemd) Run() (err error) {
|
||||
err = s.i.Start(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sigChan := make(chan os.Signal, 3)
|
||||
|
||||
signal.Notify(sigChan, os.Interrupt, os.Kill)
|
||||
|
||||
<-sigChan
|
||||
|
||||
return s.i.Stop(s)
|
||||
}
|
||||
|
||||
func (s *systemd) Start() error {
|
||||
return exec.Command("systemctl", "start", s.Name+".service").Run()
|
||||
}
|
||||
|
||||
func (s *systemd) Stop() error {
|
||||
return exec.Command("systemctl", "stop", s.Name+".service").Run()
|
||||
}
|
||||
|
||||
func (s *systemd) Restart() error {
|
||||
err := s.Stop()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
return s.Start()
|
||||
}
|
||||
|
||||
const systemdScript = `[Unit]
|
||||
Description={{.Description}}
|
||||
ConditionFileIsExecutable={{.Path|cmd}}
|
||||
|
||||
[Service]
|
||||
StartLimitInterval=5
|
||||
StartLimitBurst=10
|
||||
ExecStart={{.Path|cmd}}{{range .Arguments}} {{.|cmd}}{{end}}
|
||||
{{if .ChRoot}}RootDirectory={{.ChRoot|cmd}}{{end}}
|
||||
{{if .WorkingDirectory}}WorkingDirectory={{.WorkingDirectory|cmd}}{{end}}
|
||||
{{if .UserName}}User={{.UserName}}{{end}}
|
||||
Restart=always
|
||||
RestartSec=120
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
`
|
||||
@@ -0,0 +1,251 @@
|
||||
// Copyright 2015 Daniel Theophanes.
|
||||
// Use of this source code is governed by a zlib-style
|
||||
// license that can be found in the LICENSE file.package service
|
||||
|
||||
package service
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/kardianos/osext"
|
||||
)
|
||||
|
||||
type sysv struct {
|
||||
i Interface
|
||||
*Config
|
||||
}
|
||||
|
||||
func newSystemVService(i Interface, c *Config) (Service, error) {
|
||||
s := &sysv{
|
||||
i: i,
|
||||
Config: c,
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (s *sysv) String() string {
|
||||
if len(s.DisplayName) > 0 {
|
||||
return s.DisplayName
|
||||
}
|
||||
return s.Name
|
||||
}
|
||||
|
||||
var errNoUserServiceSystemV = errors.New("User services are not supported on SystemV.")
|
||||
|
||||
func (s *sysv) configPath() (cp string, err error) {
|
||||
if s.Config.UserService {
|
||||
err = errNoUserServiceSystemV
|
||||
return
|
||||
}
|
||||
cp = "/etc/init.d/" + s.Config.Name
|
||||
return
|
||||
}
|
||||
func (s *sysv) template() *template.Template {
|
||||
return template.Must(template.New("").Funcs(tf).Parse(sysvScript))
|
||||
}
|
||||
|
||||
func (s *sysv) Install() error {
|
||||
confPath, err := s.configPath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = os.Stat(confPath)
|
||||
if err == nil {
|
||||
return fmt.Errorf("Init already exists: %s", confPath)
|
||||
}
|
||||
|
||||
f, err := os.Create(confPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
path, err := osext.Executable()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var to = &struct {
|
||||
*Config
|
||||
Path string
|
||||
}{
|
||||
s.Config,
|
||||
path,
|
||||
}
|
||||
|
||||
err = s.template().Execute(f, to)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = os.Chmod(confPath, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, i := range [...]string{"2", "3", "4", "5"} {
|
||||
if err = os.Symlink(confPath, "/etc/rc"+i+".d/S50"+s.Name); err != nil {
|
||||
continue
|
||||
}
|
||||
}
|
||||
for _, i := range [...]string{"0", "1", "6"} {
|
||||
if err = os.Symlink(confPath, "/etc/rc"+i+".d/K02"+s.Name); err != nil {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *sysv) Uninstall() error {
|
||||
cp, err := s.configPath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.Remove(cp); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *sysv) Logger(errs chan<- error) (Logger, error) {
|
||||
if system.Interactive() {
|
||||
return ConsoleLogger, nil
|
||||
}
|
||||
return s.SystemLogger(errs)
|
||||
}
|
||||
func (s *sysv) SystemLogger(errs chan<- error) (Logger, error) {
|
||||
return newSysLogger(s.Name, errs)
|
||||
}
|
||||
|
||||
func (s *sysv) Run() (err error) {
|
||||
err = s.i.Start(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sigChan := make(chan os.Signal, 3)
|
||||
|
||||
signal.Notify(sigChan, os.Interrupt, os.Kill)
|
||||
|
||||
<-sigChan
|
||||
|
||||
return s.i.Stop(s)
|
||||
}
|
||||
|
||||
func (s *sysv) Start() error {
|
||||
return exec.Command("service", s.Name, "start").Run()
|
||||
}
|
||||
|
||||
func (s *sysv) Stop() error {
|
||||
return exec.Command("service", s.Name, "stop").Run()
|
||||
}
|
||||
|
||||
func (s *sysv) Restart() error {
|
||||
err := s.Stop()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
return s.Start()
|
||||
}
|
||||
|
||||
const sysvScript = `#!/bin/sh
|
||||
# For RedHat and cousins:
|
||||
# chkconfig: - 99 01
|
||||
# description: {{.Description}}
|
||||
# processname: {{.Path}}
|
||||
|
||||
### BEGIN INIT INFO
|
||||
# Provides: {{.Path}}
|
||||
# Required-Start:
|
||||
# Required-Stop:
|
||||
# Default-Start: 2 3 4 5
|
||||
# Default-Stop: 0 1 6
|
||||
# Short-Description: {{.DisplayName}}
|
||||
# Description: {{.Description}}
|
||||
### END INIT INFO
|
||||
|
||||
cmd="{{.Path}}{{range .Arguments}} {{.|cmd}}{{end}}"
|
||||
|
||||
name=$(basename $0)
|
||||
pid_file="/var/run/$name.pid"
|
||||
stdout_log="/var/log/$name.log"
|
||||
stderr_log="/var/log/$name.err"
|
||||
|
||||
get_pid() {
|
||||
cat "$pid_file"
|
||||
}
|
||||
|
||||
is_running() {
|
||||
[ -f "$pid_file" ] && ps $(get_pid) > /dev/null 2>&1
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
if is_running; then
|
||||
echo "Already started"
|
||||
else
|
||||
echo "Starting $name"
|
||||
$cmd >> "$stdout_log" 2>> "$stderr_log" &
|
||||
echo $! > "$pid_file"
|
||||
if ! is_running; then
|
||||
echo "Unable to start, see $stdout_log and $stderr_log"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
stop)
|
||||
if is_running; then
|
||||
echo -n "Stopping $name.."
|
||||
kill $(get_pid)
|
||||
for i in {1..10}
|
||||
do
|
||||
if ! is_running; then
|
||||
break
|
||||
fi
|
||||
echo -n "."
|
||||
sleep 1
|
||||
done
|
||||
echo
|
||||
if is_running; then
|
||||
echo "Not stopped; may still be shutting down or shutdown may have failed"
|
||||
exit 1
|
||||
else
|
||||
echo "Stopped"
|
||||
if [ -f "$pid_file" ]; then
|
||||
rm "$pid_file"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
echo "Not running"
|
||||
fi
|
||||
;;
|
||||
restart)
|
||||
$0 stop
|
||||
if is_running; then
|
||||
echo "Unable to stop, will not attempt to start"
|
||||
exit 1
|
||||
fi
|
||||
$0 start
|
||||
;;
|
||||
status)
|
||||
if is_running; then
|
||||
echo "Running"
|
||||
else
|
||||
echo "Stopped"
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 {start|stop|restart|status}"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
exit 0
|
||||
`
|
||||
@@ -0,0 +1,173 @@
|
||||
// Copyright 2015 Daniel Theophanes.
|
||||
// Use of this source code is governed by a zlib-style
|
||||
// license that can be found in the LICENSE file.package service
|
||||
|
||||
package service
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/kardianos/osext"
|
||||
)
|
||||
|
||||
func isUpstart() bool {
|
||||
if _, err := os.Stat("/sbin/upstart-udev-bridge"); err == nil {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type upstart struct {
|
||||
i Interface
|
||||
*Config
|
||||
}
|
||||
|
||||
func newUpstartService(i Interface, c *Config) (Service, error) {
|
||||
s := &upstart{
|
||||
i: i,
|
||||
Config: c,
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (s *upstart) String() string {
|
||||
if len(s.DisplayName) > 0 {
|
||||
return s.DisplayName
|
||||
}
|
||||
return s.Name
|
||||
}
|
||||
|
||||
// Upstart has some support for user services in graphical sessions.
|
||||
// Due to the mix of actual support for user services over versions, just don't bother.
|
||||
// Upstart will be replaced by systemd in most cases anyway.
|
||||
var errNoUserServiceUpstart = errors.New("User services are not supported on Upstart.")
|
||||
|
||||
func (s *upstart) configPath() (cp string, err error) {
|
||||
if s.Config.UserService {
|
||||
err = errNoUserServiceUpstart
|
||||
return
|
||||
}
|
||||
cp = "/etc/init/" + s.Config.Name + ".conf"
|
||||
return
|
||||
}
|
||||
func (s *upstart) template() *template.Template {
|
||||
return template.Must(template.New("").Funcs(tf).Parse(upstartScript))
|
||||
}
|
||||
|
||||
func (s *upstart) Install() error {
|
||||
confPath, err := s.configPath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = os.Stat(confPath)
|
||||
if err == nil {
|
||||
return fmt.Errorf("Init already exists: %s", confPath)
|
||||
}
|
||||
|
||||
f, err := os.Create(confPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
path, err := osext.Executable()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var to = &struct {
|
||||
*Config
|
||||
Path string
|
||||
}{
|
||||
s.Config,
|
||||
path,
|
||||
}
|
||||
|
||||
return s.template().Execute(f, to)
|
||||
}
|
||||
|
||||
func (s *upstart) Uninstall() error {
|
||||
cp, err := s.configPath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.Remove(cp); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *upstart) Logger(errs chan<- error) (Logger, error) {
|
||||
if system.Interactive() {
|
||||
return ConsoleLogger, nil
|
||||
}
|
||||
return s.SystemLogger(errs)
|
||||
}
|
||||
func (s *upstart) SystemLogger(errs chan<- error) (Logger, error) {
|
||||
return newSysLogger(s.Name, errs)
|
||||
}
|
||||
|
||||
func (s *upstart) Run() (err error) {
|
||||
err = s.i.Start(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sigChan := make(chan os.Signal, 3)
|
||||
|
||||
signal.Notify(sigChan, os.Interrupt, os.Kill)
|
||||
|
||||
<-sigChan
|
||||
|
||||
return s.i.Stop(s)
|
||||
}
|
||||
|
||||
func (s *upstart) Start() error {
|
||||
return exec.Command("initctl", "start", s.Name).Run()
|
||||
}
|
||||
|
||||
func (s *upstart) Stop() error {
|
||||
return exec.Command("initctl", "stop", s.Name).Run()
|
||||
}
|
||||
|
||||
func (s *upstart) Restart() error {
|
||||
err := s.Stop()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
return s.Start()
|
||||
}
|
||||
|
||||
// The upstart script should stop with an INT or the Go runtime will terminate
|
||||
// the program before the Stop handler can run.
|
||||
const upstartScript = `# {{.Description}}
|
||||
|
||||
{{if .DisplayName}}description "{{.DisplayName}}"{{end}}
|
||||
|
||||
kill signal INT
|
||||
start on filesystem or runlevel [2345]
|
||||
stop on runlevel [!2345]
|
||||
|
||||
#setuid username
|
||||
|
||||
respawn
|
||||
respawn limit 10 5
|
||||
umask 022
|
||||
|
||||
console none
|
||||
|
||||
pre-start script
|
||||
test -x {{.Path}} || { stop; exit 0; }
|
||||
end script
|
||||
|
||||
# Start
|
||||
exec {{.Path}}{{range .Arguments}} {{.|cmd}}{{end}}
|
||||
`
|
||||
Reference in New Issue
Block a user