diff --git a/linux_test/Makefile b/linux_test/Makefile index dd3d0cf1..3a3d0de3 100644 --- a/linux_test/Makefile +++ b/linux_test/Makefile @@ -1,8 +1,9 @@ -all: sysv systemd upstart clean +all: sysv systemd upstart openrc clean +# compile `go test` binary statically test: - @go test -c .. + @CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go test -installsuffix netgo -a -c .. clean: -rm service.test @@ -33,3 +34,12 @@ upstart: test @-docker rm $(shell docker ps -l -q) @-docker rmi -f service.test.upstart @-rm upstart/service.test + +openrc: test + @echo openrc + @cp service.test openrc/ + @docker build -q --tag="service.test.openrc" openrc + @-docker run service.test.openrc + @-docker rm $(shell docker ps -l -q) + @-docker rmi -f service.test.openrc + @-rm openrc/service.test \ No newline at end of file diff --git a/linux_test/openrc/Dockerfile b/linux_test/openrc/Dockerfile new file mode 100644 index 00000000..c333545a --- /dev/null +++ b/linux_test/openrc/Dockerfile @@ -0,0 +1,3 @@ +FROM alpine:latest +ADD service.test /tmp/ +CMD /tmp/service.test -test.v=true diff --git a/service.go b/service.go index 91a69944..624af3c7 100644 --- a/service.go +++ b/service.go @@ -93,6 +93,7 @@ const ( optionSysvScript = "SysvScript" optionUpstartScript = "UpstartScript" optionLaunchdConfig = "LaunchdConfig" + optionOpenRCScript = "OpenRCScript" ) // Status represents service status as an byte value @@ -386,6 +387,11 @@ type Service interface { Status() (Status, error) } +// ConfigInfoer is an optional interface which allows for certain information to be obtained. +type ConfigInfoer interface { + ConfigPath() (string, error) +} + // ControlAction list valid string texts to use in Control. var ControlAction = [5]string{"start", "stop", "restart", "install", "uninstall"} diff --git a/service_aix.go b/service_aix.go index 5710b96e..e9351880 100644 --- a/service_aix.go +++ b/service_aix.go @@ -26,6 +26,8 @@ const version = "aix-ssrc" type aixSystem struct{} +var _ ConfigInfoer = &aixService{} + func (aixSystem) String() string { return version } @@ -114,7 +116,7 @@ func (s *aixService) template() *template.Template { } } -func (s *aixService) configPath() (cp string, err error) { +func (s *aixService) ConfigPath() (cp string, err error) { cp = "/etc/rc.d/init.d/" + s.Config.Name return } @@ -131,7 +133,7 @@ func (s *aixService) Install() error { } // write start script - confPath, err := s.configPath() + confPath, err := s.ConfigPath() if err != nil { return err } @@ -182,7 +184,7 @@ func (s *aixService) Uninstall() error { return err } - confPath, err := s.configPath() + confPath, err := s.ConfigPath() if err != nil { return err } @@ -211,7 +213,7 @@ func (s *aixService) Status() (Status, error) { } } - confPath, err := s.configPath() + confPath, err := s.ConfigPath() if err != nil { return StatusUnknown, err } diff --git a/service_darwin.go b/service_darwin.go index 88e46ce8..2e47f743 100644 --- a/service_darwin.go +++ b/service_darwin.go @@ -70,6 +70,12 @@ type darwinLaunchdService struct { userService bool } +var _ ConfigInfoer = &darwinLaunchdService{} + +func (s *darwinLaunchdService) ConfigPath() (string, error) { + return s.getServiceFilePath() +} + func (s *darwinLaunchdService) String() string { if len(s.DisplayName) > 0 { return s.DisplayName diff --git a/service_freebsd.go b/service_freebsd.go index 66bb5746..9db67d33 100644 --- a/service_freebsd.go +++ b/service_freebsd.go @@ -57,6 +57,8 @@ type freebsdService struct { *Config } +var _ ConfigInfoer = &freebsdService{} + func (s *freebsdService) String() string { if len(s.DisplayName) > 0 { return s.DisplayName @@ -87,7 +89,7 @@ func (s *freebsdService) template() *template.Template { } } -func (s *freebsdService) configPath() (cp string, err error) { +func (s *freebsdService) ConfigPath() (cp string, err error) { cp = "/usr/local/etc/rc.d/" + s.Config.Name return } @@ -99,7 +101,7 @@ func (s *freebsdService) Install() error { } // write start script - confPath, err := s.configPath() + confPath, err := s.ConfigPath() if err != nil { return err } @@ -135,7 +137,7 @@ func (s *freebsdService) Install() error { } func (s *freebsdService) Uninstall() error { - cp, err := s.configPath() + cp, err := s.ConfigPath() if err != nil { return err } @@ -143,7 +145,7 @@ func (s *freebsdService) Uninstall() error { } func (s *freebsdService) Status() (Status, error) { - cp, err := s.configPath() + cp, err := s.ConfigPath() if err != nil { return StatusUnknown, err } diff --git a/service_linux.go b/service_linux.go index d0345466..13de1d31 100644 --- a/service_linux.go +++ b/service_linux.go @@ -21,6 +21,8 @@ type linuxSystemService struct { new func(i Interface, platform string, c *Config) (Service, error) } +var _ ConfigInfoer = &linuxSystemService{} + func (sc linuxSystemService) String() string { return sc.name } @@ -33,6 +35,9 @@ func (sc linuxSystemService) Interactive() bool { func (sc linuxSystemService) New(i Interface, c *Config) (Service, error) { return sc.new(i, sc.String(), c) } +func (sc linuxSystemService) ConfigPath() (string, error) { + return "", fmt.Errorf("not implemented") +} func init() { ChooseSystem(linuxSystemService{ @@ -53,6 +58,15 @@ func init() { }, new: newUpstartService, }, + linuxSystemService{ + name: "linux-openrc", + detect: isOpenRC, + interactive: func() bool { + is, _ := isInteractive() + return is + }, + new: newOpenRCService, + }, linuxSystemService{ name: "unix-systemv", detect: func() bool { return true }, diff --git a/service_openrc_linux.go b/service_openrc_linux.go new file mode 100644 index 00000000..3774d85e --- /dev/null +++ b/service_openrc_linux.go @@ -0,0 +1,228 @@ +package service + +import ( + "bytes" + "errors" + "fmt" + "os" + "os/exec" + "os/signal" + "regexp" + "strings" + "syscall" + "text/template" + "time" +) + +func isOpenRC() bool { + if _, err := exec.LookPath("openrc-init"); err == nil { + return true + } + if _, err := os.Stat("/etc/inittab"); err == nil { + filerc, err := os.Open("/etc/inittab") + if err != nil { + return false + } + defer filerc.Close() + + buf := new(bytes.Buffer) + buf.ReadFrom(filerc) + contents := buf.String() + + re := regexp.MustCompile(`::sysinit:.*openrc.*sysinit`) + matches := re.FindStringSubmatch(contents) + if len(matches) > 0 { + return true + } + return false + } + return false +} + +type openrc struct { + i Interface + platform string + *Config +} + +var _ ConfigInfoer = &openrc{} + +func (s *openrc) String() string { + if len(s.DisplayName) > 0 { + return s.DisplayName + } + return s.Name +} + +func (s *openrc) Platform() string { + return s.platform +} + +func (s *openrc) template() *template.Template { + customScript := s.Option.string(optionOpenRCScript, "") + + if customScript != "" { + return template.Must(template.New("").Funcs(tf).Parse(customScript)) + } else { + return template.Must(template.New("").Funcs(tf).Parse(openRCScript)) + } +} + +func newOpenRCService(i Interface, platform string, c *Config) (Service, error) { + s := &openrc{ + i: i, + platform: platform, + Config: c, + } + return s, nil +} + +var errNoUserServiceOpenRC = errors.New("user services are not supported on OpenRC") + +func (s *openrc) ConfigPath() (cp string, err error) { + if s.Option.bool(optionUserService, optionUserServiceDefault) { + err = errNoUserServiceOpenRC + return + } + cp = "/etc/init.d/" + s.Config.Name + return +} + +func (s *openrc) 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() + + err = os.Chmod(confPath, 0755) + if err != nil { + return err + } + + path, err := s.execPath() + 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 + } + // run rc-update + return s.runAction("add") +} + +func (s *openrc) Uninstall() error { + confPath, err := s.ConfigPath() + if err != nil { + return err + } + if err := os.Remove(confPath); err != nil { + return err + } + return s.runAction("delete") +} + +func (s *openrc) Logger(errs chan<- error) (Logger, error) { + if system.Interactive() { + return ConsoleLogger, nil + } + return s.SystemLogger(errs) +} + +func (s *openrc) SystemLogger(errs chan<- error) (Logger, error) { + return newSysLogger(s.Name, errs) +} + +func (s *openrc) Run() (err error) { + err = s.i.Start(s) + if err != nil { + return err + } + + s.Option.funcSingle(optionRunWait, func() { + var sigChan = make(chan os.Signal, 3) + signal.Notify(sigChan, syscall.SIGTERM, os.Interrupt) + <-sigChan + })() + + return s.i.Stop(s) +} + +func (s *openrc) Status() (Status, error) { + _, out, err := runWithOutput("rc-service", s.Name, "status") + if err != nil { + return StatusUnknown, err + } + + switch { + case strings.HasPrefix(out, "Running"): + return StatusRunning, nil + case strings.HasPrefix(out, "Stopped"): + return StatusStopped, nil + default: + return StatusUnknown, ErrNotInstalled + } +} + +func (s *openrc) Start() error { + return run("rc-service", s.Name, "start") +} + +func (s *openrc) Stop() error { + return run("rc-service", s.Name, "stop") +} + +func (s *openrc) Restart() error { + err := s.Stop() + if err != nil { + return err + } + time.Sleep(50 * time.Millisecond) + return s.Start() +} + +func (s *openrc) runAction(action string) error { + return s.run(action, s.Name) +} + +func (s *openrc) run(action string, args ...string) error { + return run("rc-update", append([]string{action}, args...)...) +} + +const openRCScript = `#!/sbin/openrc-run +supervisor=supervise-daemon +name="{{.DisplayName}}" +description="{{.Description}}" +command={{.Path|cmdEscape}} +{{- if .Arguments }} +command_args="{{range .Arguments}}{{.}} {{end}}" +{{- end }} +name=$(basename $(readlink -f $command)) +supervise_daemon_args="--stdout /var/log/${name}.log --stderr /var/log/${name}.err" + +{{- if .Dependencies }} +depend() { +{{- range $i, $dep := .Dependencies}} +{{"\t"}}{{$dep}}{{end}} +} +{{- end}} +` diff --git a/service_solaris.go b/service_solaris.go index 55e00740..5e9732be 100644 --- a/service_solaris.go +++ b/service_solaris.go @@ -68,6 +68,8 @@ type solarisService struct { Prefix string } +var _ ConfigInfoer = &solarisService{} + func (s *solarisService) String() string { if len(s.DisplayName) > 0 { return s.DisplayName @@ -98,7 +100,7 @@ func (s *solarisService) template() *template.Template { } } -func (s *solarisService) configPath() (string, error) { +func (s *solarisService) ConfigPath() (string, error) { return "/lib/svc/manifest/" + s.Prefix + "/" + s.Config.Name + ".xml", nil } @@ -108,7 +110,7 @@ func (s *solarisService) getFMRI() string { func (s *solarisService) Install() error { // write start script - confPath, err := s.configPath() + confPath, err := s.ConfigPath() if err != nil { return err } @@ -161,7 +163,7 @@ func (s *solarisService) Install() error { func (s *solarisService) Uninstall() error { s.Stop() - confPath, err := s.configPath() + confPath, err := s.ConfigPath() if err != nil { return err } diff --git a/service_systemd_linux.go b/service_systemd_linux.go index 36fb2656..95a319a4 100644 --- a/service_systemd_linux.go +++ b/service_systemd_linux.go @@ -46,6 +46,8 @@ type systemd struct { *Config } +var _ ConfigInfoer = &systemd{} + func newSystemdService(i Interface, platform string, c *Config) (Service, error) { s := &systemd{ i: i, @@ -67,7 +69,7 @@ func (s *systemd) Platform() string { return s.platform } -func (s *systemd) configPath() (cp string, err error) { +func (s *systemd) ConfigPath() (cp string, err error) { if !s.isUserService() { cp = "/etc/systemd/system/" + s.Config.Name + ".service" return @@ -134,7 +136,7 @@ func (s *systemd) isUserService() bool { } func (s *systemd) Install() error { - confPath, err := s.configPath() + confPath, err := s.ConfigPath() if err != nil { return err } @@ -194,7 +196,7 @@ func (s *systemd) Uninstall() error { if err != nil { return err } - cp, err := s.configPath() + cp, err := s.ConfigPath() if err != nil { return err } diff --git a/service_sysv_linux.go b/service_sysv_linux.go index f6eebb05..0907a0d9 100644 --- a/service_sysv_linux.go +++ b/service_sysv_linux.go @@ -21,6 +21,8 @@ type sysv struct { *Config } +var _ ConfigInfoer = &sysv{} + func newSystemVService(i Interface, platform string, c *Config) (Service, error) { s := &sysv{ i: i, @@ -44,7 +46,7 @@ func (s *sysv) Platform() string { var errNoUserServiceSystemV = errors.New("User services are not supported on SystemV.") -func (s *sysv) configPath() (cp string, err error) { +func (s *sysv) ConfigPath() (cp string, err error) { if s.Option.bool(optionUserService, optionUserServiceDefault) { err = errNoUserServiceSystemV return @@ -64,7 +66,7 @@ func (s *sysv) template() *template.Template { } func (s *sysv) Install() error { - confPath, err := s.configPath() + confPath, err := s.ConfigPath() if err != nil { return err } @@ -115,7 +117,7 @@ func (s *sysv) Install() error { } func (s *sysv) Uninstall() error { - cp, err := s.configPath() + cp, err := s.ConfigPath() if err != nil { return err } diff --git a/service_test.go b/service_test.go index 886b0cfb..32e0ac26 100644 --- a/service_test.go +++ b/service_test.go @@ -41,6 +41,22 @@ func TestRunInterrupt(t *testing.T) { } } +func TestConfigPath(t *testing.T) { + p := &program{} + sc := &service.Config{ + Name: "go_service_test", + } + s, err := service.New(p, sc) + if err != nil { + t.Fatalf("New err: %s", err) + } + + _, err = s.(service.ConfigInfoer).ConfigPath() + if err != nil { + t.Fatal("Failed to fetch or not implemented") + } +} + const testInstallEnv = "TEST_USER_INSTALL" // Should always run, without asking for any permission diff --git a/service_upstart_linux.go b/service_upstart_linux.go index be404d95..e27ffa58 100644 --- a/service_upstart_linux.go +++ b/service_upstart_linux.go @@ -35,6 +35,8 @@ type upstart struct { *Config } +var _ ConfigInfoer = &upstart{} + func newUpstartService(i Interface, platform string, c *Config) (Service, error) { s := &upstart{ i: i, @@ -61,7 +63,7 @@ func (s *upstart) Platform() string { // 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) { +func (s *upstart) ConfigPath() (cp string, err error) { if s.Option.bool(optionUserService, optionUserServiceDefault) { err = errNoUserServiceUpstart return @@ -126,7 +128,7 @@ func (s *upstart) template() *template.Template { } func (s *upstart) Install() error { - confPath, err := s.configPath() + confPath, err := s.ConfigPath() if err != nil { return err } @@ -164,7 +166,7 @@ func (s *upstart) Install() error { } func (s *upstart) Uninstall() error { - cp, err := s.configPath() + cp, err := s.ConfigPath() if err != nil { return err }