mkgd

package module
v0.0.5 Latest Latest
Warning

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

Go to latest
Published: Jun 8, 2026 License: MIT Imports: 18 Imported by: 0

README

mkgd

GoDoc

mkgd (m-k-g-d, or "make go daemon") provides functionality for implementing an init daemon for systems like rc.d in Go. It aims to solve common toil involved in implementing a daemon such as syslog setup, PID file management, setting the daemon process owner user, and creating command-line options that modify those levers at runtime.

What is daemonization?

Daemonization, as I understand it, occurs when a process detaches from the controlling terminal so that it can run in the background. Typically, "detaching" implies starting a copy of the current process as a child process and having the original process exit. There are several helper functions in C such as daemon(3) that automate this. In theory, Go can consume those functions using cgo or the purego library.

The issue with such an approach is said C functions may first start a copy of the calling process using fork(2) which leaves the new child process with a single OS thread. Since the Go runtime expects to have access to multiple OS threads to work correctly, calling those functions likely leaves the child process' Go runtime in a broken state.

mkgd (and many other Go libraries) work around this by starting another instance of the Go program as a child process using the exec family of functions. The goal of mkgd is to provide a simple API that does not "take over" the user's program or introduce additional external dependencies.

Features

  • Daemonize Go applications with a flexible API using Go's os/exec API
  • Automatically creates a signal-based context.Context for both the parent and child processes
  • Creates a PID file and checks the PID file to see if a process is already running. Defaults to using /var/run/ARGV_0_NAME.pid where "ARGV_0_NAME" is the base name of os.Args[0]
  • Sets up the default log.Logger to use syslog and disables timestamps
  • Opt-in support for waiting until the child process is ready (i.e., allow the child process to parse configuration files or other data before "detaching" from it)
  • Opt-in support for changing daemonization behavior using command-line arguments
  • Support for running the child process as another user (assumes that the parent process is started by the root user)

Usage

This library provides a (relatively) simple API. Users call Daemonize, which blocks and starts a second copy of the program as a child process. The child process runs through the same code as the parent process up until the child calls Daemonize itself. At this point, the Daemonize function unblocks for both the parent and child processes and informs the processes of their state using dedicated Go data types.

Here is a very simple example of this workflow:

package main

import (
    "log"
    "os"

    "codeberg.org/stephen-fox/mkgd"
)

func main() {
    log.SetFlags(0)

    daemonState := mkgd.Daemonize(&mkgd.Config{
        RuntimeOptions: &mkgd.RuntimeOptions{
            PidFilePath: "/tmp/example-pid-file.txt",
        },
    })

    switch state := daemonState.(type) {
    case *mkgd.IsParent:
        log.Printf("hello from the parent process! (pid: %d)", os.Getpid())
    case *mkgd.IsDaemon:
        log.Printf("hello from the daemon (child)! (pid: %d)", os.Getpid())
    default:
        log.Fatalf("daemonization failed - %s", state.Failed())
    }
}

Here is what the above example program does when run:

$ go run examples/helloworld/main.go
hello from the parent process! (pid: 92619)
hello from the daemon (child)! (pid: 92769)

Supported systems

mkgd is designed to support Unix-like operating systems such as BSDs and Linux. While not designed for Windows, I have made every effort to ensure it compiles on Windows and works using the "do not exec" runtime option.

Examples

Refer to the examples/ directory.

Why yet another daemon library (especially in 2026)?

I created this library for a few reasons:

  • I wanted a library that was well documented and had sensible defaults
  • I wanted to rely solely on the standard library
  • I use FreeBSD quite a bit, so daemonization is still something I need
  • I am crazy and enjoy solving these kinds of weird problems

I had previously created cyberdaemon to do this, but I wanted something more oriented towards init daemons and Unix-like systems. Plus, I learned a lot since I wrote cyberdaemon - so I wanted a clean sheet design.

Documentation

Overview

Package mkgd (m-k-g-d, or "make go daemon") provides functionality for implementing an init daemon for systems like rc.d in Go. It aims to solve common toil involved in implementing a daemon such as syslog setup, PID file management, setting the daemon process owner user, and creating command-line options that modify those levers at runtime.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ChildWaitTimeout

func ChildWaitTimeout(t time.Duration) func(context.Context) <-chan error

ChildWaitTimeout returns a function that, when called, creates a channel that is written to when the specified timeout elapses.

This function is meant for use with Config.ChildWaitGiveUpFn.

func DefaultPidFilePath added in v0.0.5

func DefaultPidFilePath() string

DefaultPidFilePath returns the default PID file path used by the library, which is "/var/run/ARGV_0_NAME.pid" where ARGV_0_NAME is the base name of os.Args[0].

func RunningAsChild

func RunningAsChild() bool

func RunningAsParent

func RunningAsParent() bool

Types

type ChildSetupWaitStrateggy

type ChildSetupWaitStrateggy string

ChildSetupWaitStrateggy configures whether the parent process should wait for the child process to indicate that it is finished setting up.

const (
	// DoNotWaitForChild is the default ChildSetupWaitStrategy.
	DoNotWaitForChild ChildSetupWaitStrateggy = ""

	// WaitForChildStdoutWrite waits for the child to write to stdout.
	WaitForChildStdoutWrite ChildSetupWaitStrateggy = "wait-stdout-write"
)

type CliConfig

type CliConfig struct {
	// PidFilePathArg is the argument string to use for the PID file
	// path argument. For example, a value of "P" would result in
	// an argument of "-P" being defined.
	//
	// Defaults to an uppercase letter if empty.
	PidFilePathArg string

	// PidFilePathDefValue sets the default value for the PID file
	// path argument.
	//
	// Defaults to the value returned by the DefaultPidFilePath
	// function.
	PidFilePathDefValue string

	// RunAsUserArg is the argument string to use for the "run as
	// user" argument. For example, a value of "U" would result in
	// an argument of "-U" being defined.
	//
	// Defaults to an uppercase letter if empty.
	RunAsUserArg string

	// RunAsUserDefValue is the default value for the "run as user"
	// argument.
	RunAsUserDefValue string

	// DoNotExecArg is the argument string to use for the "do not
	// exec" argument. For example, a value of "D" would result in
	// an argument of "-D" being defined.
	//
	// Defaults to an uppercase letter if empty.
	DoNotExecArg string

	// LogToStderrArg is the argument string to use for the log to
	// stderr argument. For example, a value of "E" would result in
	// an argument of "-E" being defined.
	//
	// Defaults to an uppercase letter if empty.
	LogToStderrArg string

	// FlagSet is the flag.FlagSet to add arguments to.
	//
	// Defaults to flag.CommandLine (the default FlagSet) if nil.
	FlagSet *flag.FlagSet
}

CliConfig configures the RuntimeOptionsFromCli function.

type Config

type Config struct {
	// RuntimeOptions are options that can be set at runtime (i.e.,
	// when the process runs via command-line arguments).
	//
	// If nil, an empty RuntimeOptions is used with the default
	// Go data type values.
	//
	// This object can be generated from command-line arguments
	// using the RuntimeOptionsFromCli function.
	RuntimeOptions *RuntimeOptions

	// ChildSetupWait specifies if the parent process should wait for
	// the child process to tell the parent process it is ready. This
	// only applies when RuntimeOptions.DoNotExec is false.
	//
	// The IsDaemon.NotifyParentReady method should be called by
	// the child process when using this feature.
	//
	// If unspecified, the default behavior is to start the child
	// process and not wait for it to tell the parent it is ready.
	ChildSetupWait ChildSetupWaitStrateggy

	// ChildWaitGiveUpFn is an optional function that specifies
	// when the parent process should give up waiting for the
	// child process to become ready.
	//
	// This function is only called if ChildSetupWait is specified
	// *and* RuntimeOptions.DoNotExec is false.
	//
	// If nil, then ChildWaitTimeout is used with a default
	// timeout value.
	ChildWaitGiveUpFn func(context.Context) <-chan error

	// PreDaemonizeFn specifies an optional function to execute
	// when Daemonize is called.
	//
	// If non-nil, this function is *always* called regardless
	// of the value of RuntimeOptions.DoNotExec.
	PreDaemonizeFn func(context.Context, *PreDaemonizeFnConfig) error

	// PreExecFn is an optional function that is executed prior to
	// starting the child process. This function is only called if
	// it is non-nil *and* RuntimeOptions.DoNotExec is false.
	PreExecFn func(context.Context, *PreExecFnConfig) error

	// PostExecFn is an optional function that works like PreExecFn,
	// but is called after successfully starting the child process.
	// The same rules as PreExecFn apply to this function.
	PostExecFn func(context.Context, *PostExecFnConfig) error

	// ParentCtxFn is an optional function that returns the
	// desired context.Context and its associated cancellation
	// function for the parent process.
	//
	// This function is only called if it is non-nil and
	// RuntimeOptions.DoNotExec is false.
	//
	// If nil, then a default signal-based context is used.
	ParentCtxFn func() (context.Context, func())

	// ChildCtxFn is an optional function that returns the
	// desired context.Context and its associated cancellation
	// function for the child process.
	//
	// This function is always called if it non-nil.
	//
	// If nil, then a default signal-based context is used.
	ChildCtxFn func() (context.Context, func())
}

Config configures the Daemonize function's behavior.

type DaemonizeError

type DaemonizeError struct {
	// contains filtered or unexported fields
}

DaemonizeError is one of the possible values returned by the Daemonize function. It represents an error condition.

func (DaemonizeError) Error

func (o DaemonizeError) Error() string

func (DaemonizeError) Failed

func (o DaemonizeError) Failed() error

func (DaemonizeError) Unwrap

func (o DaemonizeError) Unwrap() error

type DaemonizeState

type DaemonizeState interface {
	// Failed returns an error explaining why daemonization failed.
	//
	// This should only be called after checking the underlying
	// data type of the DaemonizeState.
	Failed() error
}

DaemonizeState represents the result of the Daemonize function. Users should use a type switch to check the underlying data type.

func Daemonize

func Daemonize(config *Config) DaemonizeState

Daemonize daemonizes the current process by executing it a second time. The resulting state of the calling code is conveyed by the underlying data type of the returned DaemonizeState object.

Callers should use a Go type switch to get the underlying data type of the returned DaemonizeState object. DaemonizeState can be a pointer to one of the following values:

  • *IsParent - Caller is the parent process
  • *IsDaemon - Caller is the daemon (child) process (note: this is always returned if RuntimeOptions.DoNotExec is true)
  • *DaemonizeError - The function failed. Note: Users can also use the "default" case of the switch statement to capture this state and retrieve the error using the DaemonizeState.Failed method

Each of the above data types provides additonal levers for communicating with the parent or child processes.

type IsDaemon

type IsDaemon struct {
	// Ctx is a context.Context for the current process. It is setup
	// based on the Config.ChildCtxFn field. Refer to that field's
	// documentation for more details.
	Ctx context.Context

	// CancelCtxFn is a function that cancels the context.Context
	// stored in the Ctx field.
	CancelCtxFn func()
	// contains filtered or unexported fields
}

IsDaemon is returned by Daemonize when the calling code is the daemon process. Refer to the Daemonize function for more details.

func (IsDaemon) Failed

func (o IsDaemon) Failed() error

func (IsDaemon) NotifyParentReady

func (o IsDaemon) NotifyParentReady()

NotifyParentReady should be called by the child process when using the Config.ChildSetupWait field. Calling this function will inform the parent process that the child is ready and unblock the parent's call to the Daemonize function.

type IsParent

type IsParent struct {
	// Ctx is a context.Context for the current process. It is setup
	// based on the Config.ParentCtxFn field. Refer to that field's
	// documentation for more details.
	Ctx context.Context

	// CancelCtxFn is a function that cancels the context.Context
	// stored in the Ctx field.
	CancelCtxFn func()

	// Child is the *exec.Cmd representing the running child process.
	Child *exec.Cmd

	// ChildExited is a channel that is written to *and* closed when
	// the child process exits.
	//
	// Note: This channel is closed by this library after the child
	// process exits. After that point, all future read attempts will
	// return immediately.
	ChildExited <-chan error
}

IsParent is returned by Daemonize when the calling code is the parent process. Refer to the Daemonize function for more details.

func (IsParent) Failed

func (o IsParent) Failed() error

type PostExecFnConfig

type PostExecFnConfig struct{}

type PreDaemonizeFnConfig

type PreDaemonizeFnConfig struct{}

type PreExecFnConfig

type PreExecFnConfig struct {
	UnstartedChild *exec.Cmd
}

type RuntimeOptions

type RuntimeOptions struct {
	// DoNotExec tells Daemonize to not execute a child process
	// or configure syslog.
	DoNotExec bool

	// NoSyslog disables automatic configuration of the default
	// log.Logger to use syslog.
	NoSyslog bool

	// PidFilePath is the file path of the PID file.
	PidFilePath string

	// RunAsUser specifies which user to run the child process as.
	// If this field is an empty string, then the child process
	// will be run as the same user that started the parent.
	//
	// This field is not used when DoNotExec is true.
	RunAsUser string
}

RuntimeOptions customizes the behavior of the Daemonization function at runtime (i.e., when the process is started).

The optional RuntimeOptionsFromCli helper function automates creating this object from command-line arguments.

func RuntimeOptionsFromCli

func RuntimeOptionsFromCli(cliConfig CliConfig) *RuntimeOptions

RuntimeOptionsFromCli derives a RuntimeOptions from command-line arguments. It must be called before executing flag.Parse.

The resulting RuntimeOptions object will be updated by the flag library after command-line arguments are parsed.

Directories

Path Synopsis
examples
dird command
helloworld command

Jump to

Keyboard shortcuts

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