├── .gitignore
├── LICENSE
├── README.md
├── daemon.go
├── daemon_darwin.go
├── daemon_freebsd.go
├── daemon_linux.go
├── daemon_linux_systemd.go
├── daemon_linux_systemv.go
├── daemon_linux_upstart.go
├── daemon_windows.go
├── examples
├── cron
│ └── cron_job.go
└── myservice.go
├── go.mod
├── go.sum
├── helper.go
├── helper_legacy.go
└── helper_windows.go
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2020 The Go Authors
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Go Daemon
2 |
3 | A daemon package for use with Go (golang) services
4 |
5 | [](https://godoc.org/github.com/takama/daemon)
6 |
7 | ## Examples
8 |
9 | ### Simplest example (just install self as daemon)
10 |
11 | ```go
12 | package main
13 |
14 | import (
15 | "fmt"
16 | "log"
17 |
18 | "github.com/takama/daemon"
19 | )
20 |
21 | func main() {
22 | service, err := daemon.New("name", "description", daemon.SystemDaemon)
23 | if err != nil {
24 | log.Fatal("Error: ", err)
25 | }
26 | status, err := service.Install()
27 | if err != nil {
28 | log.Fatal(status, "\nError: ", err)
29 | }
30 | fmt.Println(status)
31 | }
32 | ```
33 |
34 | ### Real example
35 |
36 | ```go
37 | // Example of a daemon with echo service
38 | package main
39 |
40 | import (
41 | "fmt"
42 | "log"
43 | "net"
44 | "os"
45 | "os/signal"
46 | "syscall"
47 |
48 | "github.com/takama/daemon"
49 | )
50 |
51 | const (
52 |
53 | // name of the service
54 | name = "myservice"
55 | description = "My Echo Service"
56 |
57 | // port which daemon should be listen
58 | port = ":9977"
59 | )
60 |
61 | // dependencies that are NOT required by the service, but might be used
62 | var dependencies = []string{"dummy.service"}
63 |
64 | var stdlog, errlog *log.Logger
65 |
66 | // Service has embedded daemon
67 | type Service struct {
68 | daemon.Daemon
69 | }
70 |
71 | // Manage by daemon commands or run the daemon
72 | func (service *Service) Manage() (string, error) {
73 |
74 | usage := "Usage: myservice install | remove | start | stop | status"
75 |
76 | // if received any kind of command, do it
77 | if len(os.Args) > 1 {
78 | command := os.Args[1]
79 | switch command {
80 | case "install":
81 | return service.Install()
82 | case "remove":
83 | return service.Remove()
84 | case "start":
85 | return service.Start()
86 | case "stop":
87 | return service.Stop()
88 | case "status":
89 | return service.Status()
90 | default:
91 | return usage, nil
92 | }
93 | }
94 |
95 | // Do something, call your goroutines, etc
96 |
97 | // Set up channel on which to send signal notifications.
98 | // We must use a buffered channel or risk missing the signal
99 | // if we're not ready to receive when the signal is sent.
100 | interrupt := make(chan os.Signal, 1)
101 | signal.Notify(interrupt, os.Interrupt, os.Kill, syscall.SIGTERM)
102 |
103 | // Set up listener for defined host and port
104 | listener, err := net.Listen("tcp", port)
105 | if err != nil {
106 | return "Possibly was a problem with the port binding", err
107 | }
108 |
109 | // set up channel on which to send accepted connections
110 | listen := make(chan net.Conn, 100)
111 | go acceptConnection(listener, listen)
112 |
113 | // loop work cycle with accept connections or interrupt
114 | // by system signal
115 | for {
116 | select {
117 | case conn := <-listen:
118 | go handleClient(conn)
119 | case killSignal := <-interrupt:
120 | stdlog.Println("Got signal:", killSignal)
121 | stdlog.Println("Stoping listening on ", listener.Addr())
122 | listener.Close()
123 | if killSignal == os.Interrupt {
124 | return "Daemon was interruped by system signal", nil
125 | }
126 | return "Daemon was killed", nil
127 | }
128 | }
129 |
130 | // never happen, but need to complete code
131 | return usage, nil
132 | }
133 |
134 | // Accept a client connection and collect it in a channel
135 | func acceptConnection(listener net.Listener, listen chan<- net.Conn) {
136 | for {
137 | conn, err := listener.Accept()
138 | if err != nil {
139 | continue
140 | }
141 | listen <- conn
142 | }
143 | }
144 |
145 | func handleClient(client net.Conn) {
146 | for {
147 | buf := make([]byte, 4096)
148 | numbytes, err := client.Read(buf)
149 | if numbytes == 0 || err != nil {
150 | return
151 | }
152 | client.Write(buf[:numbytes])
153 | }
154 | }
155 |
156 | func init() {
157 | stdlog = log.New(os.Stdout, "", log.Ldate|log.Ltime)
158 | errlog = log.New(os.Stderr, "", log.Ldate|log.Ltime)
159 | }
160 |
161 | func main() {
162 | srv, err := daemon.New(name, description, daemon.SystemDaemon, dependencies...)
163 | if err != nil {
164 | errlog.Println("Error: ", err)
165 | os.Exit(1)
166 | }
167 | service := &Service{srv}
168 | status, err := service.Manage()
169 | if err != nil {
170 | errlog.Println(status, "\nError: ", err)
171 | os.Exit(1)
172 | }
173 | fmt.Println(status)
174 | }
175 | ```
176 |
177 | ### Service config file
178 |
179 | Optionally, service config file can be retrieved or updated by calling
180 | `GetTemplate() string` and `SetTemplate(string)` methods(except MS
181 | Windows). Template will be a default Go Template(`"text/template"`).
182 |
183 | If `SetTemplate` is not called, default template content will be used
184 | while creating service.
185 |
186 | | Variable | Description |
187 | | ------------ | -------------------------------- |
188 | | Description | Description for service |
189 | | Dependencies | Service dependencies |
190 | | Name | Service name |
191 | | Path | Path of service executable |
192 | | Args | Arguments for service executable |
193 |
194 | #### Example template(for linux systemv)
195 |
196 | ```ini
197 | [Unit]
198 | Description={{.Description}}
199 | Requires={{.Dependencies}}
200 | After={{.Dependencies}}
201 |
202 | [Service]
203 | PIDFile=/var/run/{{.Name}}.pid
204 | ExecStartPre=/bin/rm -f /var/run/{{.Name}}.pid
205 | ExecStart={{.Path}} {{.Args}}
206 | Restart=on-failure
207 |
208 | [Install]
209 | WantedBy=multi-user.target
210 | ```
211 |
212 | ### Cron example
213 |
214 | See `examples/cron/cron_job.go`
215 |
216 | ## Contributors (unsorted)
217 |
218 | - [Sheile](https://github.com/Sheile)
219 | - [Nguyen Trung Loi](https://github.com/loint)
220 | - [Donny Prasetyobudi](https://github.com/donnpebe)
221 | - [Mark Berner](https://github.com/mark2b)
222 | - [Fatih Kaya](https://github.com/fatihky)
223 | - [Jannick Fahlbusch](https://github.com/jannickfahlbusch)
224 | - [TobyZXJ](https://github.com/tobyzxj)
225 | - [Pichu Chen](https://github.com/PichuChen)
226 | - [Eric Halpern](https://github.com/ehalpern)
227 | - [Yota](https://github.com/nus)
228 | - [Erkan Durmus](https://github.com/derkan)
229 | - [maxxant](https://github.com/maxxant)
230 | - [1for](https://github.com/1for)
231 | - [okamura](https://github.com/sidepelican)
232 | - [0X8C - Demired](https://github.com/Demired)
233 | - [Maximus](https://github.com/maximus12793)
234 | - [AlgorathDev](https://github.com/AlgorathDev)
235 | - [Alexis Camilleri](https://github.com/krysennn)
236 | - [neverland4u](https://github.com/neverland4u)
237 | - [Rustam](https://github.com/rusq)
238 | - [King'ori Maina](https://github.com/itskingori)
239 |
240 | All the contributors are welcome. If you would like to be the contributor please accept some rules.
241 |
242 | - The pull requests will be accepted only in `develop` branch
243 | - All modifications or additions should be tested
244 | - Sorry, We will not accept code with any dependency, only standard library
245 |
246 | Thank you for your understanding!
247 |
248 | ## License
249 |
250 | [MIT Public License](https://github.com/takama/daemon/blob/master/LICENSE)
251 |
--------------------------------------------------------------------------------
/daemon.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 The Go Authors. All rights reserved.
2 | // Use of this source code is governed by
3 | // license that can be found in the LICENSE file.
4 |
5 | /*
6 | Package daemon v1.0.0 for use with Go (golang) services.
7 |
8 | Package daemon provides primitives for daemonization of golang services. In the
9 | current implementation the only supported operating systems are macOS, FreeBSD,
10 | Linux and Windows. Also to note, for global daemons one must have root rights to
11 | install or remove the service. The only exception is macOS where there is an
12 | implementation of a user daemon that can installed or removed by the current
13 | user.
14 |
15 | Example:
16 |
17 | // Example of a daemon with echo service
18 | package main
19 |
20 | import (
21 | "fmt"
22 | "log"
23 | "net"
24 | "os"
25 | "os/signal"
26 | "syscall"
27 |
28 | "github.com/takama/daemon"
29 | )
30 |
31 | const (
32 |
33 | // name of the service
34 | name = "myservice"
35 | description = "My Echo Service"
36 |
37 | // port which daemon should be listen
38 | port = ":9977"
39 | )
40 |
41 | // dependencies that are NOT required by the service, but might be used
42 | var dependencies = []string{"dummy.service"}
43 |
44 | var stdlog, errlog *log.Logger
45 |
46 | // Service has embedded daemon
47 | type Service struct {
48 | daemon.Daemon
49 | }
50 |
51 | // Manage by daemon commands or run the daemon
52 | func (service *Service) Manage() (string, error) {
53 |
54 | usage := "Usage: myservice install | remove | start | stop | status"
55 |
56 | // if received any kind of command, do it
57 | if len(os.Args) > 1 {
58 | command := os.Args[1]
59 | switch command {
60 | case "install":
61 | return service.Install()
62 | case "remove":
63 | return service.Remove()
64 | case "start":
65 | return service.Start()
66 | case "stop":
67 | return service.Stop()
68 | case "status":
69 | return service.Status()
70 | default:
71 | return usage, nil
72 | }
73 | }
74 |
75 | // Do something, call your goroutines, etc
76 |
77 | // Set up channel on which to send signal notifications.
78 | // We must use a buffered channel or risk missing the signal
79 | // if we're not ready to receive when the signal is sent.
80 | interrupt := make(chan os.Signal, 1)
81 | signal.Notify(interrupt, os.Interrupt, os.Kill, syscall.SIGTERM)
82 |
83 | // Set up listener for defined host and port
84 | listener, err := net.Listen("tcp", port)
85 | if err != nil {
86 | return "Possibly was a problem with the port binding", err
87 | }
88 |
89 | // set up channel on which to send accepted connections
90 | listen := make(chan net.Conn, 100)
91 | go acceptConnection(listener, listen)
92 |
93 | // loop work cycle with accept connections or interrupt
94 | // by system signal
95 | for {
96 | select {
97 | case conn := <-listen:
98 | go handleClient(conn)
99 | case killSignal := <-interrupt:
100 | stdlog.Println("Got signal:", killSignal)
101 | stdlog.Println("Stoping listening on ", listener.Addr())
102 | listener.Close()
103 | if killSignal == os.Interrupt {
104 | return "Daemon was interrupted by system signal", nil
105 | }
106 | return "Daemon was killed", nil
107 | }
108 | }
109 |
110 | // never happen, but need to complete code
111 | return usage, nil
112 | }
113 |
114 | // Accept a client connection and collect it in a channel
115 | func acceptConnection(listener net.Listener, listen chan<- net.Conn) {
116 | for {
117 | conn, err := listener.Accept()
118 | if err != nil {
119 | continue
120 | }
121 | listen <- conn
122 | }
123 | }
124 |
125 | func handleClient(client net.Conn) {
126 | for {
127 | buf := make([]byte, 4096)
128 | numbytes, err := client.Read(buf)
129 | if numbytes == 0 || err != nil {
130 | return
131 | }
132 | client.Write(buf[:numbytes])
133 | }
134 | }
135 |
136 | func init() {
137 | stdlog = log.New(os.Stdout, "", log.Ldate|log.Ltime)
138 | errlog = log.New(os.Stderr, "", log.Ldate|log.Ltime)
139 | }
140 |
141 | func main() {
142 | srv, err := daemon.New(name, description, daemon.SystemDaemon, dependencies...)
143 | if err != nil {
144 | errlog.Println("Error: ", err)
145 | os.Exit(1)
146 | }
147 | service := &Service{srv}
148 | status, err := service.Manage()
149 | if err != nil {
150 | errlog.Println(status, "\nError: ", err)
151 | os.Exit(1)
152 | }
153 | fmt.Println(status)
154 | }
155 |
156 | Go daemon
157 | */
158 | package daemon
159 |
160 | import (
161 | "errors"
162 | "runtime"
163 | "strings"
164 | )
165 |
166 | // Status constants.
167 | const (
168 | statNotInstalled = "Service not installed"
169 | )
170 |
171 | // Daemon interface has a standard set of methods/commands
172 | type Daemon interface {
173 | // GetTemplate - gets service config template
174 | GetTemplate() string
175 |
176 | // SetTemplate - sets service config template
177 | SetTemplate(string) error
178 |
179 | // Install the service into the system
180 | Install(args ...string) (string, error)
181 |
182 | // Remove the service and all corresponding files from the system
183 | Remove() (string, error)
184 |
185 | // Start the service
186 | Start() (string, error)
187 |
188 | // Stop the service
189 | Stop() (string, error)
190 |
191 | // Status - check the service status
192 | Status() (string, error)
193 |
194 | // Run - run executable service
195 | Run(e Executable) (string, error)
196 | }
197 |
198 | // Executable interface defines controlling methods of executable service
199 | type Executable interface {
200 | // Start - non-blocking start service
201 | Start()
202 | // Stop - non-blocking stop service
203 | Stop()
204 | // Run - blocking run service
205 | Run()
206 | }
207 |
208 | // Kind is type of the daemon
209 | type Kind string
210 |
211 | const (
212 | // UserAgent is a user daemon that runs as the currently logged in user and
213 | // stores its property list in the user’s individual LaunchAgents directory.
214 | // In other words, per-user agents provided by the user. Valid for macOS only.
215 | UserAgent Kind = "UserAgent"
216 |
217 | // GlobalAgent is a user daemon that runs as the currently logged in user and
218 | // stores its property list in the users' global LaunchAgents directory. In
219 | // other words, per-user agents provided by the administrator. Valid for macOS
220 | // only.
221 | GlobalAgent Kind = "GlobalAgent"
222 |
223 | // GlobalDaemon is a system daemon that runs as the root user and stores its
224 | // property list in the global LaunchDaemons directory. In other words,
225 | // system-wide daemons provided by the administrator. Valid for macOS only.
226 | GlobalDaemon Kind = "GlobalDaemon"
227 |
228 | // SystemDaemon is a system daemon that runs as the root user. In other words,
229 | // system-wide daemons provided by the administrator. Valid for FreeBSD, Linux
230 | // and Windows only.
231 | SystemDaemon Kind = "SystemDaemon"
232 | )
233 |
234 | // New - Create a new daemon
235 | //
236 | // name: name of the service
237 | //
238 | // description: any explanation, what is the service, its purpose
239 | //
240 | // kind: what kind of daemon to create
241 | func New(name, description string, kind Kind, dependencies ...string) (Daemon, error) {
242 | switch runtime.GOOS {
243 | case "darwin":
244 | if kind == SystemDaemon {
245 | return nil, errors.New("Invalid daemon kind specified")
246 | }
247 | case "freebsd":
248 | if kind != SystemDaemon {
249 | return nil, errors.New("Invalid daemon kind specified")
250 | }
251 | case "linux":
252 | if kind != SystemDaemon {
253 | return nil, errors.New("Invalid daemon kind specified")
254 | }
255 | case "windows":
256 | if kind != SystemDaemon {
257 | return nil, errors.New("Invalid daemon kind specified")
258 | }
259 | }
260 |
261 | return newDaemon(strings.Join(strings.Fields(name), "_"), description, kind, dependencies)
262 | }
263 |
--------------------------------------------------------------------------------
/daemon_darwin.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 The Go Authors. All rights reserved.
2 | // Use of this source code is governed by
3 | // license that can be found in the LICENSE file.
4 |
5 | // Package daemon darwin (mac os x) version
6 | package daemon
7 |
8 | import (
9 | "os"
10 | "os/exec"
11 | "os/user"
12 | "path/filepath"
13 | "regexp"
14 | "text/template"
15 | )
16 |
17 | // darwinRecord - standard record (struct) for darwin version of daemon package
18 | type darwinRecord struct {
19 | name string
20 | description string
21 | kind Kind
22 | dependencies []string
23 | }
24 |
25 | func newDaemon(name, description string, kind Kind, dependencies []string) (Daemon, error) {
26 |
27 | return &darwinRecord{name, description, kind, dependencies}, nil
28 | }
29 |
30 | // Standard service path for system daemons
31 | func (darwin *darwinRecord) servicePath() string {
32 | var path string
33 |
34 | switch darwin.kind {
35 | case UserAgent:
36 | usr, _ := user.Current()
37 | path = usr.HomeDir + "/Library/LaunchAgents/" + darwin.name + ".plist"
38 | case GlobalAgent:
39 | path = "/Library/LaunchAgents/" + darwin.name + ".plist"
40 | case GlobalDaemon:
41 | path = "/Library/LaunchDaemons/" + darwin.name + ".plist"
42 | }
43 |
44 | return path
45 | }
46 |
47 | // Is a service installed
48 | func (darwin *darwinRecord) isInstalled() bool {
49 |
50 | if _, err := os.Stat(darwin.servicePath()); err == nil {
51 | return true
52 | }
53 |
54 | return false
55 | }
56 |
57 | // Get executable path
58 | func execPath() (string, error) {
59 | return filepath.Abs(os.Args[0])
60 | }
61 |
62 | // Check service is running
63 | func (darwin *darwinRecord) checkRunning() (string, bool) {
64 | output, err := exec.Command("launchctl", "list", darwin.name).Output()
65 | if err == nil {
66 | if matched, err := regexp.MatchString(darwin.name, string(output)); err == nil && matched {
67 | reg := regexp.MustCompile("PID\" = ([0-9]+);")
68 | data := reg.FindStringSubmatch(string(output))
69 | if len(data) > 1 {
70 | return "Service (pid " + data[1] + ") is running...", true
71 | }
72 | return "Service is running...", true
73 | }
74 | }
75 |
76 | return "Service is stopped", false
77 | }
78 |
79 | // Install the service
80 | func (darwin *darwinRecord) Install(args ...string) (string, error) {
81 | installAction := "Install " + darwin.description + ":"
82 |
83 | ok, err := checkPrivileges()
84 | if !ok && darwin.kind != UserAgent {
85 | return installAction + failed, err
86 | }
87 |
88 | srvPath := darwin.servicePath()
89 |
90 | if darwin.isInstalled() {
91 | return installAction + failed, ErrAlreadyInstalled
92 | }
93 |
94 | file, err := os.Create(srvPath)
95 | if err != nil {
96 | return installAction + failed, err
97 | }
98 | defer file.Close()
99 |
100 | execPatch, err := executablePath(darwin.name)
101 | if err != nil {
102 | return installAction + failed, err
103 | }
104 |
105 | templ, err := template.New("propertyList").Parse(propertyList)
106 | if err != nil {
107 | return installAction + failed, err
108 | }
109 |
110 | if err := templ.Execute(
111 | file,
112 | &struct {
113 | Name, Path string
114 | Args []string
115 | }{darwin.name, execPatch, args},
116 | ); err != nil {
117 | return installAction + failed, err
118 | }
119 |
120 | return installAction + success, nil
121 | }
122 |
123 | // Remove the service
124 | func (darwin *darwinRecord) Remove() (string, error) {
125 | removeAction := "Removing " + darwin.description + ":"
126 |
127 | ok, err := checkPrivileges()
128 | if !ok && darwin.kind != UserAgent {
129 | return removeAction + failed, err
130 | }
131 |
132 | if !darwin.isInstalled() {
133 | return removeAction + failed, ErrNotInstalled
134 | }
135 |
136 | if err := os.Remove(darwin.servicePath()); err != nil {
137 | return removeAction + failed, err
138 | }
139 |
140 | return removeAction + success, nil
141 | }
142 |
143 | // Start the service
144 | func (darwin *darwinRecord) Start() (string, error) {
145 | startAction := "Starting " + darwin.description + ":"
146 |
147 | ok, err := checkPrivileges()
148 | if !ok && darwin.kind != UserAgent {
149 | return startAction + failed, err
150 | }
151 |
152 | if !darwin.isInstalled() {
153 | return startAction + failed, ErrNotInstalled
154 | }
155 |
156 | if _, ok := darwin.checkRunning(); ok {
157 | return startAction + failed, ErrAlreadyRunning
158 | }
159 |
160 | if err := exec.Command("launchctl", "load", darwin.servicePath()).Run(); err != nil {
161 | return startAction + failed, err
162 | }
163 |
164 | return startAction + success, nil
165 | }
166 |
167 | // Stop the service
168 | func (darwin *darwinRecord) Stop() (string, error) {
169 | stopAction := "Stopping " + darwin.description + ":"
170 |
171 | ok, err := checkPrivileges()
172 | if !ok && darwin.kind != UserAgent {
173 | return stopAction + failed, err
174 | }
175 |
176 | if !darwin.isInstalled() {
177 | return stopAction + failed, ErrNotInstalled
178 | }
179 |
180 | if _, ok := darwin.checkRunning(); !ok {
181 | return stopAction + failed, ErrAlreadyStopped
182 | }
183 |
184 | if err := exec.Command("launchctl", "unload", darwin.servicePath()).Run(); err != nil {
185 | return stopAction + failed, err
186 | }
187 |
188 | return stopAction + success, nil
189 | }
190 |
191 | // Status - Get service status
192 | func (darwin *darwinRecord) Status() (string, error) {
193 |
194 | ok, err := checkPrivileges()
195 | if !ok && darwin.kind != UserAgent {
196 | return "", err
197 | }
198 |
199 | if !darwin.isInstalled() {
200 | return statNotInstalled, ErrNotInstalled
201 | }
202 |
203 | statusAction, _ := darwin.checkRunning()
204 |
205 | return statusAction, nil
206 | }
207 |
208 | // Run - Run service
209 | func (darwin *darwinRecord) Run(e Executable) (string, error) {
210 | runAction := "Running " + darwin.description + ":"
211 | e.Run()
212 | return runAction + " completed.", nil
213 | }
214 |
215 | // GetTemplate - gets service config template
216 | func (linux *darwinRecord) GetTemplate() string {
217 | return propertyList
218 | }
219 |
220 | // SetTemplate - sets service config template
221 | func (linux *darwinRecord) SetTemplate(tplStr string) error {
222 | propertyList = tplStr
223 | return nil
224 | }
225 |
226 | var propertyList = `
227 |
228 |
229 |
230 | KeepAlive
231 |
232 | Label
233 | {{.Name}}
234 | ProgramArguments
235 |
236 | {{.Path}}
237 | {{range .Args}}{{.}}
238 | {{end}}
239 |
240 | RunAtLoad
241 |
242 | WorkingDirectory
243 | /usr/local/var
244 | StandardErrorPath
245 | /usr/local/var/log/{{.Name}}.err
246 | StandardOutPath
247 | /usr/local/var/log/{{.Name}}.log
248 |
249 |
250 | `
251 |
--------------------------------------------------------------------------------
/daemon_freebsd.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 The Go Authors. All rights reserved.
2 | // Use of this source code is governed by
3 | // license that can be found in the LICENSE file.
4 |
5 | package daemon
6 |
7 | import (
8 | "fmt"
9 | "io/ioutil"
10 | "os"
11 | "os/exec"
12 | "path/filepath"
13 | "regexp"
14 | "strings"
15 | "text/template"
16 | )
17 |
18 | // systemVRecord - standard record (struct) for linux systemV version of daemon package
19 | type bsdRecord struct {
20 | name string
21 | description string
22 | kind Kind
23 | dependencies []string
24 | }
25 |
26 | // Standard service path for systemV daemons
27 | func (bsd *bsdRecord) servicePath() string {
28 | return "/usr/local/etc/rc.d/" + bsd.name
29 | }
30 |
31 | // Is a service installed
32 | func (bsd *bsdRecord) isInstalled() bool {
33 |
34 | if _, err := os.Stat(bsd.servicePath()); err == nil {
35 | return true
36 | }
37 |
38 | return false
39 | }
40 |
41 | // Is a service is enabled
42 | func (bsd *bsdRecord) isEnabled() (bool, error) {
43 | rcConf, err := os.Open("/etc/rc.conf")
44 | if err != nil {
45 | fmt.Println("Error opening file:", err)
46 | return false, err
47 | }
48 | defer rcConf.Close()
49 | rcData, _ := ioutil.ReadAll(rcConf)
50 | r, _ := regexp.Compile(`.*` + bsd.name + `_enable="YES".*`)
51 | v := string(r.Find(rcData))
52 | var chrFound, sharpFound bool
53 | for _, c := range v {
54 | if c == '#' && !chrFound {
55 | sharpFound = true
56 | break
57 | } else if !sharpFound && c != ' ' {
58 | chrFound = true
59 | break
60 | }
61 | }
62 | return chrFound, nil
63 | }
64 |
65 | func (bsd *bsdRecord) getCmd(cmd string) string {
66 | if ok, err := bsd.isEnabled(); !ok || err != nil {
67 | fmt.Println("Service is not enabled, using one" + cmd + " instead")
68 | cmd = "one" + cmd
69 | }
70 | return cmd
71 | }
72 |
73 | // Get the daemon properly
74 | func newDaemon(name, description string, kind Kind, dependencies []string) (Daemon, error) {
75 | return &bsdRecord{name, description, kind, dependencies}, nil
76 | }
77 |
78 | func execPath() (name string, err error) {
79 | name = os.Args[0]
80 | if name[0] == '.' {
81 | name, err = filepath.Abs(name)
82 | if err == nil {
83 | name = filepath.Clean(name)
84 | }
85 | } else {
86 | name, err = exec.LookPath(filepath.Clean(name))
87 | }
88 | return name, err
89 | }
90 |
91 | // Check service is running
92 | func (bsd *bsdRecord) checkRunning() (string, bool) {
93 | output, err := exec.Command("service", bsd.name, bsd.getCmd("status")).Output()
94 | if err == nil {
95 | if matched, err := regexp.MatchString(bsd.name, string(output)); err == nil && matched {
96 | reg := regexp.MustCompile("pid ([0-9]+)")
97 | data := reg.FindStringSubmatch(string(output))
98 | if len(data) > 1 {
99 | return "Service (pid " + data[1] + ") is running...", true
100 | }
101 | return "Service is running...", true
102 | }
103 | }
104 |
105 | return "Service is stopped", false
106 | }
107 |
108 | // Install the service
109 | func (bsd *bsdRecord) Install(args ...string) (string, error) {
110 | installAction := "Install " + bsd.description + ":"
111 |
112 | if ok, err := checkPrivileges(); !ok {
113 | return installAction + failed, err
114 | }
115 |
116 | srvPath := bsd.servicePath()
117 |
118 | if bsd.isInstalled() {
119 | return installAction + failed, ErrAlreadyInstalled
120 | }
121 |
122 | file, err := os.Create(srvPath)
123 | if err != nil {
124 | return installAction + failed, err
125 | }
126 | defer file.Close()
127 |
128 | execPatch, err := executablePath(bsd.name)
129 | if err != nil {
130 | return installAction + failed, err
131 | }
132 |
133 | templ, err := template.New("bsdConfig").Parse(bsdConfig)
134 | if err != nil {
135 | return installAction + failed, err
136 | }
137 |
138 | if err := templ.Execute(
139 | file,
140 | &struct {
141 | Name, Description, Path, Args string
142 | }{bsd.name, bsd.description, execPatch, strings.Join(args, " ")},
143 | ); err != nil {
144 | return installAction + failed, err
145 | }
146 |
147 | if err := os.Chmod(srvPath, 0755); err != nil {
148 | return installAction + failed, err
149 | }
150 |
151 | return installAction + success, nil
152 | }
153 |
154 | // Remove the service
155 | func (bsd *bsdRecord) Remove() (string, error) {
156 | removeAction := "Removing " + bsd.description + ":"
157 |
158 | if ok, err := checkPrivileges(); !ok {
159 | return removeAction + failed, err
160 | }
161 |
162 | if !bsd.isInstalled() {
163 | return removeAction + failed, ErrNotInstalled
164 | }
165 |
166 | if err := os.Remove(bsd.servicePath()); err != nil {
167 | return removeAction + failed, err
168 | }
169 |
170 | return removeAction + success, nil
171 | }
172 |
173 | // Start the service
174 | func (bsd *bsdRecord) Start() (string, error) {
175 | startAction := "Starting " + bsd.description + ":"
176 |
177 | if ok, err := checkPrivileges(); !ok {
178 | return startAction + failed, err
179 | }
180 |
181 | if !bsd.isInstalled() {
182 | return startAction + failed, ErrNotInstalled
183 | }
184 |
185 | if _, ok := bsd.checkRunning(); ok {
186 | return startAction + failed, ErrAlreadyRunning
187 | }
188 |
189 | if err := exec.Command("service", bsd.name, bsd.getCmd("start")).Run(); err != nil {
190 | return startAction + failed, err
191 | }
192 |
193 | return startAction + success, nil
194 | }
195 |
196 | // Stop the service
197 | func (bsd *bsdRecord) Stop() (string, error) {
198 | stopAction := "Stopping " + bsd.description + ":"
199 |
200 | if ok, err := checkPrivileges(); !ok {
201 | return stopAction + failed, err
202 | }
203 |
204 | if !bsd.isInstalled() {
205 | return stopAction + failed, ErrNotInstalled
206 | }
207 |
208 | if _, ok := bsd.checkRunning(); !ok {
209 | return stopAction + failed, ErrAlreadyStopped
210 | }
211 |
212 | if err := exec.Command("service", bsd.name, bsd.getCmd("stop")).Run(); err != nil {
213 | return stopAction + failed, err
214 | }
215 |
216 | return stopAction + success, nil
217 | }
218 |
219 | // Status - Get service status
220 | func (bsd *bsdRecord) Status() (string, error) {
221 |
222 | if ok, err := checkPrivileges(); !ok {
223 | return "", err
224 | }
225 |
226 | if !bsd.isInstalled() {
227 | return statNotInstalled, ErrNotInstalled
228 | }
229 |
230 | statusAction, _ := bsd.checkRunning()
231 |
232 | return statusAction, nil
233 | }
234 |
235 | // Run - Run service
236 | func (bsd *bsdRecord) Run(e Executable) (string, error) {
237 | runAction := "Running " + bsd.description + ":"
238 | e.Run()
239 | return runAction + " completed.", nil
240 | }
241 |
242 | // GetTemplate - gets service config template
243 | func (linux *bsdRecord) GetTemplate() string {
244 | return bsdConfig
245 | }
246 |
247 | // SetTemplate - sets service config template
248 | func (linux *bsdRecord) SetTemplate(tplStr string) error {
249 | bsdConfig = tplStr
250 | return nil
251 | }
252 |
253 | var bsdConfig = `#!/bin/sh
254 | #
255 | # PROVIDE: {{.Name}}
256 | # REQUIRE: networking syslog
257 | # KEYWORD:
258 |
259 | # Add the following lines to /etc/rc.conf to enable the {{.Name}}:
260 | #
261 | # {{.Name}}_enable="YES"
262 | #
263 |
264 |
265 | . /etc/rc.subr
266 |
267 | name="{{.Name}}"
268 | rcvar="{{.Name}}_enable"
269 | command="{{.Path}}"
270 | pidfile="/var/run/$name.pid"
271 |
272 | start_cmd="/usr/sbin/daemon -p $pidfile -f $command {{.Args}}"
273 | load_rc_config $name
274 | run_rc_command "$1"
275 | `
276 |
--------------------------------------------------------------------------------
/daemon_linux.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 The Go Authors. All rights reserved.
2 | // Use of this source code is governed by
3 | // license that can be found in the LICENSE file.
4 |
5 | // Package daemon linux version
6 | package daemon
7 |
8 | import (
9 | "os"
10 | )
11 |
12 | // Get the daemon properly
13 | func newDaemon(name, description string, kind Kind, dependencies []string) (Daemon, error) {
14 | // newer subsystem must be checked first
15 | if _, err := os.Stat("/run/systemd/system"); err == nil {
16 | return &systemDRecord{name, description, kind, dependencies}, nil
17 | }
18 | if _, err := os.Stat("/sbin/initctl"); err == nil {
19 | return &upstartRecord{name, description, kind, dependencies}, nil
20 | }
21 | return &systemVRecord{name, description, kind, dependencies}, nil
22 | }
23 |
24 | // Get executable path
25 | func execPath() (string, error) {
26 | return os.Readlink("/proc/self/exe")
27 | }
28 |
--------------------------------------------------------------------------------
/daemon_linux_systemd.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 The Go Authors. All rights reserved.
2 | // Use of this source code is governed by
3 | // license that can be found in the LICENSE file.
4 |
5 | package daemon
6 |
7 | import (
8 | "os"
9 | "os/exec"
10 | "regexp"
11 | "strings"
12 | "text/template"
13 | )
14 |
15 | // systemDRecord - standard record (struct) for linux systemD version of daemon package
16 | type systemDRecord struct {
17 | name string
18 | description string
19 | kind Kind
20 | dependencies []string
21 | }
22 |
23 | // Standard service path for systemD daemons
24 | func (linux *systemDRecord) servicePath() string {
25 | return "/etc/systemd/system/" + linux.name + ".service"
26 | }
27 |
28 | // Is a service installed
29 | func (linux *systemDRecord) isInstalled() bool {
30 |
31 | if _, err := os.Stat(linux.servicePath()); err == nil {
32 | return true
33 | }
34 |
35 | return false
36 | }
37 |
38 | // Check service is running
39 | func (linux *systemDRecord) checkRunning() (string, bool) {
40 | output, err := exec.Command("systemctl", "status", linux.name+".service").Output()
41 | if err == nil {
42 | if matched, err := regexp.MatchString("Active: active", string(output)); err == nil && matched {
43 | reg := regexp.MustCompile("Main PID: ([0-9]+)")
44 | data := reg.FindStringSubmatch(string(output))
45 | if len(data) > 1 {
46 | return "Service (pid " + data[1] + ") is running...", true
47 | }
48 | return "Service is running...", true
49 | }
50 | }
51 |
52 | return "Service is stopped", false
53 | }
54 |
55 | // Install the service
56 | func (linux *systemDRecord) Install(args ...string) (string, error) {
57 | installAction := "Install " + linux.description + ":"
58 |
59 | if ok, err := checkPrivileges(); !ok {
60 | return installAction + failed, err
61 | }
62 |
63 | srvPath := linux.servicePath()
64 |
65 | if linux.isInstalled() {
66 | return installAction + failed, ErrAlreadyInstalled
67 | }
68 |
69 | file, err := os.Create(srvPath)
70 | if err != nil {
71 | return installAction + failed, err
72 | }
73 | defer file.Close()
74 |
75 | execPatch, err := executablePath(linux.name)
76 | if err != nil {
77 | return installAction + failed, err
78 | }
79 |
80 | templ, err := template.New("systemDConfig").Parse(systemDConfig)
81 | if err != nil {
82 | return installAction + failed, err
83 | }
84 |
85 | if err := templ.Execute(
86 | file,
87 | &struct {
88 | Name, Description, Dependencies, Path, Args string
89 | }{
90 | linux.name,
91 | linux.description,
92 | strings.Join(linux.dependencies, " "),
93 | execPatch,
94 | strings.Join(args, " "),
95 | },
96 | ); err != nil {
97 | return installAction + failed, err
98 | }
99 |
100 | if err := exec.Command("systemctl", "daemon-reload").Run(); err != nil {
101 | return installAction + failed, err
102 | }
103 |
104 | if err := exec.Command("systemctl", "enable", linux.name+".service").Run(); err != nil {
105 | return installAction + failed, err
106 | }
107 |
108 | return installAction + success, nil
109 | }
110 |
111 | // Remove the service
112 | func (linux *systemDRecord) Remove() (string, error) {
113 | removeAction := "Removing " + linux.description + ":"
114 |
115 | if ok, err := checkPrivileges(); !ok {
116 | return removeAction + failed, err
117 | }
118 |
119 | if !linux.isInstalled() {
120 | return removeAction + failed, ErrNotInstalled
121 | }
122 |
123 | if err := exec.Command("systemctl", "disable", linux.name+".service").Run(); err != nil {
124 | return removeAction + failed, err
125 | }
126 |
127 | if err := os.Remove(linux.servicePath()); err != nil {
128 | return removeAction + failed, err
129 | }
130 |
131 | return removeAction + success, nil
132 | }
133 |
134 | // Start the service
135 | func (linux *systemDRecord) Start() (string, error) {
136 | startAction := "Starting " + linux.description + ":"
137 |
138 | if ok, err := checkPrivileges(); !ok {
139 | return startAction + failed, err
140 | }
141 |
142 | if !linux.isInstalled() {
143 | return startAction + failed, ErrNotInstalled
144 | }
145 |
146 | if _, ok := linux.checkRunning(); ok {
147 | return startAction + failed, ErrAlreadyRunning
148 | }
149 |
150 | if err := exec.Command("systemctl", "start", linux.name+".service").Run(); err != nil {
151 | return startAction + failed, err
152 | }
153 |
154 | return startAction + success, nil
155 | }
156 |
157 | // Stop the service
158 | func (linux *systemDRecord) Stop() (string, error) {
159 | stopAction := "Stopping " + linux.description + ":"
160 |
161 | if ok, err := checkPrivileges(); !ok {
162 | return stopAction + failed, err
163 | }
164 |
165 | if !linux.isInstalled() {
166 | return stopAction + failed, ErrNotInstalled
167 | }
168 |
169 | if _, ok := linux.checkRunning(); !ok {
170 | return stopAction + failed, ErrAlreadyStopped
171 | }
172 |
173 | if err := exec.Command("systemctl", "stop", linux.name+".service").Run(); err != nil {
174 | return stopAction + failed, err
175 | }
176 |
177 | return stopAction + success, nil
178 | }
179 |
180 | // Status - Get service status
181 | func (linux *systemDRecord) Status() (string, error) {
182 |
183 | if ok, err := checkPrivileges(); !ok {
184 | return "", err
185 | }
186 |
187 | if !linux.isInstalled() {
188 | return statNotInstalled, ErrNotInstalled
189 | }
190 |
191 | statusAction, _ := linux.checkRunning()
192 |
193 | return statusAction, nil
194 | }
195 |
196 | // Run - Run service
197 | func (linux *systemDRecord) Run(e Executable) (string, error) {
198 | runAction := "Running " + linux.description + ":"
199 | e.Run()
200 | return runAction + " completed.", nil
201 | }
202 |
203 | // GetTemplate - gets service config template
204 | func (linux *systemDRecord) GetTemplate() string {
205 | return systemDConfig
206 | }
207 |
208 | // SetTemplate - sets service config template
209 | func (linux *systemDRecord) SetTemplate(tplStr string) error {
210 | systemDConfig = tplStr
211 | return nil
212 | }
213 |
214 | var systemDConfig = `[Unit]
215 | Description={{.Description}}
216 | Requires={{.Dependencies}}
217 | After={{.Dependencies}}
218 |
219 | [Service]
220 | PIDFile=/var/run/{{.Name}}.pid
221 | ExecStartPre=/bin/rm -f /var/run/{{.Name}}.pid
222 | ExecStart={{.Path}} {{.Args}}
223 | Restart=on-failure
224 |
225 | [Install]
226 | WantedBy=multi-user.target
227 | `
228 |
--------------------------------------------------------------------------------
/daemon_linux_systemv.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 The Go Authors. All rights reserved.
2 | // Use of this source code is governed by
3 | // license that can be found in the LICENSE file.
4 |
5 | package daemon
6 |
7 | import (
8 | "os"
9 | "os/exec"
10 | "regexp"
11 | "strings"
12 | "text/template"
13 | )
14 |
15 | // systemVRecord - standard record (struct) for linux systemV version of daemon package
16 | type systemVRecord struct {
17 | name string
18 | description string
19 | kind Kind
20 | dependencies []string
21 | }
22 |
23 | // Standard service path for systemV daemons
24 | func (linux *systemVRecord) servicePath() string {
25 | return "/etc/init.d/" + linux.name
26 | }
27 |
28 | // Is a service installed
29 | func (linux *systemVRecord) isInstalled() bool {
30 |
31 | if _, err := os.Stat(linux.servicePath()); err == nil {
32 | return true
33 | }
34 |
35 | return false
36 | }
37 |
38 | // Check service is running
39 | func (linux *systemVRecord) checkRunning() (string, bool) {
40 | output, err := exec.Command("service", linux.name, "status").Output()
41 | if err == nil {
42 | if matched, err := regexp.MatchString(linux.name, string(output)); err == nil && matched {
43 | reg := regexp.MustCompile("pid ([0-9]+)")
44 | data := reg.FindStringSubmatch(string(output))
45 | if len(data) > 1 {
46 | return "Service (pid " + data[1] + ") is running...", true
47 | }
48 | return "Service is running...", true
49 | }
50 | }
51 |
52 | return "Service is stopped", false
53 | }
54 |
55 | // Install the service
56 | func (linux *systemVRecord) Install(args ...string) (string, error) {
57 | installAction := "Install " + linux.description + ":"
58 |
59 | if ok, err := checkPrivileges(); !ok {
60 | return installAction + failed, err
61 | }
62 |
63 | srvPath := linux.servicePath()
64 |
65 | if linux.isInstalled() {
66 | return installAction + failed, ErrAlreadyInstalled
67 | }
68 |
69 | file, err := os.Create(srvPath)
70 | if err != nil {
71 | return installAction + failed, err
72 | }
73 | defer file.Close()
74 |
75 | execPatch, err := executablePath(linux.name)
76 | if err != nil {
77 | return installAction + failed, err
78 | }
79 |
80 | templ, err := template.New("systemVConfig").Parse(systemVConfig)
81 | if err != nil {
82 | return installAction + failed, err
83 | }
84 |
85 | if err := templ.Execute(
86 | file,
87 | &struct {
88 | Name, Description, Path, Args string
89 | }{linux.name, linux.description, execPatch, strings.Join(args, " ")},
90 | ); err != nil {
91 | return installAction + failed, err
92 | }
93 |
94 | if err := os.Chmod(srvPath, 0755); err != nil {
95 | return installAction + failed, err
96 | }
97 |
98 | for _, i := range [...]string{"2", "3", "4", "5"} {
99 | if err := os.Symlink(srvPath, "/etc/rc"+i+".d/S87"+linux.name); err != nil {
100 | continue
101 | }
102 | }
103 | for _, i := range [...]string{"0", "1", "6"} {
104 | if err := os.Symlink(srvPath, "/etc/rc"+i+".d/K17"+linux.name); err != nil {
105 | continue
106 | }
107 | }
108 |
109 | return installAction + success, nil
110 | }
111 |
112 | // Remove the service
113 | func (linux *systemVRecord) Remove() (string, error) {
114 | removeAction := "Removing " + linux.description + ":"
115 |
116 | if ok, err := checkPrivileges(); !ok {
117 | return removeAction + failed, err
118 | }
119 |
120 | if !linux.isInstalled() {
121 | return removeAction + failed, ErrNotInstalled
122 | }
123 |
124 | if err := os.Remove(linux.servicePath()); err != nil {
125 | return removeAction + failed, err
126 | }
127 |
128 | for _, i := range [...]string{"2", "3", "4", "5"} {
129 | if err := os.Remove("/etc/rc" + i + ".d/S87" + linux.name); err != nil {
130 | continue
131 | }
132 | }
133 | for _, i := range [...]string{"0", "1", "6"} {
134 | if err := os.Remove("/etc/rc" + i + ".d/K17" + linux.name); err != nil {
135 | continue
136 | }
137 | }
138 |
139 | return removeAction + success, nil
140 | }
141 |
142 | // Start the service
143 | func (linux *systemVRecord) Start() (string, error) {
144 | startAction := "Starting " + linux.description + ":"
145 |
146 | if ok, err := checkPrivileges(); !ok {
147 | return startAction + failed, err
148 | }
149 |
150 | if !linux.isInstalled() {
151 | return startAction + failed, ErrNotInstalled
152 | }
153 |
154 | if _, ok := linux.checkRunning(); ok {
155 | return startAction + failed, ErrAlreadyRunning
156 | }
157 |
158 | if err := exec.Command("service", linux.name, "start").Run(); err != nil {
159 | return startAction + failed, err
160 | }
161 |
162 | return startAction + success, nil
163 | }
164 |
165 | // Stop the service
166 | func (linux *systemVRecord) Stop() (string, error) {
167 | stopAction := "Stopping " + linux.description + ":"
168 |
169 | if ok, err := checkPrivileges(); !ok {
170 | return stopAction + failed, err
171 | }
172 |
173 | if !linux.isInstalled() {
174 | return stopAction + failed, ErrNotInstalled
175 | }
176 |
177 | if _, ok := linux.checkRunning(); !ok {
178 | return stopAction + failed, ErrAlreadyStopped
179 | }
180 |
181 | if err := exec.Command("service", linux.name, "stop").Run(); err != nil {
182 | return stopAction + failed, err
183 | }
184 |
185 | return stopAction + success, nil
186 | }
187 |
188 | // Status - Get service status
189 | func (linux *systemVRecord) Status() (string, error) {
190 |
191 | if ok, err := checkPrivileges(); !ok {
192 | return "", err
193 | }
194 |
195 | if !linux.isInstalled() {
196 | return statNotInstalled, ErrNotInstalled
197 | }
198 |
199 | statusAction, _ := linux.checkRunning()
200 |
201 | return statusAction, nil
202 | }
203 |
204 | // Run - Run service
205 | func (linux *systemVRecord) Run(e Executable) (string, error) {
206 | runAction := "Running " + linux.description + ":"
207 | e.Run()
208 | return runAction + " completed.", nil
209 | }
210 |
211 | // GetTemplate - gets service config template
212 | func (linux *systemVRecord) GetTemplate() string {
213 | return systemVConfig
214 | }
215 |
216 | // SetTemplate - sets service config template
217 | func (linux *systemVRecord) SetTemplate(tplStr string) error {
218 | systemVConfig = tplStr
219 | return nil
220 | }
221 |
222 | var systemVConfig = `#! /bin/sh
223 | #
224 | # /etc/rc.d/init.d/{{.Name}}
225 | #
226 | # Starts {{.Name}} as a daemon
227 | #
228 | # chkconfig: 2345 87 17
229 | # description: Starts and stops a single {{.Name}} instance on this system
230 |
231 | ### BEGIN INIT INFO
232 | # Provides: {{.Name}}
233 | # Required-Start: $network $named
234 | # Required-Stop: $network $named
235 | # Default-Start: 2 3 4 5
236 | # Default-Stop: 0 1 6
237 | # Short-Description: This service manages the {{.Description}}.
238 | # Description: {{.Description}}
239 | ### END INIT INFO
240 |
241 | #
242 | # Source function library.
243 | #
244 | if [ -f /etc/rc.d/init.d/functions ]; then
245 | . /etc/rc.d/init.d/functions
246 | fi
247 |
248 | exec="{{.Path}}"
249 | servname="{{.Description}}"
250 |
251 | proc="{{.Name}}"
252 | pidfile="/var/run/$proc.pid"
253 | lockfile="/var/lock/subsys/$proc"
254 | stdoutlog="/var/log/$proc.log"
255 | stderrlog="/var/log/$proc.err"
256 |
257 | [ -d $(dirname $lockfile) ] || mkdir -p $(dirname $lockfile)
258 |
259 | [ -e /etc/sysconfig/$proc ] && . /etc/sysconfig/$proc
260 |
261 | start() {
262 | [ -x $exec ] || exit 5
263 |
264 | if [ -f $pidfile ]; then
265 | if ! [ -d "/proc/$(cat $pidfile)" ]; then
266 | rm $pidfile
267 | if [ -f $lockfile ]; then
268 | rm $lockfile
269 | fi
270 | fi
271 | fi
272 |
273 | if ! [ -f $pidfile ]; then
274 | printf "Starting $servname:\t"
275 | echo "$(date)" >> $stdoutlog
276 | $exec {{.Args}} >> $stdoutlog 2>> $stderrlog &
277 | echo $! > $pidfile
278 | touch $lockfile
279 | success
280 | echo
281 | else
282 | # failure
283 | echo
284 | printf "$pidfile still exists...\n"
285 | exit 7
286 | fi
287 | }
288 |
289 | stop() {
290 | echo -n $"Stopping $servname: "
291 | killproc -p $pidfile $proc
292 | retval=$?
293 | echo
294 | [ $retval -eq 0 ] && rm -f $lockfile
295 | return $retval
296 | }
297 |
298 | restart() {
299 | stop
300 | start
301 | }
302 |
303 | rh_status() {
304 | status -p $pidfile $proc
305 | }
306 |
307 | rh_status_q() {
308 | rh_status >/dev/null 2>&1
309 | }
310 |
311 | case "$1" in
312 | start)
313 | rh_status_q && exit 0
314 | $1
315 | ;;
316 | stop)
317 | rh_status_q || exit 0
318 | $1
319 | ;;
320 | restart)
321 | $1
322 | ;;
323 | status)
324 | rh_status
325 | ;;
326 | *)
327 | echo $"Usage: $0 {start|stop|status|restart}"
328 | exit 2
329 | esac
330 |
331 | exit $?
332 | `
333 |
--------------------------------------------------------------------------------
/daemon_linux_upstart.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 The Go Authors. All rights reserved.
2 | // Use of this source code is governed by
3 | // license that can be found in the LICENSE file.
4 |
5 | package daemon
6 |
7 | import (
8 | "os"
9 | "os/exec"
10 | "regexp"
11 | "strings"
12 | "text/template"
13 | )
14 |
15 | // upstartRecord - standard record (struct) for linux upstart version of daemon package
16 | type upstartRecord struct {
17 | name string
18 | description string
19 | kind Kind
20 | dependencies []string
21 | }
22 |
23 | // Standard service path for systemV daemons
24 | func (linux *upstartRecord) servicePath() string {
25 | return "/etc/init/" + linux.name + ".conf"
26 | }
27 |
28 | // Is a service installed
29 | func (linux *upstartRecord) isInstalled() bool {
30 |
31 | if _, err := os.Stat(linux.servicePath()); err == nil {
32 | return true
33 | }
34 |
35 | return false
36 | }
37 |
38 | // Check service is running
39 | func (linux *upstartRecord) checkRunning() (string, bool) {
40 | output, err := exec.Command("status", linux.name).Output()
41 | if err == nil {
42 | if matched, err := regexp.MatchString(linux.name+" start/running", string(output)); err == nil && matched {
43 | reg := regexp.MustCompile("process ([0-9]+)")
44 | data := reg.FindStringSubmatch(string(output))
45 | if len(data) > 1 {
46 | return "Service (pid " + data[1] + ") is running...", true
47 | }
48 | return "Service is running...", true
49 | }
50 | }
51 |
52 | return "Service is stopped", false
53 | }
54 |
55 | // Install the service
56 | func (linux *upstartRecord) Install(args ...string) (string, error) {
57 | installAction := "Install " + linux.description + ":"
58 |
59 | if ok, err := checkPrivileges(); !ok {
60 | return installAction + failed, err
61 | }
62 |
63 | srvPath := linux.servicePath()
64 |
65 | if linux.isInstalled() {
66 | return installAction + failed, ErrAlreadyInstalled
67 | }
68 |
69 | file, err := os.Create(srvPath)
70 | if err != nil {
71 | return installAction + failed, err
72 | }
73 | defer file.Close()
74 |
75 | execPatch, err := executablePath(linux.name)
76 | if err != nil {
77 | return installAction + failed, err
78 | }
79 |
80 | templ, err := template.New("upstatConfig").Parse(upstatConfig)
81 | if err != nil {
82 | return installAction + failed, err
83 | }
84 |
85 | if err := templ.Execute(
86 | file,
87 | &struct {
88 | Name, Description, Path, Args string
89 | }{linux.name, linux.description, execPatch, strings.Join(args, " ")},
90 | ); err != nil {
91 | return installAction + failed, err
92 | }
93 |
94 | if err := os.Chmod(srvPath, 0755); err != nil {
95 | return installAction + failed, err
96 | }
97 |
98 | return installAction + success, nil
99 | }
100 |
101 | // Remove the service
102 | func (linux *upstartRecord) Remove() (string, error) {
103 | removeAction := "Removing " + linux.description + ":"
104 |
105 | if ok, err := checkPrivileges(); !ok {
106 | return removeAction + failed, err
107 | }
108 |
109 | if !linux.isInstalled() {
110 | return removeAction + failed, ErrNotInstalled
111 | }
112 |
113 | if err := os.Remove(linux.servicePath()); err != nil {
114 | return removeAction + failed, err
115 | }
116 |
117 | return removeAction + success, nil
118 | }
119 |
120 | // Start the service
121 | func (linux *upstartRecord) Start() (string, error) {
122 | startAction := "Starting " + linux.description + ":"
123 |
124 | if ok, err := checkPrivileges(); !ok {
125 | return startAction + failed, err
126 | }
127 |
128 | if !linux.isInstalled() {
129 | return startAction + failed, ErrNotInstalled
130 | }
131 |
132 | if _, ok := linux.checkRunning(); ok {
133 | return startAction + failed, ErrAlreadyRunning
134 | }
135 |
136 | if err := exec.Command("start", linux.name).Run(); err != nil {
137 | return startAction + failed, err
138 | }
139 |
140 | return startAction + success, nil
141 | }
142 |
143 | // Stop the service
144 | func (linux *upstartRecord) Stop() (string, error) {
145 | stopAction := "Stopping " + linux.description + ":"
146 |
147 | if ok, err := checkPrivileges(); !ok {
148 | return stopAction + failed, err
149 | }
150 |
151 | if !linux.isInstalled() {
152 | return stopAction + failed, ErrNotInstalled
153 | }
154 |
155 | if _, ok := linux.checkRunning(); !ok {
156 | return stopAction + failed, ErrAlreadyStopped
157 | }
158 |
159 | if err := exec.Command("stop", linux.name).Run(); err != nil {
160 | return stopAction + failed, err
161 | }
162 |
163 | return stopAction + success, nil
164 | }
165 |
166 | // Status - Get service status
167 | func (linux *upstartRecord) Status() (string, error) {
168 |
169 | if ok, err := checkPrivileges(); !ok {
170 | return "", err
171 | }
172 |
173 | if !linux.isInstalled() {
174 | return statNotInstalled, ErrNotInstalled
175 | }
176 |
177 | statusAction, _ := linux.checkRunning()
178 |
179 | return statusAction, nil
180 | }
181 |
182 | // Run - Run service
183 | func (linux *upstartRecord) Run(e Executable) (string, error) {
184 | runAction := "Running " + linux.description + ":"
185 | e.Run()
186 | return runAction + " completed.", nil
187 | }
188 |
189 | // GetTemplate - gets service config template
190 | func (linux *upstartRecord) GetTemplate() string {
191 | return upstatConfig
192 | }
193 |
194 | // SetTemplate - sets service config template
195 | func (linux *upstartRecord) SetTemplate(tplStr string) error {
196 | upstatConfig = tplStr
197 | return nil
198 | }
199 |
200 | var upstatConfig = `# {{.Name}} {{.Description}}
201 |
202 | description "{{.Description}}"
203 | author "Pichu Chen "
204 |
205 | start on runlevel [2345]
206 | stop on runlevel [016]
207 |
208 | respawn
209 | #kill timeout 5
210 |
211 | exec {{.Path}} {{.Args}} >> /var/log/{{.Name}}.log 2>> /var/log/{{.Name}}.err
212 | `
213 |
--------------------------------------------------------------------------------
/daemon_windows.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 The Go Authors. All rights reserved.
2 | // Use of this source code is governed by
3 | // license that can be found in the LICENSE file.
4 |
5 | // Package daemon windows version
6 | package daemon
7 |
8 | import (
9 | "errors"
10 | "fmt"
11 | "os/exec"
12 | "strconv"
13 | "syscall"
14 | "time"
15 | "unicode/utf16"
16 | "unsafe"
17 |
18 | "golang.org/x/sys/windows/registry"
19 | "golang.org/x/sys/windows/svc"
20 | "golang.org/x/sys/windows/svc/mgr"
21 | )
22 |
23 | // windowsRecord - standard record (struct) for windows version of daemon package
24 | type windowsRecord struct {
25 | name string
26 | description string
27 | kind Kind
28 | dependencies []string
29 | }
30 |
31 | func newDaemon(name, description string, kind Kind, dependencies []string) (Daemon, error) {
32 |
33 | return &windowsRecord{name, description, kind, dependencies}, nil
34 | }
35 |
36 | // Install the service
37 | func (windows *windowsRecord) Install(args ...string) (string, error) {
38 | installAction := "Install " + windows.description + ":"
39 |
40 | execp, err := execPath()
41 |
42 | if err != nil {
43 | return installAction + failed, err
44 | }
45 |
46 | m, err := mgr.Connect()
47 | if err != nil {
48 | return installAction + failed, err
49 | }
50 | defer m.Disconnect()
51 |
52 | s, err := m.OpenService(windows.name)
53 | if err == nil {
54 | s.Close()
55 | return installAction + failed, ErrAlreadyRunning
56 | }
57 |
58 | s, err = m.CreateService(windows.name, execp, mgr.Config{
59 | DisplayName: windows.name,
60 | Description: windows.description,
61 | StartType: mgr.StartAutomatic,
62 | Dependencies: windows.dependencies,
63 | }, args...)
64 | if err != nil {
65 | return installAction + failed, err
66 | }
67 | defer s.Close()
68 |
69 | // set recovery action for service
70 | // restart after 5 seconds for the first 3 times
71 | // restart after 1 minute, otherwise
72 | r := []mgr.RecoveryAction{
73 | mgr.RecoveryAction{
74 | Type: mgr.ServiceRestart,
75 | Delay: 5000 * time.Millisecond,
76 | },
77 | mgr.RecoveryAction{
78 | Type: mgr.ServiceRestart,
79 | Delay: 5000 * time.Millisecond,
80 | },
81 | mgr.RecoveryAction{
82 | Type: mgr.ServiceRestart,
83 | Delay: 5000 * time.Millisecond,
84 | },
85 | mgr.RecoveryAction{
86 | Type: mgr.ServiceRestart,
87 | Delay: 60000 * time.Millisecond,
88 | },
89 | }
90 | // set reset period as a day
91 | s.SetRecoveryActions(r, uint32(86400))
92 |
93 | return installAction + " completed.", nil
94 | }
95 |
96 | // Remove the service
97 | func (windows *windowsRecord) Remove() (string, error) {
98 | removeAction := "Removing " + windows.description + ":"
99 |
100 | m, err := mgr.Connect()
101 | if err != nil {
102 | return removeAction + failed, getWindowsError(err)
103 | }
104 | defer m.Disconnect()
105 | s, err := m.OpenService(windows.name)
106 | if err != nil {
107 | return removeAction + failed, getWindowsError(err)
108 | }
109 | defer s.Close()
110 | err = s.Delete()
111 | if err != nil {
112 | return removeAction + failed, getWindowsError(err)
113 | }
114 |
115 | return removeAction + " completed.", nil
116 | }
117 |
118 | // Start the service
119 | func (windows *windowsRecord) Start() (string, error) {
120 | startAction := "Starting " + windows.description + ":"
121 |
122 | m, err := mgr.Connect()
123 | if err != nil {
124 | return startAction + failed, getWindowsError(err)
125 | }
126 | defer m.Disconnect()
127 | s, err := m.OpenService(windows.name)
128 | if err != nil {
129 | return startAction + failed, getWindowsError(err)
130 | }
131 | defer s.Close()
132 | if err = s.Start(); err != nil {
133 | return startAction + failed, getWindowsError(err)
134 | }
135 |
136 | return startAction + " completed.", nil
137 | }
138 |
139 | // Stop the service
140 | func (windows *windowsRecord) Stop() (string, error) {
141 | stopAction := "Stopping " + windows.description + ":"
142 |
143 | m, err := mgr.Connect()
144 | if err != nil {
145 | return stopAction + failed, getWindowsError(err)
146 | }
147 | defer m.Disconnect()
148 | s, err := m.OpenService(windows.name)
149 | if err != nil {
150 | return stopAction + failed, getWindowsError(err)
151 | }
152 | defer s.Close()
153 | if err := stopAndWait(s); err != nil {
154 | return stopAction + failed, getWindowsError(err)
155 | }
156 |
157 | return stopAction + " completed.", nil
158 | }
159 |
160 | func stopAndWait(s *mgr.Service) error {
161 | // First stop the service. Then wait for the service to
162 | // actually stop before starting it.
163 | status, err := s.Control(svc.Stop)
164 | if err != nil {
165 | return err
166 | }
167 |
168 | timeDuration := time.Millisecond * 50
169 |
170 | timeout := time.After(getStopTimeout() + (timeDuration * 2))
171 | tick := time.NewTicker(timeDuration)
172 | defer tick.Stop()
173 |
174 | for status.State != svc.Stopped {
175 | select {
176 | case <-tick.C:
177 | status, err = s.Query()
178 | if err != nil {
179 | return err
180 | }
181 | case <-timeout:
182 | break
183 | }
184 | }
185 | return nil
186 | }
187 |
188 | func getStopTimeout() time.Duration {
189 | // For default and paths see https://support.microsoft.com/en-us/kb/146092
190 | defaultTimeout := time.Millisecond * 20000
191 | key, err := registry.OpenKey(registry.LOCAL_MACHINE, `SYSTEM\CurrentControlSet\Control`, registry.READ)
192 | if err != nil {
193 | return defaultTimeout
194 | }
195 | sv, _, err := key.GetStringValue("WaitToKillServiceTimeout")
196 | if err != nil {
197 | return defaultTimeout
198 | }
199 | v, err := strconv.Atoi(sv)
200 | if err != nil {
201 | return defaultTimeout
202 | }
203 | return time.Millisecond * time.Duration(v)
204 | }
205 |
206 | // Status - Get service status
207 | func (windows *windowsRecord) Status() (string, error) {
208 | m, err := mgr.Connect()
209 | if err != nil {
210 | return "Getting status:" + failed, getWindowsError(err)
211 | }
212 | defer m.Disconnect()
213 | s, err := m.OpenService(windows.name)
214 | if err != nil {
215 | return "Getting status:" + failed, getWindowsError(err)
216 | }
217 | defer s.Close()
218 | status, err := s.Query()
219 | if err != nil {
220 | return "Getting status:" + failed, getWindowsError(err)
221 | }
222 |
223 | return "Status: " + getWindowsServiceStateFromUint32(status.State), nil
224 | }
225 |
226 | // Get executable path
227 | func execPath() (string, error) {
228 | var n uint32
229 | b := make([]uint16, syscall.MAX_PATH)
230 | size := uint32(len(b))
231 |
232 | r0, _, e1 := syscall.MustLoadDLL(
233 | "kernel32.dll",
234 | ).MustFindProc(
235 | "GetModuleFileNameW",
236 | ).Call(0, uintptr(unsafe.Pointer(&b[0])), uintptr(size))
237 | n = uint32(r0)
238 | if n == 0 {
239 | return "", e1
240 | }
241 | return string(utf16.Decode(b[0:n])), nil
242 | }
243 |
244 | // Get windows error
245 | func getWindowsError(inputError error) error {
246 | if exiterr, ok := inputError.(*exec.ExitError); ok {
247 | if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
248 | if sysErr, ok := WinErrCode[status.ExitStatus()]; ok {
249 | return errors.New(fmt.Sprintf("\n %s: %s \n %s", sysErr.Title, sysErr.Description, sysErr.Action))
250 | }
251 | }
252 | }
253 |
254 | return inputError
255 | }
256 |
257 | // Get windows service state
258 | func getWindowsServiceStateFromUint32(state svc.State) string {
259 | switch state {
260 | case svc.Stopped:
261 | return "SERVICE_STOPPED"
262 | case svc.StartPending:
263 | return "SERVICE_START_PENDING"
264 | case svc.StopPending:
265 | return "SERVICE_STOP_PENDING"
266 | case svc.Running:
267 | return "SERVICE_RUNNING"
268 | case svc.ContinuePending:
269 | return "SERVICE_CONTINUE_PENDING"
270 | case svc.PausePending:
271 | return "SERVICE_PAUSE_PENDING"
272 | case svc.Paused:
273 | return "SERVICE_PAUSED"
274 | }
275 | return "SERVICE_UNKNOWN"
276 | }
277 |
278 | type serviceHandler struct {
279 | executable Executable
280 | }
281 |
282 | func (sh *serviceHandler) Execute(args []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (ssec bool, errno uint32) {
283 | const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown | svc.AcceptPauseAndContinue
284 | changes <- svc.Status{State: svc.StartPending}
285 |
286 | fasttick := time.Tick(500 * time.Millisecond)
287 | slowtick := time.Tick(2 * time.Second)
288 | tick := fasttick
289 |
290 | sh.executable.Start()
291 | changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted}
292 |
293 | loop:
294 | for {
295 | select {
296 | case <-tick:
297 | break
298 | case c := <-r:
299 | switch c.Cmd {
300 | case svc.Interrogate:
301 | changes <- c.CurrentStatus
302 | // Testing deadlock from https://code.google.com/p/winsvc/issues/detail?id=4
303 | time.Sleep(100 * time.Millisecond)
304 | changes <- c.CurrentStatus
305 | case svc.Stop, svc.Shutdown:
306 | changes <- svc.Status{State: svc.StopPending}
307 | sh.executable.Stop()
308 | break loop
309 | case svc.Pause:
310 | changes <- svc.Status{State: svc.Paused, Accepts: cmdsAccepted}
311 | tick = slowtick
312 | case svc.Continue:
313 | changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted}
314 | tick = fasttick
315 | default:
316 | continue loop
317 | }
318 | }
319 | }
320 | return
321 | }
322 |
323 | func (windows *windowsRecord) Run(e Executable) (string, error) {
324 | runAction := "Running " + windows.description + ":"
325 |
326 | interactive, err := svc.IsAnInteractiveSession()
327 | if err != nil {
328 | return runAction + failed, getWindowsError(err)
329 | }
330 | if !interactive {
331 | // service called from windows service manager
332 | // use API provided by golang.org/x/sys/windows
333 | err = svc.Run(windows.name, &serviceHandler{
334 | executable: e,
335 | })
336 | if err != nil {
337 | return runAction + failed, getWindowsError(err)
338 | }
339 | } else {
340 | // otherwise, service should be called from terminal session
341 | e.Run()
342 | }
343 |
344 | return runAction + " completed.", nil
345 | }
346 |
347 | // GetTemplate - gets service config template
348 | func (linux *windowsRecord) GetTemplate() string {
349 | return ""
350 | }
351 |
352 | // SetTemplate - sets service config template
353 | func (linux *windowsRecord) SetTemplate(tplStr string) error {
354 | return errors.New(fmt.Sprintf("templating is not supported for windows"))
355 | }
356 |
--------------------------------------------------------------------------------
/examples/cron/cron_job.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "log"
6 | "os"
7 | "os/signal"
8 | "syscall"
9 | "time"
10 |
11 | "github.com/robfig/cron"
12 | "github.com/takama/daemon"
13 | )
14 |
15 | const (
16 | // name of the service
17 | name = "cron_job"
18 | description = "Cron job service example"
19 | )
20 |
21 | var stdlog, errlog *log.Logger
22 |
23 | // Service is the daemon service struct
24 | type Service struct {
25 | daemon.Daemon
26 | }
27 |
28 | func makeFile() {
29 | // create a simple file (current time).txt
30 | f, err := os.Create(fmt.Sprintf("%s/%s.txt", os.TempDir(), time.Now().Format(time.RFC3339)))
31 | if err != nil {
32 | log.Fatal(err)
33 | }
34 | defer f.Close()
35 | }
36 |
37 | // Manage by daemon commands or run the daemon
38 | func (service *Service) Manage() (string, error) {
39 |
40 | usage := "Usage: cron_job install | remove | start | stop | status"
41 | // If received any kind of command, do it
42 | if len(os.Args) > 1 {
43 | command := os.Args[1]
44 | switch command {
45 | case "install":
46 | return service.Install()
47 | case "remove":
48 | return service.Remove()
49 | case "start":
50 | return service.Start()
51 | case "stop":
52 | // No need to explicitly stop cron since job will be killed
53 | return service.Stop()
54 | case "status":
55 | return service.Status()
56 | default:
57 | return usage, nil
58 | }
59 | }
60 | // Set up channel on which to send signal notifications.
61 | // We must use a buffered channel or risk missing the signal
62 | // if we're not ready to receive when the signal is sent.
63 | interrupt := make(chan os.Signal, 1)
64 | signal.Notify(interrupt, os.Interrupt, os.Kill, syscall.SIGTERM)
65 |
66 | // Create a new cron manager
67 | c := cron.New()
68 | // Run makefile every min
69 | c.AddFunc("* * * * * *", makeFile)
70 | c.Start()
71 | // Waiting for interrupt by system signal
72 | killSignal := <-interrupt
73 | stdlog.Println("Got signal:", killSignal)
74 | return "Service exited", nil
75 | }
76 |
77 | func init() {
78 | stdlog = log.New(os.Stdout, "", log.Ldate|log.Ltime)
79 | errlog = log.New(os.Stderr, "", log.Ldate|log.Ltime)
80 | }
81 |
82 | func main() {
83 | srv, err := daemon.New(name, description, daemon.SystemDaemon)
84 | if err != nil {
85 | errlog.Println("Error: ", err)
86 | os.Exit(1)
87 | }
88 | service := &Service{srv}
89 | status, err := service.Manage()
90 | if err != nil {
91 | errlog.Println(status, "\nError: ", err)
92 | os.Exit(1)
93 | }
94 | fmt.Println(status)
95 | }
96 |
--------------------------------------------------------------------------------
/examples/myservice.go:
--------------------------------------------------------------------------------
1 | // Example of a daemon with echo service
2 | package main
3 |
4 | import (
5 | "fmt"
6 | "log"
7 | "net"
8 | "os"
9 | "os/signal"
10 | "syscall"
11 |
12 | "github.com/takama/daemon"
13 | )
14 |
15 | const (
16 |
17 | // name of the service
18 | name = "myservice"
19 | description = "My Echo Service"
20 |
21 | // port which daemon should be listen
22 | port = ":9977"
23 | )
24 |
25 | // dependencies that are NOT required by the service, but might be used
26 | var dependencies = []string{"dummy.service"}
27 |
28 | var stdlog, errlog *log.Logger
29 |
30 | // Service has embedded daemon
31 | type Service struct {
32 | daemon.Daemon
33 | }
34 |
35 | // Manage by daemon commands or run the daemon
36 | func (service *Service) Manage() (string, error) {
37 |
38 | usage := "Usage: myservice install | remove | start | stop | status"
39 |
40 | // if received any kind of command, do it
41 | if len(os.Args) > 1 {
42 | command := os.Args[1]
43 | switch command {
44 | case "install":
45 | return service.Install()
46 | case "remove":
47 | return service.Remove()
48 | case "start":
49 | return service.Start()
50 | case "stop":
51 | return service.Stop()
52 | case "status":
53 | return service.Status()
54 | default:
55 | return usage, nil
56 | }
57 | }
58 |
59 | // Do something, call your goroutines, etc
60 |
61 | // Set up channel on which to send signal notifications.
62 | // We must use a buffered channel or risk missing the signal
63 | // if we're not ready to receive when the signal is sent.
64 | interrupt := make(chan os.Signal, 1)
65 | signal.Notify(interrupt, os.Interrupt, os.Kill, syscall.SIGTERM)
66 |
67 | // Set up listener for defined host and port
68 | listener, err := net.Listen("tcp", port)
69 | if err != nil {
70 | return "Possibly was a problem with the port binding", err
71 | }
72 |
73 | // set up channel on which to send accepted connections
74 | listen := make(chan net.Conn, 100)
75 | go acceptConnection(listener, listen)
76 |
77 | // loop work cycle with accept connections or interrupt
78 | // by system signal
79 | for {
80 | select {
81 | case conn := <-listen:
82 | go handleClient(conn)
83 | case killSignal := <-interrupt:
84 | stdlog.Println("Got signal:", killSignal)
85 | stdlog.Println("Stoping listening on ", listener.Addr())
86 | listener.Close()
87 | if killSignal == os.Interrupt {
88 | return "Daemon was interrupted by system signal", nil
89 | }
90 | return "Daemon was killed", nil
91 | }
92 | }
93 | }
94 |
95 | // Accept a client connection and collect it in a channel
96 | func acceptConnection(listener net.Listener, listen chan<- net.Conn) {
97 | for {
98 | conn, err := listener.Accept()
99 | if err != nil {
100 | continue
101 | }
102 | listen <- conn
103 | }
104 | }
105 |
106 | func handleClient(client net.Conn) {
107 | for {
108 | buf := make([]byte, 4096)
109 | numbytes, err := client.Read(buf)
110 | if numbytes == 0 || err != nil {
111 | return
112 | }
113 | client.Write(buf[:numbytes])
114 | }
115 | }
116 |
117 | func init() {
118 | stdlog = log.New(os.Stdout, "", 0)
119 | errlog = log.New(os.Stderr, "", 0)
120 | }
121 |
122 | func main() {
123 | srv, err := daemon.New(name, description, daemon.SystemDaemon, dependencies...)
124 | if err != nil {
125 | errlog.Println("Error: ", err)
126 | os.Exit(1)
127 | }
128 | service := &Service{srv}
129 | status, err := service.Manage()
130 | if err != nil {
131 | errlog.Println(status, "\nError: ", err)
132 | os.Exit(1)
133 | }
134 | fmt.Println(status)
135 |
136 | }
137 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/takama/daemon
2 |
3 | go 1.14
4 |
5 | require (
6 | github.com/robfig/cron v1.2.0
7 | golang.org/x/sys v0.0.0-20200722175500-76b94024e4b6
8 | )
9 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ=
2 | github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k=
3 | golang.org/x/sys v0.0.0-20200722175500-76b94024e4b6 h1:X9xIZ1YU8bLZA3l6gqDUHSFiD0GFI9S548h6C8nDtOY=
4 | golang.org/x/sys v0.0.0-20200722175500-76b94024e4b6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
5 |
--------------------------------------------------------------------------------
/helper.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 The Go Authors. All rights reserved.
2 | // Use of this source code is governed by
3 | // license that can be found in the LICENSE file.
4 |
5 | //+build go1.8
6 |
7 | package daemon
8 |
9 | import (
10 | "errors"
11 | "os"
12 | "os/exec"
13 | "strconv"
14 | "strings"
15 | )
16 |
17 | // Service constants
18 | const (
19 | success = "\t\t\t\t\t[ \033[32mOK\033[0m ]" // Show colored "OK"
20 | failed = "\t\t\t\t\t[\033[31mFAILED\033[0m]" // Show colored "FAILED"
21 | )
22 |
23 | var (
24 | // ErrUnsupportedSystem appears if try to use service on system which is not supported by this release
25 | ErrUnsupportedSystem = errors.New("Unsupported system")
26 |
27 | // ErrRootPrivileges appears if run installation or deleting the service without root privileges
28 | ErrRootPrivileges = errors.New("You must have root user privileges. Possibly using 'sudo' command should help")
29 |
30 | // ErrAlreadyInstalled appears if service already installed on the system
31 | ErrAlreadyInstalled = errors.New("Service has already been installed")
32 |
33 | // ErrNotInstalled appears if try to delete service which was not been installed
34 | ErrNotInstalled = errors.New("Service is not installed")
35 |
36 | // ErrAlreadyRunning appears if try to start already running service
37 | ErrAlreadyRunning = errors.New("Service is already running")
38 |
39 | // ErrAlreadyStopped appears if try to stop already stopped service
40 | ErrAlreadyStopped = errors.New("Service has already been stopped")
41 | )
42 |
43 | // ExecPath tries to get executable path
44 | func ExecPath() (string, error) {
45 | return os.Executable()
46 | }
47 |
48 | // Lookup path for executable file
49 | func executablePath(name string) (string, error) {
50 | if path, err := exec.LookPath(name); err == nil {
51 | if _, err := os.Stat(path); err == nil {
52 | return path, nil
53 | }
54 | }
55 | return os.Executable()
56 | }
57 |
58 | // Check root rights to use system service
59 | func checkPrivileges() (bool, error) {
60 |
61 | if output, err := exec.Command("id", "-g").Output(); err == nil {
62 | if gid, parseErr := strconv.ParseUint(strings.TrimSpace(string(output)), 10, 32); parseErr == nil {
63 | if gid == 0 {
64 | return true, nil
65 | }
66 | return false, ErrRootPrivileges
67 | }
68 | }
69 | return false, ErrUnsupportedSystem
70 | }
71 |
--------------------------------------------------------------------------------
/helper_legacy.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 The Go Authors. All rights reserved.
2 | // Use of this source code is governed by
3 | // license that can be found in the LICENSE file.
4 |
5 | //+build !go1.8
6 |
7 | package daemon
8 |
9 | import (
10 | "errors"
11 | "os"
12 | "os/exec"
13 | "strconv"
14 | "strings"
15 | )
16 |
17 | // Service constants
18 | const (
19 | success = "\t\t\t\t\t[ \033[32mOK\033[0m ]" // Show colored "OK"
20 | failed = "\t\t\t\t\t[\033[31mFAILED\033[0m]" // Show colored "FAILED"
21 | )
22 |
23 | var (
24 | // ErrUnsupportedSystem appears if try to use service on system which is not supported by this release
25 | ErrUnsupportedSystem = errors.New("Unsupported system")
26 |
27 | // ErrRootPrivileges appears if run installation or deleting the service without root privileges
28 | ErrRootPrivileges = errors.New("You must have root user privileges. Possibly using 'sudo' command should help")
29 |
30 | // ErrAlreadyInstalled appears if service already installed on the system
31 | ErrAlreadyInstalled = errors.New("Service has already been installed")
32 |
33 | // ErrNotInstalled appears if try to delete service which was not been installed
34 | ErrNotInstalled = errors.New("Service is not installed")
35 |
36 | // ErrAlreadyRunning appears if try to start already running service
37 | ErrAlreadyRunning = errors.New("Service is already running")
38 |
39 | // ErrAlreadyStopped appears if try to stop already stopped service
40 | ErrAlreadyStopped = errors.New("Service has already been stopped")
41 | )
42 |
43 | // ExecPath tries to get executable path
44 | func ExecPath() (string, error) {
45 | return execPath()
46 | }
47 |
48 | // Lookup path for executable file
49 | func executablePath(name string) (string, error) {
50 | if path, err := exec.LookPath(name); err == nil {
51 | if _, err := os.Stat(path); err == nil {
52 | return path, nil
53 | }
54 | }
55 | return execPath()
56 | }
57 |
58 | // Check root rights to use system service
59 | func checkPrivileges() (bool, error) {
60 |
61 | if output, err := exec.Command("id", "-g").Output(); err == nil {
62 | if gid, parseErr := strconv.ParseUint(strings.TrimSpace(string(output)), 10, 32); parseErr == nil {
63 | if gid == 0 {
64 | return true, nil
65 | }
66 | return false, ErrRootPrivileges
67 | }
68 | }
69 | return false, ErrUnsupportedSystem
70 | }
71 |
--------------------------------------------------------------------------------
/helper_windows.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 The Go Authors. All rights reserved.
2 | // Use of this source code is governed by
3 | // license that can be found in the LICENSE file.
4 |
5 | package daemon
6 |
7 | // SystemError contains error description and corresponded action helper to fix it
8 | type SystemError struct {
9 | Title string
10 | Description string
11 | Action string
12 | }
13 |
14 | var (
15 | // WinErrCode - List of system errors from Microsoft source:
16 | // https://msdn.microsoft.com/en-us/library/windows/desktop/ms681385(v=vs.85).aspx
17 | WinErrCode = map[int]SystemError{
18 | 5: SystemError{
19 | Title: "ERROR_ACCESS_DENIED",
20 | Description: "Access denied.",
21 | Action: "Administrator access is needed to install a service.",
22 | },
23 | 1051: SystemError{
24 | Title: "ERROR_DEPENDENT_SERVICES_RUNNING",
25 | Description: "A stop control has been sent to a service that other running services are dependent on.",
26 | },
27 | 1052: SystemError{
28 | Title: "ERROR_INVALID_SERVICE_CONTROL",
29 | Description: "The requested control is not valid for this service.",
30 | },
31 | 1053: SystemError{
32 | Title: "ERROR_SERVICE_REQUEST_TIMEOUT",
33 | Description: "The service did not respond to the start or control request in a timely fashion.",
34 | },
35 | 1054: SystemError{
36 | Title: "ERROR_SERVICE_NO_THREAD",
37 | Description: "A thread could not be created for the service.",
38 | },
39 | 1055: SystemError{
40 | Title: "ERROR_SERVICE_DATABASE_LOCKED",
41 | Description: "The service database is locked.",
42 | },
43 | 1056: SystemError{
44 | Title: "ERROR_SERVICE_ALREADY_RUNNING",
45 | Description: "An instance of the service is already running.",
46 | },
47 | 1057: SystemError{
48 | Title: "ERROR_INVALID_SERVICE_ACCOUNT",
49 | Description: "The account name is invalid or does not exist, or the password is invalid for the account name specified.",
50 | },
51 | 1058: SystemError{
52 | Title: "ERROR_SERVICE_DISABLED",
53 | Description: "The service cannot be started, either because it is disabled or because it has no enabled devices associated with it.",
54 | },
55 | 1060: SystemError{
56 | Title: "ERROR_SERVICE_DOES_NOT_EXIST",
57 | Description: "The specified service does not exist as an installed service.",
58 | },
59 | 1061: SystemError{
60 | Title: "ERROR_SERVICE_CANNOT_ACCEPT_CTRL",
61 | Description: "The service cannot accept control messages at this time.",
62 | },
63 | 1062: SystemError{
64 | Title: "ERROR_SERVICE_NOT_ACTIVE",
65 | Description: "The service has not been started.",
66 | },
67 | 1063: SystemError{
68 | Title: "ERROR_FAILED_SERVICE_CONTROLLER_CONNECT",
69 | Description: "The service process could not connect to the service controller.",
70 | },
71 | 1064: SystemError{
72 | Title: "ERROR_EXCEPTION_IN_SERVICE",
73 | Description: "An exception occurred in the service when handling the control request.",
74 | },
75 | 1066: SystemError{
76 | Title: "ERROR_SERVICE_SPECIFIC_ERROR",
77 | Description: "The service has returned a service-specific error code.",
78 | },
79 | 1068: SystemError{
80 | Title: "ERROR_SERVICE_DEPENDENCY_FAIL",
81 | Description: "The dependency service or group failed to start.",
82 | },
83 | 1069: SystemError{
84 | Title: "ERROR_SERVICE_LOGON_FAILED",
85 | Description: "The service did not start due to a logon failure.",
86 | },
87 | 1070: SystemError{
88 | Title: "ERROR_SERVICE_START_HANG",
89 | Description: "After starting, the service hung in a start-pending state.",
90 | },
91 | 1071: SystemError{
92 | Title: "ERROR_INVALID_SERVICE_LOCK",
93 | Description: "The specified service database lock is invalid.",
94 | },
95 | 1072: SystemError{
96 | Title: "ERROR_SERVICE_MARKED_FOR_DELETE",
97 | Description: "The specified service has been marked for deletion.",
98 | },
99 | 1073: SystemError{
100 | Title: "ERROR_SERVICE_EXISTS",
101 | Description: "The specified service already exists.",
102 | },
103 | 1075: SystemError{
104 | Title: "ERROR_SERVICE_DEPENDENCY_DELETED",
105 | Description: "The dependency service does not exist or has been marked for deletion.",
106 | },
107 | 1077: SystemError{
108 | Title: "ERROR_SERVICE_NEVER_STARTED",
109 | Description: "No attempts to start the service have been made since the last boot.",
110 | },
111 | 1078: SystemError{
112 | Title: "ERROR_DUPLICATE_SERVICE_NAME",
113 | Description: "The name is already in use as either a service name or a service display name.",
114 | },
115 | 1079: SystemError{
116 | Title: "ERROR_DIFFERENT_SERVICE_ACCOUNT",
117 | Description: "The account specified for this service is different from the account specified for other services running in the same process.",
118 | },
119 | 1083: SystemError{
120 | Title: "ERROR_SERVICE_NOT_IN_EXE",
121 | Description: "The executable program that this service is configured to run in does not implement the service.",
122 | },
123 | 1084: SystemError{
124 | Title: "ERROR_NOT_SAFEBOOT_SERVICE",
125 | Description: "This service cannot be started in Safe Mode.",
126 | },
127 | }
128 | )
129 |
--------------------------------------------------------------------------------