Skip to content

Commit

Permalink
Add support for OpenRC services (alpine/busybox) (#252)
Browse files Browse the repository at this point in the history
  • Loading branch information
trawler authored Dec 11, 2020
1 parent 18c957a commit ef35c56
Show file tree
Hide file tree
Showing 5 changed files with 251 additions and 2 deletions.
14 changes: 12 additions & 2 deletions linux_test/Makefile
Original file line number Diff line number Diff line change
@@ -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 go test -installsuffix netgo -a -c ..

clean:
-rm service.test
Expand Down Expand Up @@ -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
3 changes: 3 additions & 0 deletions linux_test/openrc/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
FROM alpine:latest
ADD service.test /tmp/
CMD /tmp/service.test -test.v=true
1 change: 1 addition & 0 deletions service.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ const (
optionSysvScript = "SysvScript"
optionUpstartScript = "UpstartScript"
optionLaunchdConfig = "LaunchdConfig"
optionOpenRCScript = "OpenRCScript"
)

// Status represents service status as an byte value
Expand Down
9 changes: 9 additions & 0 deletions service_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,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 },
Expand Down
226 changes: 226 additions & 0 deletions service_openrc_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
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
}

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}}
`

0 comments on commit ef35c56

Please sign in to comment.