daemon

package
v1.9.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 20, 2023 License: Apache-2.0, MIT Imports: 8 Imported by: 1

README

Go Daemon

A daemon package for use with Go (golang) services

GoDoc

Examples

Simplest example (just install self as daemon)
package main

import (
    "fmt"
    "log"

    "github.com/takama/daemon"
)

func main() {
    service, err := daemon.New("name", "description", daemon.SystemDaemon)
    if err != nil {
        log.Fatal("Error: ", err)
    }
    status, err := service.Install()
    if err != nil {
        log.Fatal(status, "\nError: ", err)
    }
    fmt.Println(status)
}
Real example
// Example of a daemon with echo service
package main

import (
    "fmt"
    "log"
    "net"
    "os"
    "os/signal"
    "syscall"

    "github.com/takama/daemon"
)

const (

    // name of the service
    name        = "myservice"
    description = "My Echo Service"

    // port which daemon should be listen
    port = ":9977"
)

//    dependencies that are NOT required by the service, but might be used
var dependencies = []string{"dummy.service"}

var stdlog, errlog *log.Logger

// Service has embedded daemon
type Service struct {
    daemon.Daemon
}

// Manage by daemon commands or run the daemon
func (service *Service) Manage() (string, error) {

    usage := "Usage: myservice install | remove | start | stop | status"

    // 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()
        default:
            return usage, nil
        }
    }

    // Do something, call your goroutines, etc

    // Set up channel on which to send signal notifications.
    // We must use a buffered channel or risk missing the signal
    // if we're not ready to receive when the signal is sent.
    interrupt := make(chan os.Signal, 1)
    signal.Notify(interrupt, os.Interrupt, os.Kill, syscall.SIGTERM)

    // Set up listener for defined host and port
    listener, err := net.Listen("tcp", port)
    if err != nil {
        return "Possibly was a problem with the port binding", err
    }

    // set up channel on which to send accepted connections
    listen := make(chan net.Conn, 100)
    go acceptConnection(listener, listen)

    // loop work cycle with accept connections or interrupt
    // by system signal
    for {
        select {
        case conn := <-listen:
            go handleClient(conn)
        case killSignal := <-interrupt:
            stdlog.Println("Got signal:", killSignal)
            stdlog.Println("Stoping listening on ", listener.Addr())
            listener.Close()
            if killSignal == os.Interrupt {
                return "Daemon was interruped by system signal", nil
            }
            return "Daemon was killed", nil
        }
    }

    // never happen, but need to complete code
    return usage, nil
}

// Accept a client connection and collect it in a channel
func acceptConnection(listener net.Listener, listen chan<- net.Conn) {
    for {
        conn, err := listener.Accept()
        if err != nil {
            continue
        }
        listen <- conn
    }
}

func handleClient(client net.Conn) {
    for {
        buf := make([]byte, 4096)
        numbytes, err := client.Read(buf)
        if numbytes == 0 || err != nil {
            return
        }
        client.Write(buf[:numbytes])
    }
}

func init() {
    stdlog = log.New(os.Stdout, "", log.Ldate|log.Ltime)
    errlog = log.New(os.Stderr, "", log.Ldate|log.Ltime)
}

func main() {
    srv, err := daemon.New(name, description, daemon.SystemDaemon, dependencies...)
    if err != nil {
        errlog.Println("Error: ", err)
        os.Exit(1)
    }
    service := &Service{srv}
    status, err := service.Manage()
    if err != nil {
        errlog.Println(status, "\nError: ", err)
        os.Exit(1)
    }
    fmt.Println(status)
}
Service config file

Optionally, service config file can be retrieved or updated by calling GetTemplate() string and SetTemplate(string) methods(except MS Windows). Template will be a default Go Template("text/template").

If SetTemplate is not called, default template content will be used while creating service.

Variable Description
Description Description for service
Dependencies Service dependencies
Name Service name
Path Path of service executable
Args Arguments for service executable
Example template(for linux systemv)
[Unit]
Description={{.Description}}
Requires={{.Dependencies}}
After={{.Dependencies}}

[Service]
PIDFile=/var/run/{{.Name}}.pid
ExecStartPre=/bin/rm -f /var/run/{{.Name}}.pid
ExecStart={{.Path}} {{.Args}}
Restart=on-failure

[Install]
WantedBy=multi-user.target
Cron example

See examples/cron/cron_job.go

Contributors (unsorted)

All the contributors are welcome. If you would like to be the contributor please accept some rules.

  • The pull requests will be accepted only in develop branch
  • All modifications or additions should be tested
  • Sorry, We will not accept code with any dependency, only standard library

Thank you for your understanding!

License

MIT Public License

Documentation

Overview

Package daemon v1.0.0 for use with Go (golang) services.

Package daemon provides primitives for daemonization of golang services. In the current implementation the only supported operating systems are macOS, FreeBSD, Linux and Windows. Also to note, for global daemons one must have root rights to install or remove the service. The only exception is macOS where there is an implementation of a user daemon that can installed or removed by the current user.

Example:

	// Example of a daemon with echo service
	package main

	import (
		"fmt"
		"log"
		"net"
		"os"
		"os/signal"
		"syscall"

		"github.com/takama/daemon"
	)

	const (

		// name of the service
		name        = "myservice"
		description = "My Echo Service"

		// port which daemon should be listen
		port = ":9977"
	)

  // dependencies that are NOT required by the service, but might be used
  var dependencies = []string{"dummy.service"}

	var stdlog, errlog *log.Logger

	// Service has embedded daemon
	type Service struct {
		daemon.Daemon
	}

	// Manage by daemon commands or run the daemon
	func (service *Service) Manage() (string, error) {

		usage := "Usage: myservice install | remove | start | stop | status"

		// 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()
			default:
				return usage, nil
			}
		}

		// Do something, call your goroutines, etc

		// Set up channel on which to send signal notifications.
		// We must use a buffered channel or risk missing the signal
		// if we're not ready to receive when the signal is sent.
		interrupt := make(chan os.Signal, 1)
		signal.Notify(interrupt, os.Interrupt, os.Kill, syscall.SIGTERM)

		// Set up listener for defined host and port
		listener, err := net.Listen("tcp", port)
		if err != nil {
			return "Possibly was a problem with the port binding", err
		}

		// set up channel on which to send accepted connections
		listen := make(chan net.Conn, 100)
		go acceptConnection(listener, listen)

		// loop work cycle with accept connections or interrupt
		// by system signal
		for {
			select {
			case conn := <-listen:
				go handleClient(conn)
			case killSignal := <-interrupt:
				stdlog.Println("Got signal:", killSignal)
				stdlog.Println("Stoping listening on ", listener.Addr())
				listener.Close()
				if killSignal == os.Interrupt {
					return "Daemon was interrupted by system signal", nil
				}
				return "Daemon was killed", nil
			}
		}

		// never happen, but need to complete code
		return usage, nil
	}

	// Accept a client connection and collect it in a channel
	func acceptConnection(listener net.Listener, listen chan<- net.Conn) {
		for {
			conn, err := listener.Accept()
			if err != nil {
				continue
			}
			listen <- conn
		}
	}

	func handleClient(client net.Conn) {
		for {
			buf := make([]byte, 4096)
			numbytes, err := client.Read(buf)
			if numbytes == 0 || err != nil {
				return
			}
			client.Write(buf[:numbytes])
		}
	}

	func init() {
		stdlog = log.New(os.Stdout, "", log.Ldate|log.Ltime)
		errlog = log.New(os.Stderr, "", log.Ldate|log.Ltime)
	}

	func main() {
		srv, err := daemon.New(name, description, daemon.SystemDaemon, dependencies...)
		if err != nil {
			errlog.Println("Error: ", err)
			os.Exit(1)
		}
		service := &Service{srv}
		status, err := service.Manage()
		if err != nil {
			errlog.Println(status, "\nError: ", err)
			os.Exit(1)
		}
		fmt.Println(status)
	}

Go daemon

Package daemon windows version

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrUnsupportedSystem appears if try to use service on system which is not supported by this release
	ErrUnsupportedSystem = errors.New("Unsupported system")

	// ErrRootPrivileges appears if run installation or deleting the service without root privileges
	ErrRootPrivileges = errors.New("You must have root user privileges. Possibly using 'sudo' command should help")

	// ErrAlreadyInstalled appears if service already installed on the system
	ErrAlreadyInstalled = errors.New("Service has already been installed")

	// ErrNotInstalled appears if try to delete service which was not been installed
	ErrNotInstalled = errors.New("Service is not installed")

	// ErrAlreadyRunning appears if try to start already running service
	ErrAlreadyRunning = errors.New("Service is already running")

	// ErrAlreadyStopped appears if try to stop already stopped service
	ErrAlreadyStopped = errors.New("Service has already been stopped")
)
View Source
var (
	// WinErrCode - List of system errors from Microsoft source:
	// https://msdn.microsoft.com/en-us/library/windows/desktop/ms681385(v=vs.85).aspx
	WinErrCode = map[int]SystemError{
		5: SystemError{
			Title:       "ERROR_ACCESS_DENIED",
			Description: "Access denied.",
			Action:      "Administrator access is needed to install a service.",
		},
		1051: SystemError{
			Title:       "ERROR_DEPENDENT_SERVICES_RUNNING",
			Description: "A stop control has been sent to a service that other running services are dependent on.",
		},
		1052: SystemError{
			Title:       "ERROR_INVALID_SERVICE_CONTROL",
			Description: "The requested control is not valid for this service.",
		},
		1053: SystemError{
			Title:       "ERROR_SERVICE_REQUEST_TIMEOUT",
			Description: "The service did not respond to the start or control request in a timely fashion.",
		},
		1054: SystemError{
			Title:       "ERROR_SERVICE_NO_THREAD",
			Description: "A thread could not be created for the service.",
		},
		1055: SystemError{
			Title:       "ERROR_SERVICE_DATABASE_LOCKED",
			Description: "The service database is locked.",
		},
		1056: SystemError{
			Title:       "ERROR_SERVICE_ALREADY_RUNNING",
			Description: "An instance of the service is already running.",
		},
		1057: SystemError{
			Title:       "ERROR_INVALID_SERVICE_ACCOUNT",
			Description: "The account name is invalid or does not exist, or the password is invalid for the account name specified.",
		},
		1058: SystemError{
			Title:       "ERROR_SERVICE_DISABLED",
			Description: "The service cannot be started, either because it is disabled or because it has no enabled devices associated with it.",
		},
		1060: SystemError{
			Title:       "ERROR_SERVICE_DOES_NOT_EXIST",
			Description: "The specified service does not exist as an installed service.",
		},
		1061: SystemError{
			Title:       "ERROR_SERVICE_CANNOT_ACCEPT_CTRL",
			Description: "The service cannot accept control messages at this time.",
		},
		1062: SystemError{
			Title:       "ERROR_SERVICE_NOT_ACTIVE",
			Description: "The service has not been started.",
		},
		1063: SystemError{
			Title:       "ERROR_FAILED_SERVICE_CONTROLLER_CONNECT",
			Description: "The service process could not connect to the service controller.",
		},
		1064: SystemError{
			Title:       "ERROR_EXCEPTION_IN_SERVICE",
			Description: "An exception occurred in the service when handling the control request.",
		},
		1066: SystemError{
			Title:       "ERROR_SERVICE_SPECIFIC_ERROR",
			Description: "The service has returned a service-specific error code.",
		},
		1068: SystemError{
			Title:       "ERROR_SERVICE_DEPENDENCY_FAIL",
			Description: "The dependency service or group failed to start.",
		},
		1069: SystemError{
			Title:       "ERROR_SERVICE_LOGON_FAILED",
			Description: "The service did not start due to a logon failure.",
		},
		1070: SystemError{
			Title:       "ERROR_SERVICE_START_HANG",
			Description: "After starting, the service hung in a start-pending state.",
		},
		1071: SystemError{
			Title:       "ERROR_INVALID_SERVICE_LOCK",
			Description: "The specified service database lock is invalid.",
		},
		1072: SystemError{
			Title:       "ERROR_SERVICE_MARKED_FOR_DELETE",
			Description: "The specified service has been marked for deletion.",
		},
		1073: SystemError{
			Title:       "ERROR_SERVICE_EXISTS",
			Description: "The specified service already exists.",
		},
		1075: SystemError{
			Title:       "ERROR_SERVICE_DEPENDENCY_DELETED",
			Description: "The dependency service does not exist or has been marked for deletion.",
		},
		1077: SystemError{
			Title:       "ERROR_SERVICE_NEVER_STARTED",
			Description: "No attempts to start the service have been made since the last boot.",
		},
		1078: SystemError{
			Title:       "ERROR_DUPLICATE_SERVICE_NAME",
			Description: "The name is already in use as either a service name or a service display name.",
		},
		1079: SystemError{
			Title:       "ERROR_DIFFERENT_SERVICE_ACCOUNT",
			Description: "The account specified for this service is different from the account specified for other services running in the same process.",
		},
		1083: SystemError{
			Title:       "ERROR_SERVICE_NOT_IN_EXE",
			Description: "The executable program that this service is configured to run in does not implement the service.",
		},
		1084: SystemError{
			Title:       "ERROR_NOT_SAFEBOOT_SERVICE",
			Description: "This service cannot be started in Safe Mode.",
		},
	}
)

Functions

func ExecPath

func ExecPath() (string, error)

ExecPath tries to get executable path

Types

type Daemon

type Daemon interface {
	// GetTemplate - gets service config template
	GetTemplate() string

	// SetTemplate - sets service config template
	SetTemplate(string) error

	// Install the service into the system
	Install(args ...string) (string, error)

	// Remove the service and all corresponding 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)

	// Run - run executable service
	Run(e Executable) (string, error)
}

Daemon interface has a standard set of methods/commands

func New

func New(name, description string, kind Kind, dependencies ...string) (Daemon, error)

New - Create a new daemon

name: name of the service

description: any explanation, what is the service, its purpose

kind: what kind of daemon to create

type Executable

type Executable interface {
	// Start - non-blocking start service
	Start()
	// Stop - non-blocking stop service
	Stop()
	// Run - blocking run service
	Run()
}

Executable interface defines controlling methods of executable service

type Kind

type Kind string

Kind is type of the daemon

const (
	// UserAgent is a user daemon that runs as the currently logged in user and
	// stores its property list in the user’s individual LaunchAgents directory.
	// In other words, per-user agents provided by the user. Valid for macOS only.
	UserAgent Kind = "UserAgent"

	// GlobalAgent is a user daemon that runs as the currently logged in user and
	// stores its property list in the users' global LaunchAgents directory. In
	// other words, per-user agents provided by the administrator. Valid for macOS
	// only.
	GlobalAgent Kind = "GlobalAgent"

	// GlobalDaemon is a system daemon that runs as the root user and stores its
	// property list in the global LaunchDaemons directory. In other words,
	// system-wide daemons provided by the administrator. Valid for macOS only.
	GlobalDaemon Kind = "GlobalDaemon"

	// SystemDaemon is a system daemon that runs as the root user. In other words,
	// system-wide daemons provided by the administrator. Valid for FreeBSD, Linux
	// and Windows only.
	SystemDaemon Kind = "SystemDaemon"
)

type SystemError

type SystemError struct {
	Title       string
	Description string
	Action      string
}

SystemError contains error description and corresponded action helper to fix it

Directories

Path Synopsis
Example of a daemon with echo service
Example of a daemon with echo service

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL