Documentation
¶
Overview ¶
Package daemon provides a reusable, cross-platform daemon lifecycle library.
Two modes of operation:
- Client mode: Start/Stop/Restart/EnsureRunning/Status -- manage a background daemon process.
- Server mode: Serve() -- run as the foreground daemon process with HTTP, PID management, signal handling.
All lifecycle mutations (Start, Stop, Restart, EnsureRunning) serialize on a single startup lock file. Public methods are thin wrappers: acquire lock, check state, delegate to internal *Locked methods, release lock.
Consumer wires CLI commands; this library provides the operations.
import "github.com/grpmsoft/daemon"
d := daemon.New(daemon.Config{Name: "myapp", DataDir: ".myapp", Args: []string{"serve"}})
info, err := d.Start(ctx) // spawn detached background process
info, err = d.EnsureRunning(ctx) // idempotent start-or-attach
d.Stop(ctx) // graceful shutdown
d.Status() // check if running, get info
Index ¶
- Variables
- func EnsureRunning(ctx context.Context, cfg Config) (int, error)
- func Proxy(ctx context.Context, cfg Config, opts ProxyOptions) error
- func Serve(ctx context.Context, cfg Config, handler http.Handler) error
- type Config
- type ConnTracker
- type Daemon
- func (d *Daemon) EnsureRunning(ctx context.Context) (*Info, error)
- func (d *Daemon) IsRunning() bool
- func (d *Daemon) Restart(ctx context.Context) (*Info, error)
- func (d *Daemon) Start(ctx context.Context) (*Info, error)
- func (d *Daemon) Status() (*Info, error)
- func (d *Daemon) Stop(ctx context.Context) error
- type Group
- type HealthChecker
- type Info
- type PIDInfo
- type PIDStore
- type ProcessManager
- type ProxyOptions
- type Status
Constants ¶
This section is empty.
Variables ¶
var ( // ErrNotRunning indicates the daemon is not currently running. ErrNotRunning = errors.New("daemon: not running") // ErrAlreadyRunning indicates a daemon instance is already running. ErrAlreadyRunning = errors.New("daemon: already running") // ErrStartTimeout indicates the daemon did not become ready within the configured timeout. ErrStartTimeout = errors.New("daemon: start timeout") // ErrUnhealthy indicates the health check failed after the daemon started. ErrUnhealthy = errors.New("daemon: health check failed") // ErrStalePIDFile indicates the PID file exists but the daemon is not running. ErrStalePIDFile = errors.New("daemon: stale pid file") // ErrInvalidConfig indicates the configuration is invalid. ErrInvalidConfig = errors.New("daemon: invalid config") )
Functions ¶
func EnsureRunning ¶
EnsureRunning is a convenience package-level wrapper that creates a Daemon from cfg and calls its EnsureRunning method. Returns the port the daemon is listening on.
func Proxy ¶
func Proxy(ctx context.Context, cfg Config, opts ProxyOptions) error
Proxy bridges an input stream to the daemon's HTTP endpoint. Reads newline-delimited JSON-RPC from opts.Stdin, POSTs each to the daemon, writes responses to opts.Stdout.
Connection tracking: signals /daemon/connect on start and /daemon/disconnect on exit (only if connect succeeded — prevents stray decrements).
Respects ctx for cancellation. Uses context-based timeouts instead of hardcoded http.Client.Timeout.
func Serve ¶
Serve runs the daemon in the foreground. Called by the spawned process. It picks a free port, registers /health + connection tracking endpoints, writes PID data to the locked PID file, and blocks until ctx is cancelled, SIGINT/SIGTERM is received, or idle timeout fires.
Lock inheritance: if DAEMON_PIDFD=3 is set (spawned by Start/EnsureRunning), Serve inherits the locked fd from the parent via ExtraFiles. If not set (manual foreground start), Serve acquires the lock itself.
Idle auto-shutdown: if Config.IdleTimeout > 0 and all connections disconnect, Serve waits IdleTimeout before initiating graceful shutdown. A new connection arriving during the wait resets the timer.
Types ¶
type Config ¶
type Config struct {
// Name identifies the application (e.g. "gode", "goda").
// Used for PID file naming and health response.
Name string `json:"name"`
// DataDir is the directory for runtime files (PID, logs), relative to
// the workspace root. Example: ".gode".
DataDir string `json:"dataDir"`
// Binary is the executable path for the daemon process.
// Empty means os.Executable() (current binary).
Binary string `json:"binary"`
// Args are the arguments passed to the child process in Start/Restart.
Args []string `json:"args"`
// Timeout is how long Start() waits for the health check to pass.
// Zero means 30 seconds.
Timeout time.Duration `json:"timeout"`
// HealthPath is the HTTP path for the health endpoint.
// Zero value means "/health".
HealthPath string `json:"healthPath"`
// IdleTimeout is how long the daemon waits with zero active connections
// before initiating a graceful shutdown. Zero means never auto-shutdown.
IdleTimeout time.Duration `json:"idleTimeout"`
// RequireToken, when true, requires the bearer token for the application
// handler (everything outside /health and /daemon/*). Default false so
// curl debugging of the app handler keeps working. MCP deployments
// should set this to true.
RequireToken bool `json:"requireToken"`
}
Config controls daemon lifecycle behavior. Consumers provide this when creating a Daemon instance.
type ConnTracker ¶
type ConnTracker struct {
// contains filtered or unexported fields
}
ConnTracker tracks active client connections to the daemon. When IdleTimeout is configured, dropping to zero connections starts an idle timer that triggers graceful shutdown.
func NewConnTracker ¶
func NewConnTracker() *ConnTracker
NewConnTracker creates a connection tracker.
func (*ConnTracker) Active ¶
func (ct *ConnTracker) Active() int64
Active returns the current number of active connections.
func (*ConnTracker) Connect ¶
func (ct *ConnTracker) Connect()
Connect increments the connection count and signals that idle timer should be reset.
func (*ConnTracker) Disconnect ¶
func (ct *ConnTracker) Disconnect()
Disconnect decrements the connection count. If it drops to zero, signals on idleCh so the idle timer can start. The count is clamped to zero — an unmatched Disconnect (stray request, crashed client) cannot drive it negative and cause premature shutdown.
type Daemon ¶
type Daemon struct {
// contains filtered or unexported fields
}
Daemon manages a background daemon process. It depends on PIDStore, ProcessManager, and HealthChecker interfaces — not on concrete implementations. New() wires the defaults.
func NewWithDeps ¶
func NewWithDeps(cfg Config, pids PIDStore, procs ProcessManager, health HealthChecker) *Daemon
NewWithDeps creates a Daemon with explicit interface implementations. Intended for testing — callers supply mock PIDStore, ProcessManager, and HealthChecker.
func (*Daemon) EnsureRunning ¶ added in v0.3.0
EnsureRunning is idempotent: if a daemon is already running, returns its info. If not, starts a new one. Safe for concurrent callers — all serialize on the startup lock.
func (*Daemon) IsRunning ¶
IsRunning returns true if the daemon process is alive according to the PID file.
func (*Daemon) Restart ¶
Restart stops a running daemon (if any) and starts a new one, under a single startup lock acquisition. This prevents the deadlock that would occur if Restart called the public Stop() then Start() (each acquires the startup lock independently).
func (*Daemon) Start ¶
Start spawns a detached background process using Config.Binary and Config.Args. Returns ErrAlreadyRunning if a daemon is already running. The spawned process must eventually call Serve().
Lock protocol:
- Acquire startup lock (serializes concurrent Start/EnsureRunning/Stop/Restart)
- Under startup lock, check if daemon already running (PID file held)
- Delegate to startLocked (acquire PID lock, spawn, health check)
type Group ¶
type Group struct {
// contains filtered or unexported fields
}
Group manages multiple concurrent actors. When any actor returns, all others are interrupted. Modeled after oklog/run.Group but implemented from scratch with zero external dependencies.
type HealthChecker ¶
type HealthChecker interface {
Check(port int, healthPath string) error
WaitUntilReady(port int, healthPath string, timeout time.Duration) error
}
HealthChecker abstracts daemon readiness verification.
type Info ¶
type Info struct {
Status Status `json:"status"`
PID int `json:"pid"`
Port int `json:"port"`
Name string `json:"name"`
StartTime time.Time `json:"startTime"`
Uptime time.Duration `json:"uptime"`
}
Info is the status snapshot returned by Daemon.Status().
type PIDInfo ¶
type PIDInfo struct {
PID int `json:"pid"`
Port int `json:"port"`
Name string `json:"name"`
Binary string `json:"binary"`
StartTime time.Time `json:"startTime"`
Token string `json:"token,omitempty"`
}
PIDInfo holds the persisted state of a running daemon, as returned by PIDStore.Load().
type PIDStore ¶
type PIDStore interface {
Save(pid, port int, name, binary string, startTime time.Time) error
Load() (PIDInfo, error)
Clear() error
IsAlive() bool
Path() string
}
PIDStore abstracts PID file persistence. The Daemon model depends on this interface, not on a concrete file-based implementation. PIDInfo is returned by Load() with the stored daemon state.
type ProcessManager ¶
type ProcessManager interface {
StartDetached(binary string, args []string, logFile string, env []string) (pid int, err error)
KillProcess(ctx context.Context, pid int, grace time.Duration) error
IsProcessAlive(pid int) bool
}
ProcessManager abstracts platform-specific process lifecycle operations.
type ProxyOptions ¶ added in v0.2.0
type ProxyOptions struct {
// MCPPath is the HTTP path on the daemon to POST JSON-RPC messages to.
// Default: "/mcp".
MCPPath string
// Stdin is the input reader for JSON-RPC messages. Default: os.Stdin.
Stdin io.Reader
// Stdout is the output writer for JSON-RPC responses. Default: os.Stdout.
Stdout io.Writer
// LogPayloads enables logging of request/response bodies (first 500 bytes).
// Default: false (only method/id/size/latency logged for security — S3).
LogPayloads bool
}
ProxyOptions configures the Proxy behavior.
type Status ¶
type Status int
Status represents the lifecycle state of a daemon process.
const ( StatusStopped Status = iota // not running, no PID file StatusStarting // process spawned, health check pending StatusRunning // healthy and responding StatusStopping // shutdown in progress StatusError // PID file exists but process is dead or unhealthy )
Status constants representing the lifecycle state of a daemon process.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package internal provides platform-specific implementations for the daemon library: PID file management, process lifecycle operations, and health checking.
|
Package internal provides platform-specific implementations for the daemon library: PID file management, process lifecycle operations, and health checking. |
|
pidlock
Package pidlock provides an exclusive advisory lock backed by the PID file itself.
|
Package pidlock provides an exclusive advisory lock backed by the PID file itself. |