-
Notifications
You must be signed in to change notification settings - Fork 292
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
765 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
.DS_Store |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
// Copyright 2014 Igor Dolzhikov. All rights reserved. | ||
// Use of this source code is governed by a BSD-style | ||
// license that can be found in the LICENSE file. | ||
|
||
// Package daemon 0.1.0 for use with Go (golang) services. | ||
// | ||
// Package daemon provides primitives for daemonization of golang services. | ||
// This package is not provide implementation of user daemon, | ||
// accordingly must have root rights to install/remove service. | ||
// In the current implementation is only supported Linux and Mac Os X daemon. | ||
// | ||
// Example: | ||
// | ||
// package main | ||
// | ||
// import ( | ||
// "fmt" | ||
// "os" | ||
// "github.com/takama/daemon" | ||
// ) | ||
// | ||
// const ( | ||
// name = "myservice" | ||
// description = "Some explanation of the service purpose" | ||
// ) | ||
// | ||
// type Service struct { | ||
// daemon.Daemon | ||
// } | ||
// | ||
// func (service *Service) Manage() (string, error) { | ||
// // if received any kind of command, do it | ||
// if len(os.Args) > 1 { | ||
// command := os.Args[1] | ||
// switch command { | ||
// case "install": | ||
// return service.Install() | ||
// case "remove": | ||
// return service.Remove() | ||
// case "start": | ||
// return service.Start() | ||
// case "stop": | ||
// return service.Stop() | ||
// case "status": | ||
// return service.Status() | ||
// } | ||
// } | ||
// | ||
// // Do something, call your goroutines, etc | ||
// | ||
// return "Usage: myservice install | remove | start | stop | status", nil | ||
// } | ||
// | ||
// func main() { | ||
// srv, err := daemon.New(name, description) | ||
// if err != nil { | ||
// fmt.Println("Error: ", err) | ||
// os.Exit(1) | ||
// } | ||
// service := &Service{srv} | ||
// status, err := service.Manage() | ||
// if err != nil { | ||
// fmt.Println(status, "\nError: ", err) | ||
// os.Exit(1) | ||
// } | ||
// fmt.Println(status) | ||
// } | ||
// | ||
// Go daemon | ||
package daemon | ||
|
||
import ( | ||
"os" | ||
"os/exec" | ||
"os/user" | ||
) | ||
|
||
// Service constants | ||
const ( | ||
rootPrivileges = "You must have root user privileges. Possibly using 'sudo' command should help" | ||
success = "\t\t\t\t\t[ \033[32mOK\033[0m ]" // Show colored "OK" | ||
failed = "\t\t\t\t\t[\033[31mFAILED\033[0m]" // Show colored "FAILED" | ||
) | ||
|
||
// Daemon interface has standard set of a methods/commands | ||
type Daemon interface { | ||
|
||
// Install the service into the system | ||
Install() (string, error) | ||
|
||
// Remove the service and all corresponded files from the system | ||
Remove() (string, error) | ||
|
||
// Start the service | ||
Start() (string, error) | ||
|
||
// Stop the service | ||
Stop() (string, error) | ||
|
||
// Status - check the service status | ||
Status() (string, error) | ||
} | ||
|
||
// New - Create a new daemon | ||
// | ||
// name: name ot the service, must be match with executable file name; | ||
// description: any explanation, what is the service, its purpose | ||
func New(name, description string) (Daemon, error) { | ||
return newDaemon(name, description) | ||
} | ||
|
||
// Lookup path for executable file | ||
func executablePath(name string) (string, error) { | ||
if path, err := exec.LookPath(name); err == nil { | ||
_, err := os.Stat(path) | ||
if os.IsNotExist(err) { | ||
return execPath() | ||
} | ||
return path, nil | ||
} | ||
return execPath() | ||
} | ||
|
||
// Check root rights to use system service | ||
func checkPrivileges() bool { | ||
|
||
if user, err := user.Current(); err == nil && user.Gid == "0" { | ||
return true | ||
} | ||
return false | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,210 @@ | ||
// Copyright 2014 Igor Dolzhikov. All rights reserved. | ||
// Use of this source code is governed by a BSD-style | ||
// license that can be found in the LICENSE file. | ||
|
||
// Package daemon darwin (mac os x) version | ||
package daemon | ||
|
||
import ( | ||
"errors" | ||
"os" | ||
"os/exec" | ||
"path/filepath" | ||
"regexp" | ||
"text/template" | ||
) | ||
|
||
// DarwinRecord - standard record (struct) for darwin version of daemon package | ||
type DarwinRecord struct { | ||
name string | ||
description string | ||
} | ||
|
||
func newDaemon(name, description string) (*DarwinRecord, error) { | ||
|
||
return &DarwinRecord{name, description}, nil | ||
} | ||
|
||
// Standard service path for system daemons | ||
func (darwin *DarwinRecord) servicePath() string { | ||
return "/Library/LaunchDaemons/" + darwin.name + ".plist" | ||
} | ||
|
||
// Check service is installed | ||
func (darwin *DarwinRecord) checkInstalled() bool { | ||
|
||
if _, err := os.Stat(darwin.servicePath()); err == nil { | ||
return true | ||
} | ||
|
||
return false | ||
} | ||
|
||
// Get executable path | ||
func execPath() (string, error) { | ||
return filepath.Abs(os.Args[0]) | ||
} | ||
|
||
// Check service is running | ||
func (darwin *DarwinRecord) checkRunning() (string, bool) { | ||
output, err := exec.Command("launchctl", "list", darwin.name).Output() | ||
if err == nil { | ||
if matched, err := regexp.MatchString(darwin.name, string(output)); err == nil && matched { | ||
reg := regexp.MustCompile("PID\" = ([0-9]+);") | ||
data := reg.FindStringSubmatch(string(output)) | ||
if len(data) > 1 { | ||
return "Service (pid " + data[1] + ") is running...", true | ||
} | ||
return "Service is running...", true | ||
} | ||
} | ||
|
||
return "Service is stoped", false | ||
} | ||
|
||
// Install the service | ||
func (darwin *DarwinRecord) Install() (string, error) { | ||
installAction := "Install " + darwin.description + ":" | ||
|
||
if checkPrivileges() == false { | ||
return installAction + failed, errors.New(rootPrivileges) | ||
} | ||
|
||
srvPath := darwin.servicePath() | ||
|
||
if darwin.checkInstalled() == true { | ||
return installAction + failed, errors.New(darwin.description + " already installed") | ||
} | ||
|
||
file, err := os.Create(srvPath) | ||
if err != nil { | ||
return installAction + failed, err | ||
} | ||
defer file.Close() | ||
|
||
execPatch, err := executablePath(darwin.name) | ||
if err != nil { | ||
return installAction + failed, err | ||
} | ||
|
||
templ, err := template.New("propertyList").Parse(propertyList) | ||
if err != nil { | ||
return installAction + failed, err | ||
} | ||
|
||
if err := templ.Execute( | ||
file, | ||
&struct { | ||
Name, Path string | ||
}{darwin.name, execPatch}, | ||
); err != nil { | ||
return installAction + failed, err | ||
} | ||
|
||
return installAction + success, nil | ||
} | ||
|
||
// Remove the service | ||
func (darwin *DarwinRecord) Remove() (string, error) { | ||
removeAction := "Removing " + darwin.description + ":" | ||
|
||
if checkPrivileges() == false { | ||
return removeAction + failed, errors.New(rootPrivileges) | ||
} | ||
|
||
if darwin.checkInstalled() == false { | ||
return removeAction + failed, errors.New(darwin.description + " not installed") | ||
} | ||
|
||
if err := os.Remove(darwin.servicePath()); err != nil { | ||
return removeAction + failed, err | ||
} | ||
|
||
return removeAction + success, nil | ||
} | ||
|
||
// Start the service | ||
func (darwin *DarwinRecord) Start() (string, error) { | ||
startAction := "Starting " + darwin.description + ":" | ||
|
||
if checkPrivileges() == false { | ||
return startAction + failed, errors.New(rootPrivileges) | ||
} | ||
|
||
if darwin.checkInstalled() == false { | ||
return startAction + failed, errors.New(darwin.description + " not installed") | ||
} | ||
|
||
if _, status := darwin.checkRunning(); status == true { | ||
return startAction + failed, errors.New("service already running") | ||
} | ||
|
||
if err := exec.Command("launchctl", "load", darwin.servicePath()).Run(); err != nil { | ||
return startAction + failed, err | ||
} | ||
|
||
return startAction + success, nil | ||
} | ||
|
||
// Stop the service | ||
func (darwin *DarwinRecord) Stop() (string, error) { | ||
stopAction := "Stopping " + darwin.description + ":" | ||
|
||
if checkPrivileges() == false { | ||
return stopAction + failed, errors.New(rootPrivileges) | ||
} | ||
|
||
if darwin.checkInstalled() == false { | ||
return stopAction + failed, errors.New(darwin.description + " not installed") | ||
} | ||
|
||
if _, status := darwin.checkRunning(); status == false { | ||
return stopAction + failed, errors.New("service already stopped") | ||
} | ||
|
||
if err := exec.Command("launchctl", "unload", darwin.servicePath()).Run(); err != nil { | ||
return stopAction + failed, err | ||
} | ||
|
||
return stopAction + success, nil | ||
} | ||
|
||
// Status - Get service status | ||
func (darwin *DarwinRecord) Status() (string, error) { | ||
|
||
if checkPrivileges() == false { | ||
return "", errors.New(rootPrivileges) | ||
} | ||
|
||
if darwin.checkInstalled() == false { | ||
return "Status could not defined", errors.New(darwin.description + " not installed") | ||
} | ||
|
||
statusAction, _ := darwin.checkRunning() | ||
|
||
return statusAction, nil | ||
} | ||
|
||
var propertyList = `<?xml version="1.0" encoding="UTF-8"?> | ||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | ||
<plist version="1.0"> | ||
<dict> | ||
<key>KeepAlive</key> | ||
<true/> | ||
<key>Label</key> | ||
<string>{{.Name}}</string> | ||
<key>ProgramArguments</key> | ||
<array> | ||
<string>{{.Path}}</string> | ||
</array> | ||
<key>RunAtLoad</key> | ||
<true/> | ||
<key>WorkingDirectory</key> | ||
<string>/usr/local/var</string> | ||
<key>StandardErrorPath</key> | ||
<string>/usr/local/var/log/{{.Name}}.err</string> | ||
<key>StandardOutPath</key> | ||
<string>/usr/local/var/log/{{.Name}}.log</string> | ||
</dict> | ||
</plist> | ||
` |
Oops, something went wrong.