*(global): fixed service implementation for OpenWrt
We now use a procd init script for OpenWrt just like it's recommended in the documentation. The service is automatically enabled on the install command. ✅ Closes: https://github.com/AdguardTeam/AdGuardHome/issues/1386
This commit is contained in:
parent
54c285001d
commit
fc88f59f61
@ -152,15 +152,6 @@ type updateInfo struct {
|
|||||||
newBinName string // Full path to the new executable file
|
newBinName string // Full path to the new executable file
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return TRUE if file exists
|
|
||||||
func fileExists(fn string) bool {
|
|
||||||
_, err := os.Stat(fn)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fill in updateInfo object
|
// Fill in updateInfo object
|
||||||
func getUpdateInfo(jsonData []byte) (*updateInfo, error) {
|
func getUpdateInfo(jsonData []byte) (*updateInfo, error) {
|
||||||
var u updateInfo
|
var u updateInfo
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
@ -242,7 +243,7 @@ func checkPortAvailable(host string, port int) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
ln.Close()
|
_ = ln.Close()
|
||||||
|
|
||||||
// It seems that net.Listener.Close() doesn't close file descriptors right away.
|
// It seems that net.Listener.Close() doesn't close file descriptors right away.
|
||||||
// We wait for some time and hope that this fd will be closed.
|
// We wait for some time and hope that this fd will be closed.
|
||||||
@ -255,7 +256,7 @@ func checkPacketPortAvailable(host string, port int) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
ln.Close()
|
_ = ln.Close()
|
||||||
|
|
||||||
// It seems that net.Listener.Close() doesn't close file descriptors right away.
|
// It seems that net.Listener.Close() doesn't close file descriptors right away.
|
||||||
// We wait for some time and hope that this fd will be closed.
|
// We wait for some time and hope that this fd will be closed.
|
||||||
@ -329,6 +330,30 @@ func errorIsAddrInUse(err error) bool {
|
|||||||
return errErrno == syscall.EADDRINUSE
|
return errErrno == syscall.EADDRINUSE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------
|
||||||
|
// general helpers
|
||||||
|
// ---------------------
|
||||||
|
|
||||||
|
// fileExists returns TRUE if file exists
|
||||||
|
func fileExists(fn string) bool {
|
||||||
|
_, err := os.Stat(fn)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// runCommand runs shell command
|
||||||
|
func runCommand(command string, arguments ...string) (int, string, error) {
|
||||||
|
cmd := exec.Command(command, arguments...)
|
||||||
|
out, err := cmd.Output()
|
||||||
|
if err != nil {
|
||||||
|
return 1, "", fmt.Errorf("exec.Command(%s) failed: %s", command, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return cmd.ProcessState.ExitCode(), string(out), nil
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------
|
// ---------------------
|
||||||
// debug logging helpers
|
// debug logging helpers
|
||||||
// ---------------------
|
// ---------------------
|
||||||
|
201
home/service.go
201
home/service.go
@ -1,10 +1,10 @@
|
|||||||
package home
|
package home
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/AdguardTeam/golibs/log"
|
"github.com/AdguardTeam/golibs/log"
|
||||||
@ -41,23 +41,12 @@ func (p *program) Stop(s service.Service) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func runCommand(command string, arguments ...string) (int, string, error) {
|
|
||||||
cmd := exec.Command(command, arguments...)
|
|
||||||
out, err := cmd.Output()
|
|
||||||
if err != nil {
|
|
||||||
return 1, "", fmt.Errorf("exec.Command(%s) failed: %s", command, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return cmd.ProcessState.ExitCode(), string(out), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check the service's status
|
// Check the service's status
|
||||||
// Note: on OpenWrt 'service' utility may not exist - we use our service script directly in this case.
|
// Note: on OpenWrt 'service' utility may not exist - we use our service script directly in this case.
|
||||||
func svcStatus(s service.Service) (service.Status, error) {
|
func svcStatus(s service.Service) (service.Status, error) {
|
||||||
status, err := s.Status()
|
status, err := s.Status()
|
||||||
if err != nil && service.Platform() == "unix-systemv" {
|
if err != nil && service.Platform() == "unix-systemv" {
|
||||||
confPath := "/etc/init.d/" + serviceName
|
code, err := runInitdCommand("status")
|
||||||
code, _, err := runCommand("sh", "-c", confPath+" status")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return service.StatusStopped, nil
|
return service.StatusStopped, nil
|
||||||
}
|
}
|
||||||
@ -75,8 +64,7 @@ func svcAction(s service.Service, action string) error {
|
|||||||
err := service.Control(s, action)
|
err := service.Control(s, action)
|
||||||
if err != nil && service.Platform() == "unix-systemv" &&
|
if err != nil && service.Platform() == "unix-systemv" &&
|
||||||
(action == "start" || action == "stop" || action == "restart") {
|
(action == "start" || action == "stop" || action == "restart") {
|
||||||
confPath := "/etc/init.d/" + serviceName
|
_, err := runInitdCommand(action)
|
||||||
_, _, err := runCommand("sh", "-c", confPath+" "+action)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
@ -114,6 +102,28 @@ func handleServiceControlAction(action string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if action == "status" {
|
if action == "status" {
|
||||||
|
handleServiceStatusCommand(s)
|
||||||
|
} else if action == "run" {
|
||||||
|
err = s.Run()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to run service: %s", err)
|
||||||
|
}
|
||||||
|
} else if action == "install" {
|
||||||
|
handleServiceInstallCommand(s)
|
||||||
|
} else if action == "uninstall" {
|
||||||
|
handleServiceUninstallCommand(s)
|
||||||
|
} else {
|
||||||
|
err = svcAction(s, action)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Action %s has been done successfully on %s", action, service.ChosenSystem().String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleServiceStatusCommand handles service "status" command
|
||||||
|
func handleServiceStatusCommand(s service.Service) {
|
||||||
status, errSt := svcStatus(s)
|
status, errSt := svcStatus(s)
|
||||||
if errSt != nil {
|
if errSt != nil {
|
||||||
log.Fatalf("failed to get service status: %s", errSt)
|
log.Fatalf("failed to get service status: %s", errSt)
|
||||||
@ -127,30 +137,23 @@ func handleServiceControlAction(action string) {
|
|||||||
case service.StatusRunning:
|
case service.StatusRunning:
|
||||||
log.Printf("Service is running")
|
log.Printf("Service is running")
|
||||||
}
|
}
|
||||||
} else if action == "run" {
|
}
|
||||||
err = s.Run()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Failed to run service: %s", err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if action == "uninstall" {
|
|
||||||
// In case of Windows and Linux when a running service is being uninstalled,
|
|
||||||
// it is just marked for deletion but not stopped
|
|
||||||
// So we explicitly stop it here
|
|
||||||
_ = svcAction(s, "stop")
|
|
||||||
}
|
|
||||||
|
|
||||||
err = svcAction(s, action)
|
// handleServiceStatusCommand handles service "install" command
|
||||||
|
func handleServiceInstallCommand(s service.Service) {
|
||||||
|
err := svcAction(s, "install")
|
||||||
if 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())
|
|
||||||
|
|
||||||
if action == "install" {
|
if isOpenWrt() {
|
||||||
err := afterInstall()
|
// On OpenWrt it is important to run enable after the service installation
|
||||||
|
// Otherwise, the service won't start on the system startup
|
||||||
|
_, 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")
|
||||||
@ -166,9 +169,33 @@ There are a few more things that must be configured before you can use it.
|
|||||||
Click on the link below and follow the Installation Wizard steps to finish setup.`)
|
Click on the link below and follow the Installation Wizard steps to finish setup.`)
|
||||||
printHTTPAddresses("http")
|
printHTTPAddresses("http")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} else if action == "uninstall" {
|
// handleServiceStatusCommand handles service "uninstall" command
|
||||||
cleanupService()
|
func handleServiceUninstallCommand(s service.Service) {
|
||||||
|
if isOpenWrt() {
|
||||||
|
// On OpenWrt it is important to run disable command first
|
||||||
|
// as it will remove the symlink
|
||||||
|
_, err := runInitdCommand("disable")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err := svcAction(s, "uninstall")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if runtime.GOOS == "darwin" {
|
||||||
|
// Removing log files on cleanup and ignore errors
|
||||||
|
err := os.Remove(launchdStdoutPath)
|
||||||
|
if err != nil && !os.IsNotExist(err) {
|
||||||
|
log.Printf("cannot remove %s", launchdStdoutPath)
|
||||||
|
}
|
||||||
|
err = os.Remove(launchdStderrPath)
|
||||||
|
if err != nil && !os.IsNotExist(err) {
|
||||||
|
log.Printf("cannot remove %s", launchdStderrPath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -191,43 +218,33 @@ func configureService(c *service.Config) {
|
|||||||
// Use modified service file templates
|
// Use modified service file templates
|
||||||
c.Option["SystemdScript"] = systemdScript
|
c.Option["SystemdScript"] = systemdScript
|
||||||
c.Option["SysvScript"] = sysvScript
|
c.Option["SysvScript"] = sysvScript
|
||||||
|
|
||||||
|
// On OpenWrt we're using a different type of sysvScript
|
||||||
|
if isOpenWrt() {
|
||||||
|
c.Option["SysvScript"] = openWrtScript
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// On SysV systems supported by kardianos/service package, there must be multiple /etc/rc{N}.d directories.
|
// runInitdCommand runs init.d service command
|
||||||
// On OpenWrt, however, there is only /etc/rc.d - we handle this case ourselves.
|
// returns command code or error if any
|
||||||
// We also use relative path, because this is how all other service files are set up.
|
func runInitdCommand(action string) (int, error) {
|
||||||
func afterInstall() error {
|
confPath := "/etc/init.d/" + serviceName
|
||||||
if service.Platform() == "unix-systemv" && fileExists("/etc/rc.d") {
|
code, _, err := runCommand("sh", "-c", confPath+" "+action)
|
||||||
confPath := "../init.d/" + serviceName
|
return code, err
|
||||||
err := os.Symlink(confPath, "/etc/rc.d/S99"+serviceName)
|
}
|
||||||
|
|
||||||
|
// isOpenWrt checks if OS is OpenWRT
|
||||||
|
func isOpenWrt() bool {
|
||||||
|
if runtime.GOOS != "linux" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := ioutil.ReadFile("/etc/os-release")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return false
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// cleanupService called on the service uninstall, cleans up additional files if needed
|
|
||||||
func cleanupService() {
|
|
||||||
if runtime.GOOS == "darwin" {
|
|
||||||
// Removing log files on cleanup and ignore errors
|
|
||||||
err := os.Remove(launchdStdoutPath)
|
|
||||||
if err != nil && !os.IsNotExist(err) {
|
|
||||||
log.Printf("cannot remove %s", launchdStdoutPath)
|
|
||||||
}
|
|
||||||
err = os.Remove(launchdStderrPath)
|
|
||||||
if err != nil && !os.IsNotExist(err) {
|
|
||||||
log.Printf("cannot remove %s", launchdStderrPath)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if service.Platform() == "unix-systemv" {
|
return strings.Contains(string(body), "OpenWrt")
|
||||||
fn := "/etc/rc.d/S99" + serviceName
|
|
||||||
err := os.Remove(fn)
|
|
||||||
if err != nil && !os.IsNotExist(err) {
|
|
||||||
log.Printf("os.Remove: %s: %s", fn, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Basically the same template as the one defined in github.com/kardianos/service
|
// Basically the same template as the one defined in github.com/kardianos/service
|
||||||
@ -388,3 +405,55 @@ case "$1" in
|
|||||||
esac
|
esac
|
||||||
exit 0
|
exit 0
|
||||||
`
|
`
|
||||||
|
|
||||||
|
// OpenWrt procd init script
|
||||||
|
// https://github.com/AdguardTeam/AdGuardHome/issues/1386
|
||||||
|
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
|
||||||
|
}
|
||||||
|
`
|
||||||
|
Loading…
Reference in New Issue
Block a user