Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for OpenRC services (alpine/busybox) #252

Merged
merged 1 commit into from
Dec 11, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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}}
`