2019-06-10 11:33:19 +03:00
|
|
|
package home
|
2019-02-04 13:54:53 +03:00
|
|
|
|
|
|
|
import (
|
2020-02-18 16:01:32 +03:00
|
|
|
"fmt"
|
2021-05-21 14:55:42 +03:00
|
|
|
"io/fs"
|
2019-02-04 13:54:53 +03:00
|
|
|
"os"
|
2019-02-05 14:09:05 +03:00
|
|
|
"runtime"
|
2020-02-18 16:01:32 +03:00
|
|
|
"strconv"
|
2020-02-05 17:38:23 +03:00
|
|
|
"strings"
|
2019-07-02 12:56:23 +03:00
|
|
|
"syscall"
|
2021-08-24 18:00:30 +03:00
|
|
|
"time"
|
2019-02-04 13:54:53 +03:00
|
|
|
|
2022-09-23 13:23:35 +03:00
|
|
|
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
|
2021-03-16 19:42:15 +03:00
|
|
|
"github.com/AdguardTeam/AdGuardHome/internal/aghos"
|
2021-08-24 18:00:30 +03:00
|
|
|
"github.com/AdguardTeam/AdGuardHome/internal/version"
|
2021-05-24 17:28:11 +03:00
|
|
|
"github.com/AdguardTeam/golibs/errors"
|
2019-02-25 16:44:22 +03:00
|
|
|
"github.com/AdguardTeam/golibs/log"
|
2019-02-04 13:54:53 +03:00
|
|
|
"github.com/kardianos/service"
|
|
|
|
)
|
|
|
|
|
2021-09-17 16:24:44 +03:00
|
|
|
// TODO(a.garipov): Consider moving the shell templates into actual files and
|
|
|
|
// using go:embed instead of using large string constants.
|
2021-02-01 14:12:57 +03:00
|
|
|
|
2019-02-05 14:09:05 +03:00
|
|
|
const (
|
2019-02-05 14:21:07 +03:00
|
|
|
launchdStdoutPath = "/var/log/AdGuardHome.stdout.log"
|
|
|
|
launchdStderrPath = "/var/log/AdGuardHome.stderr.log"
|
|
|
|
serviceName = "AdGuardHome"
|
|
|
|
serviceDisplayName = "AdGuard Home service"
|
|
|
|
serviceDescription = "AdGuard Home: Network-level blocker"
|
2019-02-05 14:09:05 +03:00
|
|
|
)
|
|
|
|
|
2021-08-24 18:00:30 +03:00
|
|
|
// program represents the program that will be launched by as a service or a
|
|
|
|
// daemon.
|
2019-02-04 13:54:53 +03:00
|
|
|
type program struct {
|
2021-05-21 14:55:42 +03:00
|
|
|
clientBuildFS fs.FS
|
2023-08-28 16:40:46 +03:00
|
|
|
done chan struct{}
|
2021-05-21 14:55:42 +03:00
|
|
|
opts options
|
2019-02-04 13:54:53 +03:00
|
|
|
}
|
|
|
|
|
2023-07-19 16:57:57 +03:00
|
|
|
// type check
|
|
|
|
var _ service.Interface = (*program)(nil)
|
|
|
|
|
2021-08-24 18:00:30 +03:00
|
|
|
// Start implements service.Interface interface for *program.
|
|
|
|
func (p *program) Start(_ service.Service) (err error) {
|
|
|
|
// Start should not block. Do the actual work async.
|
2020-09-07 12:26:40 +03:00
|
|
|
args := p.opts
|
|
|
|
args.runningAsService = true
|
2021-08-24 18:00:30 +03:00
|
|
|
|
2023-08-28 16:40:46 +03:00
|
|
|
go run(args, p.clientBuildFS, p.done)
|
2021-05-21 14:55:42 +03:00
|
|
|
|
2019-02-04 13:54:53 +03:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-08-24 18:00:30 +03:00
|
|
|
// Stop implements service.Interface interface for *program.
|
2023-07-19 16:57:57 +03:00
|
|
|
func (p *program) Stop(_ service.Service) (err error) {
|
2023-08-28 16:40:46 +03:00
|
|
|
log.Info("service: stopping: waiting for cleanup")
|
|
|
|
|
|
|
|
<-p.done
|
2021-08-24 18:00:30 +03:00
|
|
|
|
2019-02-04 13:54:53 +03:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-08-24 18:00:30 +03:00
|
|
|
// svcStatus returns the service's status.
|
2021-03-12 14:32:08 +03:00
|
|
|
//
|
|
|
|
// On OpenWrt, the service utility may not exist. We use our service script
|
|
|
|
// directly in this case.
|
|
|
|
func svcStatus(s service.Service) (status service.Status, err error) {
|
|
|
|
status, err = s.Status()
|
2020-01-16 12:44:06 +03:00
|
|
|
if err != nil && service.Platform() == "unix-systemv" {
|
2021-03-12 14:32:08 +03:00
|
|
|
var code int
|
|
|
|
code, err = runInitdCommand("status")
|
|
|
|
if err != nil || code != 0 {
|
2020-01-16 12:44:06 +03:00
|
|
|
return service.StatusStopped, nil
|
|
|
|
}
|
2021-03-12 14:32:08 +03:00
|
|
|
|
2020-01-16 12:44:06 +03:00
|
|
|
return service.StatusRunning, nil
|
|
|
|
}
|
2021-03-12 14:32:08 +03:00
|
|
|
|
2020-01-16 12:44:06 +03:00
|
|
|
return status, err
|
|
|
|
}
|
|
|
|
|
2021-03-12 14:32:08 +03:00
|
|
|
// svcAction performs the action on the service.
|
|
|
|
//
|
|
|
|
// On OpenWrt, the service utility may not exist. We use our service script
|
|
|
|
// directly in this case.
|
|
|
|
func svcAction(s service.Service, action string) (err error) {
|
2023-05-10 16:30:03 +03:00
|
|
|
if action == "start" {
|
|
|
|
if err = aghos.PreCheckActionStart(); err != nil {
|
|
|
|
log.Error("starting service: %s", err)
|
2022-02-28 16:26:45 +03:00
|
|
|
}
|
2022-01-11 21:33:14 +03:00
|
|
|
}
|
|
|
|
|
2021-03-12 14:32:08 +03:00
|
|
|
err = service.Control(s, action)
|
2020-01-16 12:44:06 +03:00
|
|
|
if err != nil && service.Platform() == "unix-systemv" &&
|
|
|
|
(action == "start" || action == "stop" || action == "restart") {
|
2021-03-12 14:32:08 +03:00
|
|
|
_, err = runInitdCommand(action)
|
2020-01-16 12:44:06 +03:00
|
|
|
}
|
2021-03-12 14:32:08 +03:00
|
|
|
|
2020-01-16 12:44:06 +03:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2021-08-24 18:00:30 +03:00
|
|
|
// Send SIGHUP to a process with PID taken from our .pid file. If it doesn't
|
|
|
|
// exist, find our PID using 'ps' command.
|
2020-02-18 16:01:32 +03:00
|
|
|
func sendSigReload() {
|
|
|
|
if runtime.GOOS == "windows" {
|
2021-11-23 20:45:14 +03:00
|
|
|
log.Error("service: not implemented on windows")
|
2021-05-14 19:41:45 +03:00
|
|
|
|
2020-02-18 16:01:32 +03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-12-27 19:12:40 +03:00
|
|
|
pidFile := fmt.Sprintf("/var/run/%s.pid", serviceName)
|
2021-08-18 13:20:56 +03:00
|
|
|
var pid int
|
2021-12-27 19:12:40 +03:00
|
|
|
data, err := os.ReadFile(pidFile)
|
2021-05-14 19:41:45 +03:00
|
|
|
if errors.Is(err, os.ErrNotExist) {
|
2021-08-18 13:20:56 +03:00
|
|
|
if pid, err = aghos.PIDByCommand(serviceName, os.Getpid()); err != nil {
|
2021-11-23 20:45:14 +03:00
|
|
|
log.Error("service: finding AdGuardHome process: %s", err)
|
2021-05-14 19:41:45 +03:00
|
|
|
|
2020-02-18 16:01:32 +03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
} else if err != nil {
|
2021-12-27 19:12:40 +03:00
|
|
|
log.Error("service: reading pid file %s: %s", pidFile, err)
|
2021-05-14 19:41:45 +03:00
|
|
|
|
2020-02-18 16:01:32 +03:00
|
|
|
return
|
2021-08-18 13:20:56 +03:00
|
|
|
} else {
|
|
|
|
parts := strings.SplitN(string(data), "\n", 2)
|
|
|
|
if len(parts) == 0 {
|
2021-12-27 19:12:40 +03:00
|
|
|
log.Error("service: parsing pid file %s: bad value", pidFile)
|
2021-08-18 13:20:56 +03:00
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if pid, err = strconv.Atoi(strings.TrimSpace(parts[0])); err != nil {
|
2021-12-27 19:12:40 +03:00
|
|
|
log.Error("service: parsing pid from file %s: %s", pidFile, err)
|
2021-08-18 13:20:56 +03:00
|
|
|
|
|
|
|
return
|
|
|
|
}
|
2020-02-18 16:01:32 +03:00
|
|
|
}
|
|
|
|
|
2021-08-18 13:20:56 +03:00
|
|
|
var proc *os.Process
|
|
|
|
if proc, err = os.FindProcess(pid); err != nil {
|
2021-11-23 20:45:14 +03:00
|
|
|
log.Error("service: finding process for pid %d: %s", pid, err)
|
2021-08-18 13:20:56 +03:00
|
|
|
|
2020-02-18 16:01:32 +03:00
|
|
|
return
|
|
|
|
}
|
2021-08-18 13:20:56 +03:00
|
|
|
|
|
|
|
if err = proc.Signal(syscall.SIGHUP); err != nil {
|
2021-11-23 20:45:14 +03:00
|
|
|
log.Error("service: sending signal HUP to pid %d: %s", pid, err)
|
2021-08-18 13:20:56 +03:00
|
|
|
|
2020-02-18 16:01:32 +03:00
|
|
|
return
|
|
|
|
}
|
2021-08-18 13:20:56 +03:00
|
|
|
|
2021-11-23 20:45:14 +03:00
|
|
|
log.Debug("service: sent signal to pid %d", pid)
|
2020-02-18 16:01:32 +03:00
|
|
|
}
|
|
|
|
|
2023-01-09 13:38:31 +03:00
|
|
|
// restartService restarts the service. It returns error if the service is not
|
|
|
|
// running.
|
|
|
|
func restartService() (err error) {
|
|
|
|
// Call chooseSystem explicitly to introduce OpenBSD support for service
|
|
|
|
// package. It's a noop for other GOOS values.
|
|
|
|
chooseSystem()
|
|
|
|
|
|
|
|
pwd, err := os.Getwd()
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("getting current directory: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
svcConfig := &service.Config{
|
|
|
|
Name: serviceName,
|
|
|
|
DisplayName: serviceDisplayName,
|
|
|
|
Description: serviceDescription,
|
|
|
|
WorkingDirectory: pwd,
|
|
|
|
}
|
|
|
|
configureService(svcConfig)
|
|
|
|
|
|
|
|
var s service.Service
|
|
|
|
if s, err = service.New(&program{}, svcConfig); err != nil {
|
|
|
|
return fmt.Errorf("initializing service: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err = svcAction(s, "restart"); err != nil {
|
|
|
|
return fmt.Errorf("restarting service: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-02-04 13:54:53 +03:00
|
|
|
// handleServiceControlAction one of the possible control actions:
|
2022-08-31 18:57:02 +03:00
|
|
|
//
|
|
|
|
// - install: Installs a service/daemon.
|
|
|
|
// - uninstall: Uninstalls it.
|
|
|
|
// - status: Prints the service status.
|
|
|
|
// - start: Starts the previously installed service.
|
|
|
|
// - stop: Stops the previously installed service.
|
|
|
|
// - restart: Restarts the previously installed service.
|
|
|
|
// - run: This is a special command that is not supposed to be used directly
|
|
|
|
// it is specified when we register a service, and it indicates to the app
|
|
|
|
// that it is being run as a service/daemon.
|
2023-08-28 16:40:46 +03:00
|
|
|
func handleServiceControlAction(opts options, clientBuildFS fs.FS, done chan struct{}) {
|
2021-12-28 17:00:52 +03:00
|
|
|
// Call chooseSystem explicitly to introduce OpenBSD support for service
|
2021-08-24 18:00:30 +03:00
|
|
|
// package. It's a noop for other GOOS values.
|
|
|
|
chooseSystem()
|
|
|
|
|
2020-09-07 12:26:40 +03:00
|
|
|
action := opts.serviceControlAction
|
2022-09-29 19:04:26 +03:00
|
|
|
log.Info(version.Full())
|
|
|
|
log.Info("service: control action: %s", action)
|
2019-02-04 13:54:53 +03:00
|
|
|
|
2020-02-18 16:01:32 +03:00
|
|
|
if action == "reload" {
|
|
|
|
sendSigReload()
|
2021-08-24 18:00:30 +03:00
|
|
|
|
2020-02-18 16:01:32 +03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-02-04 13:54:53 +03:00
|
|
|
pwd, err := os.Getwd()
|
|
|
|
if err != nil {
|
2021-11-23 20:45:14 +03:00
|
|
|
log.Fatalf("service: getting current directory: %s", err)
|
2019-02-04 13:54:53 +03:00
|
|
|
}
|
2021-08-24 18:00:30 +03:00
|
|
|
|
2020-09-07 12:26:40 +03:00
|
|
|
runOpts := opts
|
|
|
|
runOpts.serviceControlAction = "run"
|
2023-05-10 16:30:03 +03:00
|
|
|
|
2019-02-04 13:54:53 +03:00
|
|
|
svcConfig := &service.Config{
|
2019-02-05 14:21:07 +03:00
|
|
|
Name: serviceName,
|
|
|
|
DisplayName: serviceDisplayName,
|
|
|
|
Description: serviceDescription,
|
2019-02-04 13:54:53 +03:00
|
|
|
WorkingDirectory: pwd,
|
2022-10-05 17:07:08 +03:00
|
|
|
Arguments: optsToArgs(runOpts),
|
2019-02-04 13:54:53 +03:00
|
|
|
}
|
2019-02-05 14:09:05 +03:00
|
|
|
configureService(svcConfig)
|
2021-08-24 18:00:30 +03:00
|
|
|
|
2023-07-19 16:57:57 +03:00
|
|
|
s, err := service.New(&program{
|
|
|
|
clientBuildFS: clientBuildFS,
|
2023-08-28 16:40:46 +03:00
|
|
|
done: done,
|
2023-07-19 16:57:57 +03:00
|
|
|
opts: runOpts,
|
|
|
|
}, svcConfig)
|
2023-05-10 16:30:03 +03:00
|
|
|
if err != nil {
|
2021-11-23 20:45:14 +03:00
|
|
|
log.Fatalf("service: initializing service: %s", err)
|
2019-02-04 13:54:53 +03:00
|
|
|
}
|
|
|
|
|
2023-05-10 16:30:03 +03:00
|
|
|
err = handleServiceCommand(s, action, opts)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("service: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
log.Printf(
|
|
|
|
"service: action %s has been done successfully on %s",
|
|
|
|
action,
|
|
|
|
service.ChosenSystem(),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
// handleServiceCommand handles service command.
|
|
|
|
func handleServiceCommand(s service.Service, action string, opts options) (err error) {
|
2021-08-24 18:00:30 +03:00
|
|
|
switch action {
|
|
|
|
case "status":
|
2020-02-05 17:38:23 +03:00
|
|
|
handleServiceStatusCommand(s)
|
2021-08-24 18:00:30 +03:00
|
|
|
case "run":
|
|
|
|
if err = s.Run(); err != nil {
|
2023-05-10 16:30:03 +03:00
|
|
|
return fmt.Errorf("failed to run service: %w", err)
|
2019-02-05 14:09:05 +03:00
|
|
|
}
|
2021-08-24 18:00:30 +03:00
|
|
|
case "install":
|
2020-09-28 10:57:56 +03:00
|
|
|
initConfigFilename(opts)
|
2023-05-10 16:30:03 +03:00
|
|
|
if err = initWorkingDir(opts); err != nil {
|
|
|
|
return fmt.Errorf("failed to init working dir: %w", err)
|
|
|
|
}
|
|
|
|
|
2020-02-05 17:38:23 +03:00
|
|
|
handleServiceInstallCommand(s)
|
2021-08-24 18:00:30 +03:00
|
|
|
case "uninstall":
|
2020-02-05 17:38:23 +03:00
|
|
|
handleServiceUninstallCommand(s)
|
2021-08-24 18:00:30 +03:00
|
|
|
default:
|
|
|
|
if err = svcAction(s, action); err != nil {
|
2023-05-10 16:30:03 +03:00
|
|
|
return fmt.Errorf("executing action %q: %w", action, err)
|
2019-02-05 14:09:05 +03:00
|
|
|
}
|
2020-02-05 17:38:23 +03:00
|
|
|
}
|
2019-02-05 14:09:05 +03:00
|
|
|
|
2023-05-10 16:30:03 +03:00
|
|
|
return nil
|
2020-02-05 17:38:23 +03:00
|
|
|
}
|
|
|
|
|
2021-08-24 18:00:30 +03:00
|
|
|
// handleServiceStatusCommand handles service "status" command.
|
2020-02-05 17:38:23 +03:00
|
|
|
func handleServiceStatusCommand(s service.Service) {
|
|
|
|
status, errSt := svcStatus(s)
|
|
|
|
if errSt != nil {
|
2021-11-23 20:45:14 +03:00
|
|
|
log.Fatalf("service: failed to get service status: %s", errSt)
|
2020-02-05 17:38:23 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
switch status {
|
|
|
|
case service.StatusUnknown:
|
2021-11-23 20:45:14 +03:00
|
|
|
log.Printf("service: status is unknown")
|
2020-02-05 17:38:23 +03:00
|
|
|
case service.StatusStopped:
|
2021-11-23 20:45:14 +03:00
|
|
|
log.Printf("service: stopped")
|
2020-02-05 17:38:23 +03:00
|
|
|
case service.StatusRunning:
|
2021-11-23 20:45:14 +03:00
|
|
|
log.Printf("service: running")
|
2020-02-05 17:38:23 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// handleServiceStatusCommand handles service "install" command
|
|
|
|
func handleServiceInstallCommand(s service.Service) {
|
|
|
|
err := svcAction(s, "install")
|
|
|
|
if err != nil {
|
2021-11-23 20:45:14 +03:00
|
|
|
log.Fatalf("service: executing action %q: %s", "install", err)
|
2020-02-05 17:38:23 +03:00
|
|
|
}
|
|
|
|
|
2021-04-08 20:42:04 +03:00
|
|
|
if aghos.IsOpenWrt() {
|
2021-08-24 18:00:30 +03:00
|
|
|
// On OpenWrt it is important to run enable after the service
|
2021-12-27 19:12:40 +03:00
|
|
|
// installation. Otherwise, the service won't start on the system
|
2021-08-24 18:00:30 +03:00
|
|
|
// startup.
|
2021-03-12 14:32:08 +03:00
|
|
|
_, err = runInitdCommand("enable")
|
2019-02-04 13:54:53 +03:00
|
|
|
if err != nil {
|
2021-11-23 20:45:14 +03:00
|
|
|
log.Fatalf("service: running init enable: %s", err)
|
2019-02-04 13:54:53 +03:00
|
|
|
}
|
2020-02-05 17:38:23 +03:00
|
|
|
}
|
|
|
|
|
2021-08-24 18:00:30 +03:00
|
|
|
// Start automatically after install.
|
2020-02-05 17:38:23 +03:00
|
|
|
err = svcAction(s, "start")
|
|
|
|
if err != nil {
|
2021-11-23 20:45:14 +03:00
|
|
|
log.Fatalf("service: starting: %s", err)
|
2020-02-05 17:38:23 +03:00
|
|
|
}
|
2021-11-23 20:45:14 +03:00
|
|
|
log.Printf("service: started")
|
2020-02-05 17:38:23 +03:00
|
|
|
|
|
|
|
if detectFirstRun() {
|
|
|
|
log.Printf(`Almost ready!
|
2019-04-17 19:57:42 +03:00
|
|
|
AdGuard Home is successfully installed and will automatically start on boot.
|
|
|
|
There are a few more things that must be configured before you can use it.
|
2021-06-01 21:06:55 +03:00
|
|
|
Click on the link below and follow the Installation Wizard steps to finish setup.
|
|
|
|
AdGuard Home is now available at the following addresses:`)
|
2022-09-23 13:23:35 +03:00
|
|
|
printHTTPAddresses(aghhttp.SchemeHTTP)
|
2020-02-05 17:38:23 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// handleServiceStatusCommand handles service "uninstall" command
|
|
|
|
func handleServiceUninstallCommand(s service.Service) {
|
2021-04-08 20:42:04 +03:00
|
|
|
if aghos.IsOpenWrt() {
|
2020-02-05 17:38:23 +03:00
|
|
|
// On OpenWrt it is important to run disable command first
|
|
|
|
// as it will remove the symlink
|
|
|
|
_, err := runInitdCommand("disable")
|
|
|
|
if err != nil {
|
2021-11-23 20:45:14 +03:00
|
|
|
log.Fatalf("service: running init disable: %s", err)
|
2020-02-05 17:38:23 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-27 19:12:40 +03:00
|
|
|
if err := svcAction(s, "stop"); err != nil {
|
|
|
|
log.Debug("service: executing action %q: %s", "stop", err)
|
|
|
|
}
|
|
|
|
|
2021-08-24 18:00:30 +03:00
|
|
|
if err := svcAction(s, "uninstall"); err != nil {
|
2021-11-23 20:45:14 +03:00
|
|
|
log.Fatalf("service: executing action %q: %s", "uninstall", err)
|
2020-02-05 17:38:23 +03:00
|
|
|
}
|
2019-04-17 19:57:42 +03:00
|
|
|
|
2020-02-05 17:38:23 +03:00
|
|
|
if runtime.GOOS == "darwin" {
|
2021-03-12 14:32:08 +03:00
|
|
|
// Remove log files on cleanup and log errors.
|
2021-08-24 18:00:30 +03:00
|
|
|
err := os.Remove(launchdStdoutPath)
|
2021-05-14 19:41:45 +03:00
|
|
|
if err != nil && !errors.Is(err, os.ErrNotExist) {
|
2021-11-23 20:45:14 +03:00
|
|
|
log.Info("service: warning: removing stdout file: %s", err)
|
2020-02-05 17:38:23 +03:00
|
|
|
}
|
2021-03-12 14:32:08 +03:00
|
|
|
|
2020-02-05 17:38:23 +03:00
|
|
|
err = os.Remove(launchdStderrPath)
|
2021-05-14 19:41:45 +03:00
|
|
|
if err != nil && !errors.Is(err, os.ErrNotExist) {
|
2021-11-23 20:45:14 +03:00
|
|
|
log.Info("service: warning: removing stderr file: %s", err)
|
2019-02-05 14:09:05 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// configureService defines additional settings of the service
|
|
|
|
func configureService(c *service.Config) {
|
|
|
|
c.Option = service.KeyValue{}
|
|
|
|
|
2021-06-01 16:04:22 +03:00
|
|
|
// macOS
|
|
|
|
|
2019-02-05 14:09:05 +03:00
|
|
|
// Redefines the launchd config file template
|
|
|
|
// The purpose is to enable stdout/stderr redirect by default
|
|
|
|
c.Option["LaunchdConfig"] = launchdConfig
|
|
|
|
// This key is used to start the job as soon as it has been loaded. For daemons this means execution at boot time, for agents execution at login.
|
|
|
|
c.Option["RunAtLoad"] = true
|
|
|
|
|
2021-09-13 17:06:16 +03:00
|
|
|
// POSIX / systemd
|
2021-06-01 16:04:22 +03:00
|
|
|
|
2021-09-13 17:06:16 +03:00
|
|
|
// Redirect stderr and stdout to files. Make sure we always restart.
|
2019-02-05 14:09:05 +03:00
|
|
|
c.Option["LogOutput"] = true
|
2021-09-13 17:06:16 +03:00
|
|
|
c.Option["Restart"] = "always"
|
2021-09-17 16:24:44 +03:00
|
|
|
|
|
|
|
// Start only once network is up on Linux/systemd.
|
|
|
|
if runtime.GOOS == "linux" {
|
|
|
|
c.Dependencies = []string{
|
|
|
|
"After=syslog.target network-online.target",
|
|
|
|
}
|
2021-09-13 17:06:16 +03:00
|
|
|
}
|
2019-10-08 12:29:11 +03:00
|
|
|
|
2021-09-17 16:24:44 +03:00
|
|
|
// Use the modified service file templates.
|
2019-10-08 12:29:11 +03:00
|
|
|
c.Option["SystemdScript"] = systemdScript
|
2020-01-16 12:44:06 +03:00
|
|
|
c.Option["SysvScript"] = sysvScript
|
|
|
|
|
2021-09-17 16:24:44 +03:00
|
|
|
// Use different scripts on OpenWrt and FreeBSD.
|
2021-04-08 20:42:04 +03:00
|
|
|
if aghos.IsOpenWrt() {
|
2020-02-05 17:38:23 +03:00
|
|
|
c.Option["SysvScript"] = openWrtScript
|
2020-11-06 12:15:08 +03:00
|
|
|
} else if runtime.GOOS == "freebsd" {
|
2020-07-02 11:41:36 +03:00
|
|
|
c.Option["SysvScript"] = freeBSDScript
|
2020-01-16 12:44:06 +03:00
|
|
|
}
|
2021-08-24 18:00:30 +03:00
|
|
|
|
|
|
|
c.Option["RunComScript"] = openBSDScript
|
|
|
|
c.Option["SvcInfo"] = fmt.Sprintf("%s %s", version.Full(), time.Now())
|
2019-02-05 14:09:05 +03:00
|
|
|
}
|
|
|
|
|
2020-02-05 17:38:23 +03:00
|
|
|
// runInitdCommand runs init.d service command
|
|
|
|
// returns command code or error if any
|
|
|
|
func runInitdCommand(action string) (int, error) {
|
|
|
|
confPath := "/etc/init.d/" + serviceName
|
2021-12-27 19:12:40 +03:00
|
|
|
// Pass the script and action as a single string argument.
|
2021-03-22 16:46:36 +03:00
|
|
|
code, _, err := aghos.RunCommand("sh", "-c", confPath+" "+action)
|
2021-12-27 19:12:40 +03:00
|
|
|
|
2020-02-05 17:38:23 +03:00
|
|
|
return code, err
|
|
|
|
}
|
|
|
|
|
2019-02-05 14:09:05 +03:00
|
|
|
// Basically the same template as the one defined in github.com/kardianos/service
|
|
|
|
// but with two additional keys - StandardOutPath and StandardErrorPath
|
|
|
|
var launchdConfig = `<?xml version='1.0' encoding='UTF-8'?>
|
|
|
|
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN"
|
|
|
|
"http://www.apple.com/DTDs/PropertyList-1.0.dtd" >
|
|
|
|
<plist version='1.0'>
|
|
|
|
<dict>
|
|
|
|
<key>Label</key><string>{{html .Name}}</string>
|
|
|
|
<key>ProgramArguments</key>
|
|
|
|
<array>
|
|
|
|
<string>{{html .Path}}</string>
|
|
|
|
{{range .Config.Arguments}}
|
|
|
|
<string>{{html .}}</string>
|
|
|
|
{{end}}
|
|
|
|
</array>
|
|
|
|
{{if .UserName}}<key>UserName</key><string>{{html .UserName}}</string>{{end}}
|
|
|
|
{{if .ChRoot}}<key>RootDirectory</key><string>{{html .ChRoot}}</string>{{end}}
|
|
|
|
{{if .WorkingDirectory}}<key>WorkingDirectory</key><string>{{html .WorkingDirectory}}</string>{{end}}
|
|
|
|
<key>SessionCreate</key><{{bool .SessionCreate}}/>
|
|
|
|
<key>KeepAlive</key><{{bool .KeepAlive}}/>
|
|
|
|
<key>RunAtLoad</key><{{bool .RunAtLoad}}/>
|
|
|
|
<key>Disabled</key><false/>
|
|
|
|
<key>StandardOutPath</key>
|
|
|
|
<string>` + launchdStdoutPath + `</string>
|
|
|
|
<key>StandardErrorPath</key>
|
|
|
|
<string>` + launchdStderrPath + `</string>
|
|
|
|
</dict>
|
|
|
|
</plist>
|
|
|
|
`
|
2019-10-08 12:29:11 +03:00
|
|
|
|
2021-09-13 17:06:16 +03:00
|
|
|
// systemdScript is an improved version of the systemd script originally from
|
|
|
|
// the systemdScript constant in file service_systemd_linux.go in module
|
|
|
|
// github.com/kardianos/service. The following changes have been made:
|
|
|
|
//
|
2022-08-31 18:57:02 +03:00
|
|
|
// 1. The RestartSec setting is set to a lower value of 10 to make sure we
|
2021-09-13 17:06:16 +03:00
|
|
|
// always restart quickly.
|
|
|
|
//
|
2022-08-31 18:57:02 +03:00
|
|
|
// 2. The ExecStartPre setting is added to make sure that the log directory is
|
2021-09-13 17:06:16 +03:00
|
|
|
// always created to prevent the 209/STDOUT errors.
|
2019-10-08 12:29:11 +03:00
|
|
|
const systemdScript = `[Unit]
|
|
|
|
Description={{.Description}}
|
|
|
|
ConditionFileIsExecutable={{.Path|cmdEscape}}
|
2021-09-13 17:06:16 +03:00
|
|
|
{{range $i, $dep := .Dependencies}}
|
|
|
|
{{$dep}} {{end}}
|
2019-10-08 12:29:11 +03:00
|
|
|
|
|
|
|
[Service]
|
|
|
|
StartLimitInterval=5
|
|
|
|
StartLimitBurst=10
|
2021-09-22 18:18:23 +03:00
|
|
|
ExecStartPre=/bin/mkdir -p /var/log/
|
2019-10-08 12:29:11 +03:00
|
|
|
ExecStart={{.Path|cmdEscape}}{{range .Arguments}} {{.|cmd}}{{end}}
|
|
|
|
{{if .ChRoot}}RootDirectory={{.ChRoot|cmd}}{{end}}
|
|
|
|
{{if .WorkingDirectory}}WorkingDirectory={{.WorkingDirectory|cmdEscape}}{{end}}
|
|
|
|
{{if .UserName}}User={{.UserName}}{{end}}
|
|
|
|
{{if .ReloadSignal}}ExecReload=/bin/kill -{{.ReloadSignal}} "$MAINPID"{{end}}
|
|
|
|
{{if .PIDFile}}PIDFile={{.PIDFile|cmd}}{{end}}
|
|
|
|
{{if and .LogOutput .HasOutputFileSupport -}}
|
|
|
|
StandardOutput=file:/var/log/{{.Name}}.out
|
|
|
|
StandardError=file:/var/log/{{.Name}}.err
|
|
|
|
{{- end}}
|
2021-09-13 17:06:16 +03:00
|
|
|
{{if gt .LimitNOFILE -1 }}LimitNOFILE={{.LimitNOFILE}}{{end}}
|
|
|
|
{{if .Restart}}Restart={{.Restart}}{{end}}
|
|
|
|
{{if .SuccessExitStatus}}SuccessExitStatus={{.SuccessExitStatus}}{{end}}
|
2019-10-08 12:29:11 +03:00
|
|
|
RestartSec=10
|
|
|
|
EnvironmentFile=-/etc/sysconfig/{{.Name}}
|
|
|
|
|
|
|
|
[Install]
|
|
|
|
WantedBy=multi-user.target
|
|
|
|
`
|
2020-01-16 12:44:06 +03:00
|
|
|
|
2022-05-24 14:43:54 +03:00
|
|
|
// sysvScript is the source of the daemon script for SysV-based Linux systems.
|
|
|
|
// Keep as close as possible to the https://github.com/kardianos/service/blob/29f8c79c511bc18422bb99992779f96e6bc33921/service_sysv_linux.go#L187.
|
|
|
|
//
|
|
|
|
// Use ps command instead of reading the procfs since it's a more
|
|
|
|
// implementation-independent approach.
|
2020-01-16 12:44:06 +03:00
|
|
|
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 $(readlink -f $0))
|
|
|
|
pid_file="/var/run/$name.pid"
|
|
|
|
stdout_log="/var/log/$name.log"
|
|
|
|
stderr_log="/var/log/$name.err"
|
|
|
|
|
|
|
|
[ -e /etc/sysconfig/$name ] && . /etc/sysconfig/$name
|
|
|
|
|
|
|
|
get_pid() {
|
|
|
|
cat "$pid_file"
|
|
|
|
}
|
|
|
|
|
|
|
|
is_running() {
|
2022-05-24 14:43:54 +03:00
|
|
|
[ -f "$pid_file" ] && ps -p "$(get_pid)" > /dev/null 2>&1
|
2020-01-16 12:44:06 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
case "$1" in
|
|
|
|
start)
|
|
|
|
if is_running; then
|
|
|
|
echo "Already started"
|
|
|
|
else
|
|
|
|
echo "Starting $name"
|
|
|
|
{{if .WorkingDirectory}}cd '{{.WorkingDirectory}}'{{end}}
|
|
|
|
$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 $(seq 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
|
|
|
|
`
|
2020-02-05 17:38:23 +03:00
|
|
|
|
|
|
|
// OpenWrt procd init script
|
2020-10-30 13:32:02 +03:00
|
|
|
// https://github.com/AdguardTeam/AdGuardHome/internal/issues/1386
|
2020-02-05 17:38:23 +03:00
|
|
|
const openWrtScript = `#!/bin/sh /etc/rc.common
|
|
|
|
|
|
|
|
USE_PROCD=1
|
|
|
|
|
|
|
|
START=95
|
|
|
|
STOP=01
|
|
|
|
|
|
|
|
cmd="{{.Path}}{{range .Arguments}} {{.|cmd}}{{end}}"
|
|
|
|
name="{{.Name}}"
|
|
|
|
pid_file="/var/run/${name}.pid"
|
|
|
|
|
|
|
|
start_service() {
|
|
|
|
echo "Starting ${name}"
|
|
|
|
|
|
|
|
procd_open_instance
|
|
|
|
procd_set_param command ${cmd}
|
|
|
|
procd_set_param respawn # respawn automatically if something died
|
|
|
|
procd_set_param stdout 1 # forward stdout of the command to logd
|
|
|
|
procd_set_param stderr 1 # same for stderr
|
|
|
|
procd_set_param pidfile ${pid_file} # write a pid file on instance start and remove it on stop
|
|
|
|
|
|
|
|
procd_close_instance
|
|
|
|
echo "${name} has been started"
|
|
|
|
}
|
|
|
|
|
|
|
|
stop_service() {
|
|
|
|
echo "Stopping ${name}"
|
|
|
|
}
|
|
|
|
|
|
|
|
EXTRA_COMMANDS="status"
|
|
|
|
EXTRA_HELP=" status Print the service status"
|
|
|
|
|
|
|
|
get_pid() {
|
|
|
|
cat "${pid_file}"
|
|
|
|
}
|
|
|
|
|
|
|
|
is_running() {
|
|
|
|
[ -f "${pid_file}" ] && ps | grep -v grep | grep $(get_pid) >/dev/null 2>&1
|
|
|
|
}
|
|
|
|
|
|
|
|
status() {
|
|
|
|
if is_running; then
|
|
|
|
echo "Running"
|
|
|
|
else
|
|
|
|
echo "Stopped"
|
|
|
|
exit 1
|
|
|
|
fi
|
|
|
|
}
|
|
|
|
`
|
2020-11-06 12:15:08 +03:00
|
|
|
|
2022-02-28 16:26:45 +03:00
|
|
|
// freeBSDScript is the source of the daemon script for FreeBSD. Keep as close
|
|
|
|
// as possible to the https://github.com/kardianos/service/blob/18c957a3dc1120a2efe77beb401d476bade9e577/service_freebsd.go#L204.
|
|
|
|
//
|
2021-02-01 14:12:57 +03:00
|
|
|
// TODO(a.garipov): Don't use .WorkingDirectory here. There are currently no
|
|
|
|
// guarantees that it will actually be the required directory.
|
|
|
|
//
|
|
|
|
// See https://github.com/AdguardTeam/AdGuardHome/issues/2614.
|
2020-07-02 11:41:36 +03:00
|
|
|
const freeBSDScript = `#!/bin/sh
|
|
|
|
# PROVIDE: {{.Name}}
|
|
|
|
# REQUIRE: networking
|
|
|
|
# KEYWORD: shutdown
|
2022-02-28 16:26:45 +03:00
|
|
|
|
2020-07-02 11:41:36 +03:00
|
|
|
. /etc/rc.subr
|
2022-02-28 16:26:45 +03:00
|
|
|
|
2020-07-02 11:41:36 +03:00
|
|
|
name="{{.Name}}"
|
|
|
|
{{.Name}}_env="IS_DAEMON=1"
|
|
|
|
{{.Name}}_user="root"
|
2021-12-27 19:12:40 +03:00
|
|
|
pidfile_child="/var/run/${name}.pid"
|
|
|
|
pidfile="/var/run/${name}_daemon.pid"
|
2020-07-02 11:41:36 +03:00
|
|
|
command="/usr/sbin/daemon"
|
2022-02-28 16:26:45 +03:00
|
|
|
command_args="-P ${pidfile} -p ${pidfile_child} -T ${name} -r {{.WorkingDirectory}}/{{.Name}}"
|
2020-07-02 11:41:36 +03:00
|
|
|
run_rc_command "$1"
|
|
|
|
`
|
2021-08-24 18:00:30 +03:00
|
|
|
|
2022-04-29 14:39:02 +03:00
|
|
|
const openBSDScript = `#!/bin/ksh
|
2021-08-24 18:00:30 +03:00
|
|
|
#
|
|
|
|
# $OpenBSD: {{ .SvcInfo }}
|
|
|
|
|
|
|
|
daemon="{{.Path}}"
|
|
|
|
daemon_flags={{ .Arguments | args }}
|
2022-02-28 16:26:45 +03:00
|
|
|
daemon_logger="daemon.info"
|
2021-08-24 18:00:30 +03:00
|
|
|
|
|
|
|
. /etc/rc.d/rc.subr
|
|
|
|
|
|
|
|
rc_bg=YES
|
|
|
|
|
|
|
|
rc_cmd $1
|
|
|
|
`
|