-
Notifications
You must be signed in to change notification settings - Fork 19
/
service.go
167 lines (148 loc) · 3.88 KB
/
service.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
package cli
import (
"bytes"
"errors"
"fmt"
"os"
"os/exec"
"github.com/kardianos/service"
"github.com/Control-D-Inc/ctrld/internal/router"
)
// newService wraps service.New call to return service.Service
// wrapper which is suitable for the current platform.
func newService(i service.Interface, c *service.Config) (service.Service, error) {
s, err := service.New(i, c)
if err != nil {
return nil, err
}
switch {
case router.IsOldOpenwrt():
return &procd{&sysV{s}}, nil
case router.IsGLiNet():
return &sysV{s}, nil
case s.Platform() == "unix-systemv":
return &sysV{s}, nil
case s.Platform() == "linux-systemd":
return &systemd{s}, nil
}
return s, nil
}
// sysV wraps a service.Service, and provide start/stop/status command
// base on "/etc/init.d/<service_name>".
//
// Use this on system where "service" command is not available, like GL.iNET router.
type sysV struct {
service.Service
}
func (s *sysV) installed() bool {
fi, err := os.Stat("/etc/init.d/ctrld")
if err != nil {
return false
}
mode := fi.Mode()
return mode.IsRegular() && (mode&0111) != 0
}
func (s *sysV) Start() error {
if !s.installed() {
return service.ErrNotInstalled
}
_, err := exec.Command("/etc/init.d/ctrld", "start").CombinedOutput()
return err
}
func (s *sysV) Stop() error {
if !s.installed() {
return service.ErrNotInstalled
}
_, err := exec.Command("/etc/init.d/ctrld", "stop").CombinedOutput()
return err
}
func (s *sysV) Restart() error {
if !s.installed() {
return service.ErrNotInstalled
}
// We don't care about error returned by s.Stop,
// because the service may already be stopped.
_ = s.Stop()
return s.Start()
}
func (s *sysV) Status() (service.Status, error) {
if !s.installed() {
return service.StatusUnknown, service.ErrNotInstalled
}
return unixSystemVServiceStatus()
}
// procd wraps a service.Service, and provide start/stop command
// base on "/etc/init.d/<service_name>", status command base on parsing "ps" command output.
//
// Use this on system where "/etc/init.d/<service_name> status" command is not available,
// like old GL.iNET Opal router.
type procd struct {
*sysV
}
func (s *procd) Status() (service.Status, error) {
if !s.installed() {
return service.StatusUnknown, service.ErrNotInstalled
}
exe, err := os.Executable()
if err != nil {
return service.StatusUnknown, nil
}
// Looking for something like "/sbin/ctrld run ".
shellCmd := fmt.Sprintf("ps | grep -q %q", exe+" [r]un ")
if err := exec.Command("sh", "-c", shellCmd).Run(); err != nil {
return service.StatusStopped, nil
}
return service.StatusRunning, nil
}
// procd wraps a service.Service, and provide status command to
// report the status correctly.
type systemd struct {
service.Service
}
func (s *systemd) Status() (service.Status, error) {
out, _ := exec.Command("systemctl", "status", "ctrld").CombinedOutput()
if bytes.Contains(out, []byte("/FAILURE)")) {
return service.StatusStopped, nil
}
return s.Service.Status()
}
type task struct {
f func() error
abortOnError bool
}
func doTasks(tasks []task) bool {
var prevErr error
for _, task := range tasks {
if err := task.f(); err != nil {
if task.abortOnError {
mainLog.Load().Error().Msg(errors.Join(prevErr, err).Error())
return false
}
prevErr = err
}
}
return true
}
func checkHasElevatedPrivilege() {
ok, err := hasElevatedPrivilege()
if err != nil {
mainLog.Load().Error().Msgf("could not detect user privilege: %v", err)
return
}
if !ok {
mainLog.Load().Error().Msg("Please relaunch process with admin/root privilege.")
os.Exit(1)
}
}
func unixSystemVServiceStatus() (service.Status, error) {
out, err := exec.Command("/etc/init.d/ctrld", "status").CombinedOutput()
if err != nil {
return service.StatusUnknown, nil
}
switch string(bytes.ToLower(bytes.TrimSpace(out))) {
case "running":
return service.StatusRunning, nil
default:
return service.StatusStopped, nil
}
}