Pull request: 3226 support service on OpenBSD
Merge in DNS/adguard-home from 3226-openbsd-svc to master
Closes #3226.
Squashed commit of the following:
commit bcf1a31a8343ae4b35c7cadeb45bc7a10863fda2
Author: Eugene Burkov <e.burkov@adguard.com>
Date: Tue Aug 24 17:43:31 2021 +0300
aghos: imp code
commit 3d4060ce6b5a37cf7af05b117b8bc4a49f69b2e8
Merge: 9e9225ec b92db25e
Author: Eugene Burkov <e.burkov@adguard.com>
Date: Tue Aug 24 17:09:00 2021 +0300
Merge branch 'master' into 3226-openbsd-svc
commit 9e9225ecb2af30fe46999b43c0683e4b3c946778
Author: Eugene Burkov <e.burkov@adguard.com>
Date: Tue Aug 24 17:02:52 2021 +0300
home: fix lil bugs
commit 03456f9a09081c6178dca0ac9887590b5d24f333
Author: Eugene Burkov <e.burkov@adguard.com>
Date: Tue Aug 24 16:18:48 2021 +0300
home: imp code
commit 5cdf4fcbae78c07b663190012228003fe94bfdee
Author: Eugene Burkov <e.burkov@adguard.com>
Date: Fri Aug 20 23:32:15 2021 +0300
home: imp code, docs
commit d2a95faa0a7d176cdcba304e7226ebe11c1ce341
Author: Eugene Burkov <e.burkov@adguard.com>
Date: Fri Aug 20 14:01:53 2021 +0300
home: sup service on openbsd
This commit is contained in:
parent
b92db25e6a
commit
16092e8ba9
|
@ -27,7 +27,8 @@ and this project adheres to
|
||||||
- Settable timeouts for querying the upstream servers ([#2280]).
|
- Settable timeouts for querying the upstream servers ([#2280]).
|
||||||
- Configuration file parameters to change group and user ID on startup on Unix
|
- Configuration file parameters to change group and user ID on startup on Unix
|
||||||
([#2763]).
|
([#2763]).
|
||||||
- Experimental OpenBSD support for AMD64 and 64-bit ARM CPUs ([#2439], [#3225]).
|
- Experimental OpenBSD support for AMD64 and 64-bit ARM CPUs ([#2439], [#3225],
|
||||||
|
[#3226]).
|
||||||
- Support for custom port in DNS-over-HTTPS profiles for Apple's devices
|
- Support for custom port in DNS-over-HTTPS profiles for Apple's devices
|
||||||
([#3172]).
|
([#3172]).
|
||||||
- `darwin/arm64` support ([#2443]).
|
- `darwin/arm64` support ([#2443]).
|
||||||
|
@ -118,6 +119,7 @@ and this project adheres to
|
||||||
[#3198]: https://github.com/AdguardTeam/AdGuardHome/issues/3198
|
[#3198]: https://github.com/AdguardTeam/AdGuardHome/issues/3198
|
||||||
[#3217]: https://github.com/AdguardTeam/AdGuardHome/issues/3217
|
[#3217]: https://github.com/AdguardTeam/AdGuardHome/issues/3217
|
||||||
[#3225]: https://github.com/AdguardTeam/AdGuardHome/issues/3225
|
[#3225]: https://github.com/AdguardTeam/AdGuardHome/issues/3225
|
||||||
|
[#3226]: https://github.com/AdguardTeam/AdGuardHome/issues/3226
|
||||||
[#3256]: https://github.com/AdguardTeam/AdGuardHome/issues/3256
|
[#3256]: https://github.com/AdguardTeam/AdGuardHome/issues/3256
|
||||||
[#3257]: https://github.com/AdguardTeam/AdGuardHome/issues/3257
|
[#3257]: https://github.com/AdguardTeam/AdGuardHome/issues/3257
|
||||||
[#3289]: https://github.com/AdguardTeam/AdGuardHome/issues/3289
|
[#3289]: https://github.com/AdguardTeam/AdGuardHome/issues/3289
|
||||||
|
|
|
@ -13,6 +13,7 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/golibs/errors"
|
||||||
"github.com/AdguardTeam/golibs/log"
|
"github.com/AdguardTeam/golibs/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -59,8 +60,11 @@ func RunCommand(command string, arguments ...string) (int, string, error) {
|
||||||
if len(out) > MaxCmdOutputSize {
|
if len(out) > MaxCmdOutputSize {
|
||||||
out = out[:MaxCmdOutputSize]
|
out = out[:MaxCmdOutputSize]
|
||||||
}
|
}
|
||||||
if err != nil {
|
|
||||||
return 1, "", fmt.Errorf("exec.Command(%s) failed: %v: %s", command, err, string(out))
|
if errors.As(err, new(*exec.ExitError)) {
|
||||||
|
return cmd.ProcessState.ExitCode(), string(out), nil
|
||||||
|
} else if err != nil {
|
||||||
|
return 1, "", fmt.Errorf("exec.Command(%s) failed: %w: %s", command, err, string(out))
|
||||||
}
|
}
|
||||||
|
|
||||||
return cmd.ProcessState.ExitCode(), string(out), nil
|
return cmd.ProcessState.ExitCode(), string(out), nil
|
||||||
|
|
|
@ -116,13 +116,6 @@ func Main(clientBuildFS fs.FS) {
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if args.serviceControlAction != "" {
|
if args.serviceControlAction != "" {
|
||||||
// TODO(a.garipov): github.com/kardianos/service doesn't seem to
|
|
||||||
// support OpenBSD currently. Either patch it to do so or make
|
|
||||||
// our own implementation of the service.System interface.
|
|
||||||
if runtime.GOOS == "openbsd" {
|
|
||||||
log.Fatal("service actions are not supported on openbsd, see issue 3226")
|
|
||||||
}
|
|
||||||
|
|
||||||
handleServiceControlAction(args, clientBuildFS)
|
handleServiceControlAction(args, clientBuildFS)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
|
@ -8,8 +8,10 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/aghos"
|
"github.com/AdguardTeam/AdGuardHome/internal/aghos"
|
||||||
|
"github.com/AdguardTeam/AdGuardHome/internal/version"
|
||||||
"github.com/AdguardTeam/golibs/errors"
|
"github.com/AdguardTeam/golibs/errors"
|
||||||
"github.com/AdguardTeam/golibs/log"
|
"github.com/AdguardTeam/golibs/log"
|
||||||
"github.com/kardianos/service"
|
"github.com/kardianos/service"
|
||||||
|
@ -26,33 +28,37 @@ const (
|
||||||
serviceDescription = "AdGuard Home: Network-level blocker"
|
serviceDescription = "AdGuard Home: Network-level blocker"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Represents the program that will be launched by a service or daemon
|
// program represents the program that will be launched by as a service or a
|
||||||
|
// daemon.
|
||||||
type program struct {
|
type program struct {
|
||||||
clientBuildFS fs.FS
|
clientBuildFS fs.FS
|
||||||
opts options
|
opts options
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start should quickly start the program
|
// Start implements service.Interface interface for *program.
|
||||||
func (p *program) Start(s service.Service) error {
|
func (p *program) Start(_ service.Service) (err error) {
|
||||||
// Start should not block. Do the actual work async.
|
// Start should not block. Do the actual work async.
|
||||||
args := p.opts
|
args := p.opts
|
||||||
args.runningAsService = true
|
args.runningAsService = true
|
||||||
|
|
||||||
go run(args, p.clientBuildFS)
|
go run(args, p.clientBuildFS)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop stops the program
|
// Stop implements service.Interface interface for *program.
|
||||||
func (p *program) Stop(s service.Service) error {
|
func (p *program) Stop(_ service.Service) error {
|
||||||
// Stop should not block. Return with a few seconds.
|
// Stop should not block. Return with a few seconds.
|
||||||
if Context.appSignalChannel == nil {
|
if Context.appSignalChannel == nil {
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
Context.appSignalChannel <- syscall.SIGINT
|
Context.appSignalChannel <- syscall.SIGINT
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// svcStatus check the service's status.
|
// svcStatus returns the service's status.
|
||||||
//
|
//
|
||||||
// On OpenWrt, the service utility may not exist. We use our service script
|
// On OpenWrt, the service utility may not exist. We use our service script
|
||||||
// directly in this case.
|
// directly in this case.
|
||||||
|
@ -87,8 +93,8 @@ func svcAction(s service.Service, action string) (err error) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send SIGHUP to a process with ID taken from our pid-file
|
// Send SIGHUP to a process with PID taken from our .pid file. If it doesn't
|
||||||
// If pid-file doesn't exist, find our PID using 'ps' command
|
// exist, find our PID using 'ps' command.
|
||||||
func sendSigReload() {
|
func sendSigReload() {
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
log.Error("not implemented on windows")
|
log.Error("not implemented on windows")
|
||||||
|
@ -152,11 +158,16 @@ func sendSigReload() {
|
||||||
// it is specified when we register a service, and it indicates to the app
|
// it is specified when we register a service, and it indicates to the app
|
||||||
// that it is being run as a service/daemon.
|
// that it is being run as a service/daemon.
|
||||||
func handleServiceControlAction(opts options, clientBuildFS fs.FS) {
|
func handleServiceControlAction(opts options, clientBuildFS fs.FS) {
|
||||||
|
// Call chooseSystem expicitly to introduce OpenBSD support for service
|
||||||
|
// package. It's a noop for other GOOS values.
|
||||||
|
chooseSystem()
|
||||||
|
|
||||||
action := opts.serviceControlAction
|
action := opts.serviceControlAction
|
||||||
log.Printf("Service control action: %s", action)
|
log.Printf("Service control action: %s", action)
|
||||||
|
|
||||||
if action == "reload" {
|
if action == "reload" {
|
||||||
sendSigReload()
|
sendSigReload()
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -164,6 +175,7 @@ func handleServiceControlAction(opts options, clientBuildFS fs.FS) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Unable to find the path to the current directory")
|
log.Fatal("Unable to find the path to the current directory")
|
||||||
}
|
}
|
||||||
|
|
||||||
runOpts := opts
|
runOpts := opts
|
||||||
runOpts.serviceControlAction = "run"
|
runOpts.serviceControlAction = "run"
|
||||||
svcConfig := &service.Config{
|
svcConfig := &service.Config{
|
||||||
|
@ -174,39 +186,39 @@ func handleServiceControlAction(opts options, clientBuildFS fs.FS) {
|
||||||
Arguments: serialize(runOpts),
|
Arguments: serialize(runOpts),
|
||||||
}
|
}
|
||||||
configureService(svcConfig)
|
configureService(svcConfig)
|
||||||
|
|
||||||
prg := &program{
|
prg := &program{
|
||||||
clientBuildFS: clientBuildFS,
|
clientBuildFS: clientBuildFS,
|
||||||
opts: runOpts,
|
opts: runOpts,
|
||||||
}
|
}
|
||||||
s, err := service.New(prg, svcConfig)
|
var s service.Service
|
||||||
if err != nil {
|
if s, err = service.New(prg, svcConfig); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if action == "status" {
|
switch action {
|
||||||
|
case "status":
|
||||||
handleServiceStatusCommand(s)
|
handleServiceStatusCommand(s)
|
||||||
} else if action == "run" {
|
case "run":
|
||||||
err = s.Run()
|
if err = s.Run(); err != nil {
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Failed to run service: %s", err)
|
log.Fatalf("Failed to run service: %s", err)
|
||||||
}
|
}
|
||||||
} else if action == "install" {
|
case "install":
|
||||||
initConfigFilename(opts)
|
initConfigFilename(opts)
|
||||||
initWorkingDir(opts)
|
initWorkingDir(opts)
|
||||||
handleServiceInstallCommand(s)
|
handleServiceInstallCommand(s)
|
||||||
} else if action == "uninstall" {
|
case "uninstall":
|
||||||
handleServiceUninstallCommand(s)
|
handleServiceUninstallCommand(s)
|
||||||
} else {
|
default:
|
||||||
err = svcAction(s, action)
|
if err = svcAction(s, action); err != nil {
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("Action %s has been done successfully on %s", action, service.ChosenSystem().String())
|
log.Printf("action %s has been done successfully on %s", action, service.ChosenSystem())
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleServiceStatusCommand handles service "status" command
|
// handleServiceStatusCommand handles service "status" command.
|
||||||
func handleServiceStatusCommand(s service.Service) {
|
func handleServiceStatusCommand(s service.Service) {
|
||||||
status, errSt := svcStatus(s)
|
status, errSt := svcStatus(s)
|
||||||
if errSt != nil {
|
if errSt != nil {
|
||||||
|
@ -231,15 +243,16 @@ func handleServiceInstallCommand(s service.Service) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if aghos.IsOpenWrt() {
|
if aghos.IsOpenWrt() {
|
||||||
// On OpenWrt it is important to run enable after the service installation
|
// On OpenWrt it is important to run enable after the service
|
||||||
// Otherwise, the service won't start on the system startup
|
// installation Otherwise, the service won't start on the system
|
||||||
|
// startup.
|
||||||
_, err = runInitdCommand("enable")
|
_, err = runInitdCommand("enable")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start automatically after install
|
// Start automatically after install.
|
||||||
err = svcAction(s, "start")
|
err = svcAction(s, "start")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to start the service: %s", err)
|
log.Fatalf("Failed to start the service: %s", err)
|
||||||
|
@ -267,14 +280,13 @@ func handleServiceUninstallCommand(s service.Service) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err := svcAction(s, "uninstall")
|
if err := svcAction(s, "uninstall"); err != nil {
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if runtime.GOOS == "darwin" {
|
if runtime.GOOS == "darwin" {
|
||||||
// Remove log files on cleanup and log errors.
|
// Remove log files on cleanup and log errors.
|
||||||
err = os.Remove(launchdStdoutPath)
|
err := os.Remove(launchdStdoutPath)
|
||||||
if err != nil && !errors.Is(err, os.ErrNotExist) {
|
if err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||||
log.Printf("removing stdout file: %s", err)
|
log.Printf("removing stdout file: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -313,6 +325,9 @@ func configureService(c *service.Config) {
|
||||||
} else if runtime.GOOS == "freebsd" {
|
} else if runtime.GOOS == "freebsd" {
|
||||||
c.Option["SysvScript"] = freeBSDScript
|
c.Option["SysvScript"] = freeBSDScript
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.Option["RunComScript"] = openBSDScript
|
||||||
|
c.Option["SvcInfo"] = fmt.Sprintf("%s %s", version.Full(), time.Now())
|
||||||
}
|
}
|
||||||
|
|
||||||
// runInitdCommand runs init.d service command
|
// runInitdCommand runs init.d service command
|
||||||
|
@ -551,3 +566,17 @@ command="/usr/sbin/daemon"
|
||||||
command_args="-p ${pidfile} -f -r {{.WorkingDirectory}}/{{.Name}}"
|
command_args="-p ${pidfile} -f -r {{.WorkingDirectory}}/{{.Name}}"
|
||||||
run_rc_command "$1"
|
run_rc_command "$1"
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const openBSDScript = `#!/bin/sh
|
||||||
|
#
|
||||||
|
# $OpenBSD: {{ .SvcInfo }}
|
||||||
|
|
||||||
|
daemon="{{.Path}}"
|
||||||
|
daemon_flags={{ .Arguments | args }}
|
||||||
|
|
||||||
|
. /etc/rc.d/rc.subr
|
||||||
|
|
||||||
|
rc_bg=YES
|
||||||
|
|
||||||
|
rc_cmd $1
|
||||||
|
`
|
||||||
|
|
|
@ -0,0 +1,432 @@
|
||||||
|
//go:build openbsd
|
||||||
|
// +build openbsd
|
||||||
|
|
||||||
|
package home
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/AdGuardHome/internal/aghos"
|
||||||
|
"github.com/AdguardTeam/golibs/errors"
|
||||||
|
"github.com/AdguardTeam/golibs/log"
|
||||||
|
"github.com/AdguardTeam/golibs/stringutil"
|
||||||
|
"github.com/kardianos/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
// OpenBSD Service Implementation
|
||||||
|
//
|
||||||
|
// The file contains OpenBSD implementations for service.System and
|
||||||
|
// service.Service interfaces. It uses the default approach for RunCom-based
|
||||||
|
// services systems, e.g. rc.d script. It's written as if it was in a separate
|
||||||
|
// package and has only one internal dependency.
|
||||||
|
//
|
||||||
|
// TODO(e.burkov): Perhaps, file a PR to github.com/kardianos/service.
|
||||||
|
|
||||||
|
// sysVersion is the version of local service.System interface
|
||||||
|
// implementation.
|
||||||
|
const sysVersion = "openbsd-runcom"
|
||||||
|
|
||||||
|
func chooseSystem() {
|
||||||
|
service.ChooseSystem(openbsdSystem{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// openbsdSystem is the service.System to be used on the OpenBSD.
|
||||||
|
type openbsdSystem struct{}
|
||||||
|
|
||||||
|
// String implements service.System interface for openbsdSystem.
|
||||||
|
func (openbsdSystem) String() string {
|
||||||
|
return sysVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect implements service.System interface for openbsdSystem.
|
||||||
|
func (openbsdSystem) Detect() (ok bool) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interactive implements service.System interface for openbsdSystem.
|
||||||
|
func (openbsdSystem) Interactive() (ok bool) {
|
||||||
|
return os.Getppid() != 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// New implements service.System interface for openbsdSystem.
|
||||||
|
func (openbsdSystem) New(i service.Interface, c *service.Config) (s service.Service, err error) {
|
||||||
|
return &openbsdRunComService{
|
||||||
|
i: i,
|
||||||
|
cfg: c,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// openbsdRunComService is the RunCom-based service.Service to be used on the
|
||||||
|
// OpenBSD.
|
||||||
|
type openbsdRunComService struct {
|
||||||
|
i service.Interface
|
||||||
|
cfg *service.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
// Platform implements service.Service interface for *openbsdRunComService.
|
||||||
|
func (*openbsdRunComService) Platform() (p string) {
|
||||||
|
return "openbsd"
|
||||||
|
}
|
||||||
|
|
||||||
|
// String implements service.Service interface for *openbsdRunComService.
|
||||||
|
func (s *openbsdRunComService) String() string {
|
||||||
|
return stringutil.Coalesce(s.cfg.DisplayName, s.cfg.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getBool returns the value of the given name from kv, assuming the value is a
|
||||||
|
// boolean. If the value isn't found or is not of the type, the defaultValue is
|
||||||
|
// returned.
|
||||||
|
func getBool(kv service.KeyValue, name string, defaultValue bool) (val bool) {
|
||||||
|
var ok bool
|
||||||
|
if val, ok = kv[name].(bool); ok {
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
// getString returns the value of the given name from kv, assuming the value is
|
||||||
|
// a string. If the value isn't found or is not of the type, the defaultValue
|
||||||
|
// is returned.
|
||||||
|
func getString(kv service.KeyValue, name, defaultValue string) (val string) {
|
||||||
|
var ok bool
|
||||||
|
if val, ok = kv[name].(string); ok {
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
// getFuncNiladic returns the value of the given name from kv, assuming the
|
||||||
|
// value is a func(). If the value isn't found or is not of the type, the
|
||||||
|
// defaultValue is returned.
|
||||||
|
func getFuncNiladic(kv service.KeyValue, name string, defaultValue func()) (val func()) {
|
||||||
|
var ok bool
|
||||||
|
if val, ok = kv[name].(func()); ok {
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// optionUserService is the UserService option name.
|
||||||
|
optionUserService = "UserService"
|
||||||
|
|
||||||
|
// optionUserServiceDefault is the UserService option default value.
|
||||||
|
optionUserServiceDefault = false
|
||||||
|
|
||||||
|
// errNoUserServiceRunCom is returned when the service uses some custom
|
||||||
|
// path to script.
|
||||||
|
errNoUserServiceRunCom errors.Error = "user services are not supported on " + sysVersion
|
||||||
|
)
|
||||||
|
|
||||||
|
// scriptPath returns the absolute path to the script. It's commonly used to
|
||||||
|
// send commands to the service.
|
||||||
|
func (s *openbsdRunComService) scriptPath() (cp string, err error) {
|
||||||
|
if getBool(s.cfg.Option, optionUserService, optionUserServiceDefault) {
|
||||||
|
return "", errNoUserServiceRunCom
|
||||||
|
}
|
||||||
|
|
||||||
|
const scriptPathPref = "/etc/rc.d"
|
||||||
|
|
||||||
|
return filepath.Join(scriptPathPref, s.cfg.Name), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// optionRunComScript is the RunCom script option name.
|
||||||
|
optionRunComScript = "RunComScript"
|
||||||
|
|
||||||
|
// runComScript is the default RunCom script.
|
||||||
|
runComScript = `#!/bin/sh
|
||||||
|
#
|
||||||
|
# $OpenBSD: {{ .SvcInfo }}
|
||||||
|
|
||||||
|
daemon="{{.Path}}"
|
||||||
|
daemon_flags={{ .Arguments | args }}
|
||||||
|
|
||||||
|
. /etc/rc.d/rc.subr
|
||||||
|
|
||||||
|
rc_bg=YES
|
||||||
|
|
||||||
|
rc_cmd $1
|
||||||
|
`
|
||||||
|
)
|
||||||
|
|
||||||
|
// template returns the script template to put into rc.d.
|
||||||
|
func (s *openbsdRunComService) template() (t *template.Template) {
|
||||||
|
tf := map[string]interface{}{
|
||||||
|
"args": func(sl []string) string {
|
||||||
|
return `"` + strings.Join(sl, " ") + `"`
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return template.Must(template.New("").Funcs(tf).Parse(getString(
|
||||||
|
s.cfg.Option,
|
||||||
|
optionRunComScript,
|
||||||
|
runComScript,
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// execPath returns the absolute path to the excutable to be run as a service.
|
||||||
|
func (s *openbsdRunComService) execPath() (path string, err error) {
|
||||||
|
if c := s.cfg; c != nil && len(c.Executable) != 0 {
|
||||||
|
return filepath.Abs(c.Executable)
|
||||||
|
}
|
||||||
|
|
||||||
|
if path, err = os.Executable(); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return filepath.Abs(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// annotate wraps errors.Annotate applying a common error format.
|
||||||
|
func (s *openbsdRunComService) annotate(action string, err error) (annotated error) {
|
||||||
|
return errors.Annotate(err, "%s %s %s service: %w", action, sysVersion, s.cfg.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Install implements service.Service interface for *openbsdRunComService.
|
||||||
|
func (s *openbsdRunComService) Install() (err error) {
|
||||||
|
defer func() { err = s.annotate("installing", err) }()
|
||||||
|
|
||||||
|
if err = s.writeScript(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.configureSysStartup(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// configureSysStartup adds s into the group of packages started with system.
|
||||||
|
func (s *openbsdRunComService) configureSysStartup(enable bool) (err error) {
|
||||||
|
cmd := "enable"
|
||||||
|
if !enable {
|
||||||
|
cmd = "disable"
|
||||||
|
}
|
||||||
|
|
||||||
|
var code int
|
||||||
|
code, _, err = aghos.RunCommand("rcctl", cmd, s.cfg.Name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else if code != 0 {
|
||||||
|
return fmt.Errorf("rcctl finished with code %d", code)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeScript tries to write the script for the service.
|
||||||
|
func (s *openbsdRunComService) writeScript() (err error) {
|
||||||
|
var scriptPath string
|
||||||
|
if scriptPath, err = s.scriptPath(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = os.Stat(scriptPath); !errors.Is(err, os.ErrNotExist) {
|
||||||
|
return fmt.Errorf("script already exists at %s", scriptPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
var execPath string
|
||||||
|
if execPath, err = s.execPath(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
t := s.template()
|
||||||
|
f, err := os.Create(scriptPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("creating rc.d script file: %w", err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
err = t.Execute(f, &struct {
|
||||||
|
*service.Config
|
||||||
|
Path string
|
||||||
|
SvcInfo string
|
||||||
|
}{
|
||||||
|
Config: s.cfg,
|
||||||
|
Path: execPath,
|
||||||
|
SvcInfo: getString(s.cfg.Option, "SvcInfo", s.String()),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors.Annotate(
|
||||||
|
os.Chmod(scriptPath, 0o755),
|
||||||
|
"changing rc.d script file permissions: %w",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uninstall implements service.Service interface for *openbsdRunComService.
|
||||||
|
func (s *openbsdRunComService) Uninstall() (err error) {
|
||||||
|
defer func() { err = s.annotate("uninstalling", err) }()
|
||||||
|
|
||||||
|
if err = s.configureSysStartup(false); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var scriptPath string
|
||||||
|
if scriptPath, err = s.scriptPath(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = os.Remove(scriptPath); errors.Is(err, os.ErrNotExist) {
|
||||||
|
return service.ErrNotInstalled
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors.Annotate(err, "removing rc.d script: %w")
|
||||||
|
}
|
||||||
|
|
||||||
|
// optionRunWait is the name of the option associated with function which waits
|
||||||
|
// for the service to be stopped.
|
||||||
|
const optionRunWait = "RunWait"
|
||||||
|
|
||||||
|
// runWait is the default function to wait for service to be stopped.
|
||||||
|
func runWait() {
|
||||||
|
sigChan := make(chan os.Signal, 3)
|
||||||
|
signal.Notify(sigChan, syscall.SIGTERM, os.Interrupt)
|
||||||
|
<-sigChan
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run implements service.Service interface for *openbsdRunComService.
|
||||||
|
func (s *openbsdRunComService) Run() (err error) {
|
||||||
|
if err = s.i.Start(s); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
getFuncNiladic(s.cfg.Option, optionRunWait, runWait)()
|
||||||
|
|
||||||
|
return s.i.Stop(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// runCom calls the script with the specified cmd.
|
||||||
|
func (s *openbsdRunComService) runCom(cmd string) (out string, err error) {
|
||||||
|
var scriptPath string
|
||||||
|
if scriptPath, err = s.scriptPath(); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(e.burkov): It's possible that os.ErrNotExist is caused by
|
||||||
|
// something different than the service script's non-existence. Keep it
|
||||||
|
// in mind, when replace the aghos.RunCommand.
|
||||||
|
_, out, err = aghos.RunCommand(scriptPath, cmd)
|
||||||
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
|
return "", service.ErrNotInstalled
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Status implements service.Service interface for *openbsdRunComService.
|
||||||
|
func (s *openbsdRunComService) Status() (status service.Status, err error) {
|
||||||
|
defer func() { err = s.annotate("getting status of", err) }()
|
||||||
|
|
||||||
|
var out string
|
||||||
|
if out, err = s.runCom("check"); err != nil {
|
||||||
|
return service.StatusUnknown, err
|
||||||
|
}
|
||||||
|
|
||||||
|
name := s.cfg.Name
|
||||||
|
switch out {
|
||||||
|
case fmt.Sprintf("%s(ok)\n", name):
|
||||||
|
return service.StatusRunning, nil
|
||||||
|
case fmt.Sprintf("%s(failed)\n", name):
|
||||||
|
return service.StatusStopped, nil
|
||||||
|
default:
|
||||||
|
return service.StatusUnknown, service.ErrNotInstalled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start implements service.Service interface for *openbsdRunComService.
|
||||||
|
func (s *openbsdRunComService) Start() (err error) {
|
||||||
|
_, err = s.runCom("start")
|
||||||
|
|
||||||
|
return s.annotate("starting", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop implements service.Service interface for *openbsdRunComService.
|
||||||
|
func (s *openbsdRunComService) Stop() (err error) {
|
||||||
|
_, err = s.runCom("stop")
|
||||||
|
|
||||||
|
return s.annotate("stopping", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restart implements service.Service interface for *openbsdRunComService.
|
||||||
|
func (s *openbsdRunComService) Restart() (err error) {
|
||||||
|
if err = s.Stop(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.Start()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logger implements service.Service interface for *openbsdRunComService.
|
||||||
|
func (s *openbsdRunComService) Logger(errs chan<- error) (l service.Logger, err error) {
|
||||||
|
if service.ChosenSystem().Interactive() {
|
||||||
|
return service.ConsoleLogger, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.SystemLogger(errs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SystemLogger implements service.Service interface for *openbsdRunComService.
|
||||||
|
func (s *openbsdRunComService) SystemLogger(errs chan<- error) (l service.Logger, err error) {
|
||||||
|
return newSysLogger(s.cfg.Name, errs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// newSysLogger returns a stub service.Logger implementation.
|
||||||
|
func newSysLogger(_ string, _ chan<- error) (service.Logger, error) {
|
||||||
|
return sysLogger{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// sysLogger wraps calls of the logging functions understandable for service
|
||||||
|
// interfaces.
|
||||||
|
type sysLogger struct{}
|
||||||
|
|
||||||
|
// Error implements service.Logger interface for sysLogger.
|
||||||
|
func (sysLogger) Error(v ...interface{}) error {
|
||||||
|
log.Error(fmt.Sprint(v...))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warning implements service.Logger interface for sysLogger.
|
||||||
|
func (sysLogger) Warning(v ...interface{}) error {
|
||||||
|
log.Info("warning: %s", fmt.Sprint(v...))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Info implements service.Logger interface for sysLogger.
|
||||||
|
func (sysLogger) Info(v ...interface{}) error {
|
||||||
|
log.Info(fmt.Sprint(v...))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Errorf implements service.Logger interface for sysLogger.
|
||||||
|
func (sysLogger) Errorf(format string, a ...interface{}) error {
|
||||||
|
log.Error(format, a...)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warningf implements service.Logger interface for sysLogger.
|
||||||
|
func (sysLogger) Warningf(format string, a ...interface{}) error {
|
||||||
|
log.Info("warning: %s", fmt.Sprintf(format, a...))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Infof implements service.Logger interface for sysLogger.
|
||||||
|
func (sysLogger) Infof(format string, a ...interface{}) error {
|
||||||
|
log.Info(format, a...)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
//go:build !openbsd
|
||||||
|
// +build !openbsd
|
||||||
|
|
||||||
|
package home
|
||||||
|
|
||||||
|
func chooseSystem() {}
|
Loading…
Reference in New Issue