Creating a service

Some applications are capable of automatically handling their service file, and this is what we will try to achieve, step by step. Let's start with an init.d script:

#!/bin/sh

"/path/to/mydaemon" $1

This is a sample script that passes the first argument to the daemon. The path to the binary will be dependent on where the file is located. This needs to be defined at runtime:

// ErrSudo is an error that suggest to execute the command as super user
// It will be used with the functions that fail because of permissions
var ErrSudo error

var (
bin string
cmd string
)

func init() {
p, err := filepath.Abs(filepath.Dir(os.Args[0]))
if err != nil {
panic(err)
}
bin = p
if len(os.Args) != 1 {
cmd = os.Args[1]
}
ErrSudo = fmt.Errorf("try `sudo %s %s`", bin, cmd)
}

The main function will handle the different commands, as follows:

func main() {
var err error
switch cmd {
case "run":
err = runApp()
case "install":
err = installApp()
case "uninstall":
err = uninstallApp()
case "status":
err = statusApp()
case "start":
err = startApp()
case "stop":
err = stopApp()
default:
helpApp()
}
if err != nil {
fmt.Println(cmd, "error:", err)
}
}

How can we make sure that our app is running? A very sound strategy is to use a PID file, which is a text file that contains the current PID of the running process. Let's define a couple of auxiliary functions to achieve this:

const (
varDir = "/var/mydaemon/"
pidFile = "mydaemon.pid"
)

func writePid(pid int) (err error) {
f, err := os.OpenFile(filepath.Join(varDir, pidFile), os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return err
}
defer f.Close()
if _, err = fmt.Fprintf(f, "%d", pid); err != nil {
return err
}
return nil
}

func getPid() (pid int, err error) {
b, err := ioutil.ReadFile(filepath.Join(varDir, pidFile))
if err != nil {
return 0, err
}
if pid, err = strconv.Atoi(string(b)); err != nil {
return 0, fmt.Errorf("Invalid PID value: %s", string(b))
}
return pid, nil
}

The install and uninstall functions will take care of adding or removing the service file located at /etc/init.d/mydaemon and requires us to launch the app with root permissions because of the file's location:

const initdFile = "/etc/init.d/mydaemon"

func installApp() error {
_, err := os.Stat(initdFile)
if err == nil {
return errors.New("Already installed")
}
f, err := os.OpenFile(initdFile, os.O_CREATE|os.O_WRONLY, 0755)
if err != nil {
if !os.IsPermission(err) {
return err
}
return ErrSudo
}
defer f.Close()
if _, err = fmt.Fprintf(f, "#!/bin/sh\n\"%s\" $1", bin); err != nil {
return err
}
fmt.Println("Daemon", bin, "installed")
return nil
}

func uninstallApp() error {
_, err := os.Stat(initdFile)
if err != nil && os.IsNotExist(err) {
return errors.New("not installed")
}
if err = os.Remove(initdFile); err != nil {
if err != nil {
if !os.IsPermission(err) {
return err
}
return ErrSudo
}
}
fmt.Println("Daemon", bin, "removed")
return err
}

Once the file is created, we can install the app as a service with the mydaemon install command and remove it with mydaemon uninstall.

Once the daemon has been installed, we can use sudo service mydaemon [start|stop|status] to control the daemon. Now, all we need to do is implement these actions:

Let's take a look at how the status command is implemented. Note that the 0 signal doesn't exist in Unix, and doesn't trigger an action from the operating system or the app, but the operation will fail if the process isn't running. This tells us whether the process is alive or not:

func statusApp() (err error) {
var pid int
defer func() {
if pid == 0 {
fmt.Println("status: not active")
return
}
fmt.Println("status: active - pid", pid)
}()
pid, err = getPid()
if err != nil {
if os.IsNotExist(err) {
return nil
}
return err
}
p, err := os.FindProcess(pid)
if err != nil {
return nil
}
if err = p.Signal(syscall.Signal(0)); err != nil {
fmt.Println(pid, "not found - removing PID file...")
os.Remove(filepath.Join(varDir, pidFile))
pid = 0
}
return nil
}

In the start command, we will create the daemon by following the steps we covered in the Operating system support section:

  1. Use files for standard output and input
  2. Set the working directory to root
  3. Start the command asynchronously

In addition to these operations, the start command saves the PID value of the process in a specific file, which will be used to see whether the process is alive:

func startApp() (err error) {
const perm = os.O_CREATE | os.O_APPEND | os.O_WRONLY
if err = os.MkdirAll(varDir, 0755); err != nil {
if !os.IsPermission(err) {
return err
}
return ErrSudo
}
cmd := exec.Command(bin, "run")
cmd.Stdout, err = os.OpenFile(filepath.Join(varDir, outFile),
perm, 0644)
if err != nil {
return err
}
cmd.Stderr, err = os.OpenFile(filepath.Join(varDir, errFile),
perm, 0644)
if err != nil {
return err
}
cmd.Dir = "/"
if err = cmd.Start(); err != nil {
return err
}
if err := writePid(cmd.Process.Pid); err != nil {
if err := cmd.Process.Kill(); err != nil {
fmt.Println("Cannot kill process", cmd.Process.Pid, err)
}
return err
}
fmt.Println("Started with PID", cmd.Process.Pid)
return nil
}

Lastly, stopApp will terminate the process identified by the PID file, if it exists:

func stopApp() (err error) {
pid, err := getPid()
if err != nil {
if os.IsNotExist(err) {
return nil
}
return err
}
p, err := os.FindProcess(pid)
if err != nil {
return nil
}
if err = p.Signal(os.Kill); err != nil {
return err
}
if err := os.Remove(filepath.Join(varDir, pidFile)); err != nil {
return err
}
fmt.Println("Stopped PID", pid)
return nil
}

Now, all of the parts that are needed for an application's control are there, and all that is missing is the main application part, which should be a loop so that the daemon stays alive:

func runApp() error {
fmt.Println("RUN")
for {
time.Sleep(time.Second)
}
return nil
}

In this example, all it does is sleep for a fixed amount of time, between the loop iterations. This is generally a good idea in a main loop, because an empty for loop would use a lot of resources for no reason. Let's assume that your application is checking for a certain condition in a for loop. If that is satisfied, continually checking for this will use a lot of resources. Adding an idle sleep of a few milliseconds can help reduce idle CPU consumption by 90-95%, so keep it in mind when designing your daemons!