Skip to content

Commit

Permalink
Merge branch 'release-0.1.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
takama committed Aug 2, 2014
2 parents f186a79 + 18ea48d commit 6346ac6
Show file tree
Hide file tree
Showing 6 changed files with 765 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.DS_Store
131 changes: 131 additions & 0 deletions daemon.go
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
}
210 changes: 210 additions & 0 deletions darwin.go
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>
`
Loading

0 comments on commit 6346ac6

Please sign in to comment.