svcmgr

package module
v1.1.1-0...-c5b0043 Latest Latest
Warning

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

Go to latest
Published: Sep 10, 2025 License: Apache-2.0 Imports: 23 Imported by: 0

README

go-svcmgr

Go Reference Go Report Card Coverage Status GitHub release

A Go-native library for controlling process supervisors including runit, s6, and other daemontools-compatible systems, with an adapter for systemd on Linux.

Features

  • Native daemontools protocol: Direct binary control via supervise/control and supervise/status
  • systemd adapter: Unified API for systemd services on Linux
  • Real-time monitoring: Status changes via fsnotify (no polling)
  • Concurrent operations: Worker pool management via Manager
  • Zero allocations: Optimized hot paths with stack-based operations
  • Cross-platform: Linux and macOS (systemd on Linux only)
  • Development mode: Unprivileged runsvdir trees for testing
  • Multi-supervisor support: runit, s6, daemontools, and systemd

Installation

go get github.com/axondata/go-svcmgr

Optional build tags:

  • fsnotify - Enable file watching (recommended)
  • devtree_cmd - Enable dev tree helpers for spawning runsvdir
  • sv_fallback - Enable text-based status fallback (testing only)

Quick Start

package main

import (
    "context"
    "fmt"
    "log"
    "sync"
    "time"

    "github.com/axondata/go-svcmgr"
)

func main() {
    // Create client for a service
    client, err := svcmgr.New("/etc/service/web")
    if err != nil {
        log.Fatal(err)
    }

    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()

    var wg sync.WaitGroup

    // Start watching in a goroutine
    wg.Add(1)
    go func() {
        defer wg.Done()

        events, stop, err := client.Watch(ctx)
        if err != nil {
            log.Printf("Watch error: %v", err)
            return
        }
        defer stop()

        for event := range events {
            if event.Err != nil {
                log.Printf("Event error: %v", event.Err)
                continue
            }
            log.Printf("State changed: %v (PID: %d)",
                event.Status.State, event.Status.PID)
        }
    }()

    // Control the service in another goroutine
    wg.Add(1)
    go func() {
        defer wg.Done()

        // Give watcher time to start
        time.Sleep(100 * time.Millisecond)

        // Stop the service
        log.Println("Stopping service...")
        if err := client.Down(ctx); err != nil {
            log.Printf("Down error: %v", err)
            return
        }

        time.Sleep(2 * time.Second)

        // Start the service
        log.Println("Starting service...")
        if err := client.Up(ctx); err != nil {
            log.Printf("Up error: %v", err)
            return
        }

        time.Sleep(2 * time.Second)

        // Get final status
        status, err := client.Status(ctx)
        if err != nil {
            log.Printf("Status error: %v", err)
            return
        }

        fmt.Printf("Final state: %v, PID: %d, Uptime: %s\n",
            status.State, status.PID, status.Uptime)

        // Cancel context to stop watcher
        cancel()
    }()

    wg.Wait()
}

API Reference

Client (Single Service)
// Create a client
client, err := svcmgr.New("/etc/service/myapp",
    svcmgr.WithDialTimeout(3*time.Second),
    svcmgr.WithMaxAttempts(5),
    svcmgr.WithBackoff(10*time.Millisecond, 1*time.Second),
)

// Control commands
client.Up(ctx)            // Start service (send 'u')
client.Down(ctx)          // Stop service (send 'd')
client.Once(ctx)          // Run once (send 'o')
client.Term(ctx)          // Send SIGTERM (send 't')
client.Kill(ctx)          // Send SIGKILL (send 'k')
client.HUP(ctx)           // Send SIGHUP (send 'h')
client.Interrupt(ctx)     // Send SIGINT (send 'i')
client.Alarm(ctx)         // Send SIGALRM (send 'a')
client.Quit(ctx)          // Send SIGQUIT (send 'q')
client.Pause(ctx)         // Send SIGSTOP (send 'p')
client.Cont(ctx)          // Send SIGCONT (send 'c')
client.ExitSupervise(ctx) // Exit supervise (send 'x')

// Get status
status, err := client.Status(ctx)
Status Structure

See Status and State types in the API documentation.

Manager (Multiple Services)
// Create manager with worker pool
mgr := svcmgr.NewManager(
    svcmgr.WithConcurrency(10),
    svcmgr.WithTimeout(5*time.Second),
)

// Bulk operations
services := []string{
    "/etc/service/web",
    "/etc/service/db",
    "/etc/service/cache",
}

// Start all services
err := mgr.Up(ctx, services...)

// Get all statuses
statuses, err := mgr.Status(ctx, services...)
for svc, status := range statuses {
    fmt.Printf("%s: %v (PID %d)\n", svc, status.State, status.PID)
}

// Stop all services
err = mgr.Down(ctx, services...)
DevTree (Development Mode)

Build with -tags devtree_cmd to enable:

// Create dev tree
tree, err := svcmgr.NewDevTree("/tmp/my-runit")
if err != nil {
    log.Fatal(err)
}

// Initialize directories
err = tree.Ensure()

// Start runsvdir
err = tree.EnsureRunsvdir()

// Enable a service
err = tree.EnableService("myapp")

// Disable a service
err = tree.DisableService("myapp")
ServiceBuilder
builder := svcmgr.NewServiceBuilder("myapp", "/tmp/services")

builder.
    WithCmd([]string{"/usr/bin/myapp", "--port", "8080"}).
    WithCwd("/var/myapp").
    WithEnv("NODE_ENV", "production").
    WithChpst(func(c *svcmgr.ChpstBuilder) { // See https://pkg.go.dev/github.com/axondata/go-svcmgr#ChpstBuilder
        c.User = "myapp"
        c.LimitMem = 1024 * 1024 * 512  // 512MB
        c.LimitFiles = 1024
    }).
    WithSvlogd(func(s *svcmgr.SvlogdBuilder) { // See https://pkg.go.dev/github.com/axondata/go-svcmgr#SvlogdBuilder
        s.Size = 10000000  // 10MB per file
        s.Num = 10         // Keep 10 files
    })

err := builder.Build()

Compatibility with daemontools and s6

This library works with any daemontools-compatible supervision system, including:

  • runit - Full support for all operations
  • daemontools - Compatible except for Once() and Quit() operations
  • s6 - Full compatibility with all operations

The library provides factory functions for each system (see compatibility functions):

// For runit
config := svcmgr.ConfigRunit()
client, err := svcmgr.NewClientWithConfig("/etc/service/myapp", config)

// For daemontools
config := svcmgr.ConfigDaemontools()
client, err := svcmgr.NewClientWithConfig("/service/myapp", config)

// For s6
config := svcmgr.ConfigS6()
client, err := svcmgr.NewClientWithConfig("/run/service/myapp", config)

// Service builders for each system
runitBuilder := svcmgr.ServiceBuilderRunit("myapp", "/etc/service")        // See https://pkg.go.dev/github.com/axondata/go-svcmgr#ServiceBuilderRunit
dtBuilder := svcmgr.ServiceBuilderDaemontools("myapp", "/service")         // See https://pkg.go.dev/github.com/axondata/go-svcmgr#ServiceBuilderDaemontools
s6Builder := svcmgr.ServiceBuilderS6("myapp", "/run/service")              // See https://pkg.go.dev/github.com/axondata/go-svcmgr#ServiceBuilderS6
systemd Adapter (Linux only)

While systemd uses a different architecture than daemontools-family supervisors, this library provides an adapter that offers a consistent API:

// Create a service builder
builder := svcmgr.NewServiceBuilder("myapp", "")
builder.WithCmd([]string{"/usr/bin/myapp", "--config", "/etc/myapp.conf"})
builder.WithCwd("/var/lib/myapp")
builder.WithEnv("ENV_VAR", "value")
builder.WithChpst(func(c *svcmgr.ChpstConfig) {
    c.User = "myuser"
    c.LimitMem = 1024*1024*1024  // 1GB
})

// Generate and install systemd unit file
systemdBuilder := svcmgr.NewBuilderSystemd(builder)
if err := systemdBuilder.Build(); err != nil {
    log.Fatal(err)
}

// Control the service
client := svcmgr.NewClientSystemd("myapp")
if err := client.Start(context.Background()); err != nil {
    log.Fatal(err)
}

// Send signals
client.USR1(ctx)  // Send SIGUSR1 to main process
client.Term(ctx)  // Send SIGTERM to main process

Key features:

  • Generates native systemd unit files from ServiceBuilder configurations
  • Maps process limits and environment variables to systemd directives
  • Translates operations to appropriate systemctl commands
  • Sends signals directly to MainPID for precise control
  • Automatic sudo handling for non-root users
Differences between systems
Feature runit daemontools s6 systemd
Default path /etc/service /service /run/service /etc/systemd/system
Privilege tool chpst setuidgid s6-setuidgid Unit directives
Logger svlogd multilog s6-log journald
Scanner runsvdir svscan s6-svscan systemd (PID 1)
Control method Binary protocol Binary protocol Binary protocol D-Bus/systemctl
Once() support ✓ (via systemd-run)
Quit() support
USR1/USR2 support
Platform Unix-like Unix-like Unix-like Linux only

The daemontools family (runit, daemontools, s6) share a common binary protocol for supervise/control and supervise/status. The systemd adapter translates the same operations to systemctl commands and generates native unit files from shared service configurations.

Control Commands Reference

Method Byte Signal Description runit daemontools s6 systemd
Up() / Start() u - Start service (want up)
Once() o - Run service once
Down() / Stop() d - Stop service (want down)
Restart() - - Stop then start service
Term() t SIGTERM Graceful termination
Interrupt() i SIGINT Interrupt
HUP() h SIGHUP Reload configuration
Alarm() a SIGALRM Alarm signal
Quit() q SIGQUIT Quit with core dump
USR1() 1 SIGUSR1 User signal 1
USR2() 2 SIGUSR2 User signal 2
Kill() k SIGKILL Force kill
Pause() p SIGSTOP Pause process
Cont() c SIGCONT Continue process
ExitSupervise() x - Terminate supervise N/A

Status Binary Format

The 20-byte supervise/status record:

Bytes 0-7:   TAI64N seconds (big-endian uint64)
Bytes 8-11:  TAI64N nanoseconds (big-endian uint32)
Bytes 12-15: PID (big-endian uint32)
Byte 16:     Paused flag (non-zero = paused)
Byte 17:     Want flag ('u' = up, 'd' = down)
Byte 18:     Term flag (non-zero = TERM sent)
Byte 19:     Run flag (non-zero = normally up)

Error Handling

The library provides typed errors. See OpError and the error variables in the API documentation.

Testing

Unit Tests

Run the standard unit tests:

go test ./...
Integration Tests

The library includes integration tests for different supervision systems. Each requires the respective tools to be installed.

Runit Integration Tests

Tests for runit require runsv and runsvdir to be installed:

# Run all runit integration tests
go test -tags=integration -v ./...

# Or explicitly for runit
go test -tags=integration_runit -v ./...

# Run a specific integration test
go test -tags=integration -v -run TestIntegrationSingleService
Daemontools Integration Tests

Tests for daemontools require svscan and supervise to be installed:

# Run daemontools integration tests
go test -tags=integration_daemontools -v ./...
S6 Integration Tests

Tests for s6 require s6-svscan and s6-supervise to be installed:

# Run s6 integration tests
go test -tags=integration_s6 -v ./...

The runit integration tests cover:

  • Service lifecycle (start, stop, restart)
  • Signal handling (TERM, HUP, etc.)
  • Status monitoring and state transitions
  • Watch functionality with fsnotify
  • Services with different exit codes
  • ServiceBuilder generated services

Performance

Benchmarks on Apple M3 Pro (2025-09-08):

BenchmarkStatusDecode-12          32006547	 37.64 ns/op   0 B/op  0 allocs/op
BenchmarkStatusDecodeParallel-12  187062128	  9.825 ns/op  0 B/op  0 allocs/op
BenchmarkDecodeStatus-12          31235832	 37.89 ns/op   0 B/op  0 allocs/op
  • Status decode: ~38ns/op with zero allocations
  • Parallel decode: ~10ns/op when running concurrently
  • State/Op strings: <1ns/op with zero allocations
  • Control send: Sub-millisecond for local sockets
  • Watch events: Debounced at 25ms by default (configurable)

Examples

See the examples/ directory for complete examples:

  • examples/basic/ - Simple service control
  • examples/watch/ - Real-time status monitoring
  • examples/manager/ - Bulk service operations
  • examples/compat/ - Using with daemontools and s6
  • examples/devtree/ - Development environment setup

Requirements

License

Apache 2.0 - See LICENSE file for details.

Documentation

Overview

Package svcmgr provides a native Go library for controlling process supervisors including runit, s6, daemontools, and systemd without shelling out to external commands.

The core functionality centers around the Client type, which provides direct control and status operations for a single runit service:

client, err := svcmgr.New("/etc/service/myapp")
if err != nil {
    log.Fatal(err)
}

// Start the service
err = client.Up(context.Background())

// Get status
status, err := client.Status(context.Background())
fmt.Printf("Service state: %v, PID: %d\n", status.State, status.PID)

Manager for Bulk Operations

The Manager type is provided as a convenience for applications that need to control multiple services concurrently. It's particularly useful for:

  • System initialization/shutdown sequences
  • Health monitoring dashboards
  • Service orchestration tools
  • Testing frameworks that manage multiple services

If your application already has its own concurrency framework or only manages single services, you may not need the Manager. It's designed to be optional - the Client type provides all core functionality.

manager := svcmgr.NewManager(
    svcmgr.WithConcurrency(5),
    svcmgr.WithTimeout(10 * time.Second),
)

// Start multiple services concurrently
err = manager.Up(ctx, "/etc/service/web", "/etc/service/db", "/etc/service/cache")

Design Philosophy

This library prioritizes:

  • Zero external process spawning (no exec of sv/runsv)
  • Direct communication with supervise control/status endpoints
  • Zero allocations on hot paths (status decode, state strings)
  • Context-aware operations with proper timeouts
  • Type safety (no string-based operation codes)

The Manager is included because many supervisor deployments involve coordinating multiple services, and having a tested, concurrent implementation prevents users from reimplementing the same patterns. However, it remains optional - all its functionality can be replicated using Client instances directly.

Index

Constants

View Source
const (
	// SuperviseDir is the subdirectory containing runit control files
	SuperviseDir = "supervise"

	// ControlFile is the control socket/FIFO file name
	ControlFile = "control"

	// StatusFile is the binary status file name
	StatusFile = "status"

	// StatusFileSize is the exact size of the binary status record in bytes
	// Reference: https://github.com/g-pape/runit/blob/master/src/sv.c#L53
	// char svstatus[20];
	StatusFileSize = 20

	// DefaultWatchDebounce is the default debounce time for status file watching
	DefaultWatchDebounce = 25 * time.Millisecond

	// DefaultDialTimeout is the default timeout for control socket connections
	DefaultDialTimeout = 2 * time.Second

	// DefaultWriteTimeout is the default timeout for control write operations
	DefaultWriteTimeout = 1 * time.Second

	// DefaultReadTimeout is the default timeout for status read operations
	DefaultReadTimeout = 1 * time.Second

	// DefaultBackoffMin is the minimum backoff duration for retries
	DefaultBackoffMin = 10 * time.Millisecond

	// DefaultBackoffMax is the maximum backoff duration for retries
	DefaultBackoffMax = 1 * time.Second

	// DefaultMaxAttempts is the default maximum number of retry attempts
	DefaultMaxAttempts = 10
)

Runit directory and file constants

View Source
const (
	// DefaultChpstPath is the default path to the chpst binary
	DefaultChpstPath = "chpst"

	// DefaultSvlogdPath is the default path to the svlogd binary
	DefaultSvlogdPath = "svlogd"

	// DefaultRunsvdirPath is the default path to the runsvdir binary
	DefaultRunsvdirPath = "runsvdir"

	// DefaultSvPath is the default path to the sv binary (for fallback mode)
	DefaultSvPath = "sv"
)

Binary paths with defaults that can be overridden

View Source
const (
	// DirMode is the default mode for created directories
	DirMode = 0o755

	// FileMode is the default mode for created files
	FileMode = 0o644

	// ExecMode is the default mode for executable scripts
	ExecMode = 0o755
)

File modes

View Source
const (
	// OpStart is an alias for OpUp
	OpStart = OpUp
	// OpStop is an alias for OpDown
	OpStop = OpDown
	// OpRestart represents a restart operation (handled at client level)
	OpRestart Operation = iota + 100
)

Operation aliases for common commands

View Source
const (
	S6FlagUp         = 1 << 0 // bit 0: service is up
	S6FlagNormallyUp = 1 << 1 // bit 1: service normally up
	S6FlagWantUp     = 1 << 2 // bit 2: admin wants service up
	S6FlagReady      = 1 << 3 // bit 3: service sent readiness notification
	S6FlagPaused     = 1 << 4 // bit 4: service is paused
	S6FlagFinishing  = 1 << 5 // bit 5: finish script is running
)

S6 status flag bits (byte 0 of S6 status file)

View Source
const (
	RunitStatusSize = 20

	// Byte positions
	RunitTAI64Start = 0 // TAI64 seconds (8 bytes, big-endian)
	RunitTAI64End   = 8
	RunitNanoStart  = 8 // Nanoseconds (4 bytes, big-endian)
	RunitNanoEnd    = 12
	RunitPIDStart   = 12 // PID (4 bytes, little-endian)
	RunitPIDEnd     = 16
	RunitPausedFlag = 16 // Paused flag
	RunitWantFlag   = 17 // Want flag ('u' or 'd')
	RunitTermFlag   = 18 // Term flag
	RunitRunFlag    = 19 // Run flag (service has process)
)

Runit status file format (20 bytes)

View Source
const (
	DaemontoolsStatusSize = 18

	// Byte positions
	DaemontoolsTAI64Start = 0 // TAI64 seconds (8 bytes, big-endian)
	DaemontoolsTAI64End   = 8
	DaemontoolsNanoStart  = 8 // Nanoseconds (4 bytes, big-endian)
	DaemontoolsNanoEnd    = 12
	DaemontoolsPIDStart   = 12 // PID (4 bytes, little-endian)
	DaemontoolsPIDEnd     = 16
	DaemontoolsStatusFlag = 16 // Status/reserved byte
	DaemontoolsWantFlag   = 17 // Want flag ('u' or 'd')
)

Daemontools status file format (18 bytes) Based on actual observation: uses TAI64N (12 bytes) + PID (4 bytes) + flags (2 bytes)

View Source
const (
	S6StatusSizePre220  = 35 // Pre-2.20.0 format size
	S6StatusSizeCurrent = 43 // Current format size (>= 2.20.0)

	// S6MaxStatusSize is the maximum size of any S6 status format.
	// We use this when allocating buffers to ensure we can read any S6 status file version.
	// This allows us to read the actual file size and then determine which format to use for decoding.
	S6MaxStatusSize = S6StatusSizeCurrent

	// Pre-2.20.0 S6 format (35 bytes)
	// bytes 0-11:  TAI64N timestamp
	// bytes 12-23: TAI64N ready timestamp
	// bytes 24-27: reserved/zeros
	// bytes 28-31: PID (big-endian uint32)
	// bytes 32-34: flags/status
	S6TimestampStartPre220 = 0  // TAI64N timestamp start
	S6TimestampEndPre220   = 12 // TAI64N timestamp end
	S6ReadyStartPre220     = 12 // TAI64N ready timestamp start
	S6ReadyEndPre220       = 24 // TAI64N ready timestamp end
	S6PIDStartPre220       = 28 // PID start (big-endian uint32)
	S6PIDEndPre220         = 32 // PID end
	S6FlagsBytePre220      = 34 // Flags byte

	// Current S6 format (43 bytes, S6 >= 2.20.0)
	// bytes 0-11:  tain timestamp
	// bytes 12-23: tain readystamp
	// bytes 24-31: PID (big-endian uint64)
	// bytes 32-39: PGID (big-endian uint64)
	// bytes 40-41: wstat (big-endian uint16)
	// byte 42:     flags
	S6TimestampStartCurrent = 0  // tain timestamp start
	S6TimestampEndCurrent   = 12 // tain timestamp end
	S6ReadyStartCurrent     = 12 // tain readystamp start
	S6ReadyEndCurrent       = 24 // tain readystamp end
	S6PIDStartCurrent       = 24 // PID start (big-endian uint64)
	S6PIDEndCurrent         = 32 // PID end
	S6PGIDStartCurrent      = 32 // PGID start (big-endian uint64)
	S6PGIDEndCurrent        = 40 // PGID end
	S6WstatStartCurrent     = 40 // wstat start (big-endian uint16)
	S6WstatEndCurrent       = 42 // wstat end
	S6FlagsByteCurrent      = 42 // Flags byte
)

S6 status file formats S6 has two incompatible formats: - Old format (35 bytes): Older S6 versions (exact cutoff unclear, but suspect < 2.20.0 as 2.12.0.x uses this) - New format (43 bytes): Newer S6 versions (current upstream uses this, >= 2.20.0)

View Source
const (
	// TAI64Base is the TAI64 epoch offset from Unix epoch (1970-01-01 00:00:10 TAI)
	// Reference: https://github.com/g-pape/runit/blob/master/src/tai.h#L12
	// #define tai_unix(t,u) ((void) ((t)->x = 4611686018427387914ULL + (uint64) (u)))
	// This value is 2^62 + 10 seconds (TAI is 10 seconds ahead of UTC at Unix epoch)
	// Calculated as: (1 << 62) + 10
	TAI64Base = uint64(1<<62) + 10 // 4611686018427387914
)

TAI64N constants for timestamp decoding

View Source
const (
	// TAI64Offset is the TAI64 epoch offset (2^62)
	// TAI64 stores seconds since 1970-01-01 00:00:00 TAI
	TAI64Offset = uint64(1) << 62
)

TAI64 constants

View Source
const Version = "1.0.0"

Version is the current version of the go-runit library

Variables

View Source
var (
	// ErrNotSupervised indicates the service directory lacks a supervise subdirectory
	ErrNotSupervised = errors.New("runit: supervise dir missing")

	// ErrControlNotReady indicates the control socket/FIFO is not accepting connections
	ErrControlNotReady = errors.New("runit: control not accepting connections")

	// ErrTimeout indicates an operation exceeded its timeout
	ErrTimeout = errors.New("runit: timeout")

	// ErrDecode indicates the status file could not be decoded
	ErrDecode = errors.New("runit: status decode")
)

Common errors returned by runit operations

View Source
var DefaultUmask fs.FileMode = 0o022

DefaultUmask is the default umask for created files

Functions

func CheckAllToolsAvailable

func CheckAllToolsAvailable(tools ...string) bool

CheckAllToolsAvailable returns true only if all tools are available

func CheckAnyToolAvailable

func CheckAnyToolAvailable(tools ...string) bool

CheckAnyToolAvailable returns true if any of the tools are available

func CheckToolAvailable

func CheckToolAvailable(tool string) bool

CheckToolAvailable returns true if a tool is available in PATH. This is a non-skipping version for conditional logic.

func FormatDiagnostics

func FormatDiagnostics(diag *DiagnosticInfo) string

FormatDiagnostics formats diagnostic information for display

func RequireDaemontools

func RequireDaemontools(t *testing.T)

RequireDaemontools ensures all daemontools tools are available

func RequireLinux

func RequireLinux(t *testing.T)

RequireLinux skips the test if not running on Linux. Use this for Linux-specific functionality like systemd.

func RequireNotShort

func RequireNotShort(t *testing.T)

RequireNotShort skips the test if running in short mode. Use this for integration tests that take longer to run.

func RequireRoot

func RequireRoot(t *testing.T)

RequireRoot skips the test if not running as root. Use this for tests that need system-level privileges.

func RequireRunit

func RequireRunit(t *testing.T)

RequireRunit ensures all runit tools are available

func RequireS6

func RequireS6(t *testing.T)

RequireS6 ensures all s6 tools are available

func RequireSystemd

func RequireSystemd(t *testing.T)

RequireSystemd ensures systemd is available (Linux only)

func RequireTool

func RequireTool(t *testing.T, toolName string)

RequireTool skips the test if the tool is not available in PATH. This should be used for any test that depends on external binaries.

func RequireTools

func RequireTools(t *testing.T, tools ...string)

RequireTools skips the test if any of the tools are not available in PATH. This is useful for tests that need multiple tools (e.g., both runsv and sv).

func SkipIfShort

func SkipIfShort(t *testing.T, reason string)

SkipIfShort skips the test if running in short mode

func WaitForRunning

func WaitForRunning(t *testing.T, client ServiceClient, timeout time.Duration) error

WaitForRunning waits for a service to reach the running state

func WaitForRunningWithDiagnostics

func WaitForRunningWithDiagnostics(t *testing.T, client ServiceClient, serviceDir string, serviceType ServiceType, timeout time.Duration) error

WaitForRunningWithDiagnostics is an enhanced version that provides detailed diagnostics on failure

func WaitForState

func WaitForState(t *testing.T, client ServiceClient, expectedState State, timeout time.Duration) error

WaitForState waits for a service to reach a specific state

func WaitForStatusFile

func WaitForStatusFile(serviceDir string, serviceType ServiceType, timeout time.Duration) error

WaitForStatusFile waits for a valid status file to be created

func WaitForSupervise

func WaitForSupervise(serviceDir string, timeout time.Duration) error

WaitForSupervise waits for the supervise directory to be created

Types

type BuilderSystemd

type BuilderSystemd struct {
	*ServiceBuilder
	// UseSudo indicates whether to use sudo for privileged operations
	UseSudo bool
	// SudoCommand is the sudo command to use (default: "sudo")
	SudoCommand string
	// UnitDir is the directory where unit files are written (default: /etc/systemd/system)
	UnitDir string
	// SystemctlPath is the path to systemctl binary
	SystemctlPath string
}

BuilderSystemd extends ServiceBuilder to generate systemd unit files

func NewBuilderSystemd

func NewBuilderSystemd(sb *ServiceBuilder) *BuilderSystemd

NewBuilderSystemd creates a new BuilderSystemd from a ServiceBuilder

func ServiceBuilderSystemd

func ServiceBuilderSystemd(name, dir string) *BuilderSystemd

ServiceBuilderSystemd creates a service builder configured for systemd

func (*BuilderSystemd) Build

func (b *BuilderSystemd) Build() error

Build creates and installs the systemd unit file

func (*BuilderSystemd) BuildSystemdUnit

func (b *BuilderSystemd) BuildSystemdUnit() (string, error)

BuildSystemdUnit generates the systemd unit file content

func (*BuilderSystemd) BuildWithContext

func (b *BuilderSystemd) BuildWithContext(ctx context.Context) error

BuildWithContext creates and installs the systemd unit file with context

func (*BuilderSystemd) Enable

func (b *BuilderSystemd) Enable(ctx context.Context) error

Enable enables the systemd service to start on boot

func (*BuilderSystemd) Remove

func (b *BuilderSystemd) Remove(ctx context.Context) error

Remove removes the systemd unit file

func (*BuilderSystemd) WithSudo

func (b *BuilderSystemd) WithSudo(use bool, command string) *BuilderSystemd

WithSudo configures sudo usage

func (*BuilderSystemd) WithUnitDir

func (b *BuilderSystemd) WithUnitDir(dir string) *BuilderSystemd

WithUnitDir sets the systemd unit directory

type ChpstConfig

type ChpstConfig struct {
	// User to run the process as
	User string
	// Group to run the process as
	Group string
	// Nice value for process priority
	Nice int
	// IONice value for I/O priority
	IONice int
	// LimitMem sets memory limit in bytes
	LimitMem int64
	// LimitFiles sets maximum number of open files
	LimitFiles int
	// LimitProcs sets maximum number of processes
	LimitProcs int
	// LimitCPU sets CPU time limit in seconds
	LimitCPU int
	// Root changes the root directory
	Root string
}

ChpstConfig configures chpst options for process control

type ClientDaemontools

type ClientDaemontools struct {
	// ServiceDir is the canonical path to the service directory
	ServiceDir string

	// DialTimeout is the timeout for establishing control socket connections
	DialTimeout time.Duration

	// WriteTimeout is the timeout for writing control commands
	WriteTimeout time.Duration

	// ReadTimeout is the timeout for reading status information
	ReadTimeout time.Duration

	// BackoffMin is the minimum duration between retry attempts
	BackoffMin time.Duration

	// BackoffMax is the maximum duration between retry attempts
	BackoffMax time.Duration

	// MaxAttempts is the maximum number of retry attempts for control operations
	MaxAttempts int

	// WatchDebounce is the debounce duration for watch events to coalesce rapid changes
	WatchDebounce time.Duration
	// contains filtered or unexported fields
}

ClientDaemontools provides control and status operations for a daemontools service. It communicates directly with the service's supervise process through control sockets/FIFOs and status files.

func NewClientDaemontools

func NewClientDaemontools(serviceDir string) (*ClientDaemontools, error)

NewClientDaemontools creates a new ClientDaemontools for the specified service directory. It verifies the service has a supervise directory.

func (*ClientDaemontools) Alarm

func (cd *ClientDaemontools) Alarm(ctx context.Context) error

Alarm sends SIGALRM to the service process

func (*ClientDaemontools) Continue

func (cd *ClientDaemontools) Continue(ctx context.Context) error

Continue sends SIGCONT to the service process

func (*ClientDaemontools) Down

func (cd *ClientDaemontools) Down(ctx context.Context) error

Down stops the service (sets want down)

func (*ClientDaemontools) ExitSupervise

func (cd *ClientDaemontools) ExitSupervise(ctx context.Context) error

ExitSupervise terminates the supervise process for this service

func (*ClientDaemontools) HUP

func (cd *ClientDaemontools) HUP(ctx context.Context) error

HUP sends SIGHUP to the service process

func (*ClientDaemontools) Interrupt

func (cd *ClientDaemontools) Interrupt(ctx context.Context) error

Interrupt sends SIGINT to the service process

func (*ClientDaemontools) Kill

func (cd *ClientDaemontools) Kill(ctx context.Context) error

Kill sends SIGKILL to the service process

func (*ClientDaemontools) Once

func (cd *ClientDaemontools) Once(ctx context.Context) error

Once starts the service once (does not restart if it exits)

func (*ClientDaemontools) Pause

func (cd *ClientDaemontools) Pause(ctx context.Context) error

Pause sends SIGSTOP to the service process

func (*ClientDaemontools) Quit

func (cd *ClientDaemontools) Quit(_ context.Context) error

Quit sends SIGQUIT to the service process

func (*ClientDaemontools) Restart

func (cd *ClientDaemontools) Restart(ctx context.Context) error

Restart restarts the service by sending Down then Up

func (*ClientDaemontools) Start

func (cd *ClientDaemontools) Start(ctx context.Context) error

Start is an alias for Up

func (*ClientDaemontools) Status

func (cd *ClientDaemontools) Status(_ context.Context) (Status, error)

Status reads and decodes the service's binary status file. It returns typed Status information.

func (*ClientDaemontools) Stop

func (cd *ClientDaemontools) Stop(ctx context.Context) error

Stop is an alias for Down

func (*ClientDaemontools) Term

func (cd *ClientDaemontools) Term(ctx context.Context) error

Term sends SIGTERM to the service process

func (*ClientDaemontools) USR1

func (cd *ClientDaemontools) USR1(ctx context.Context) error

USR1 sends SIGUSR1 to the service process

func (*ClientDaemontools) USR2

func (cd *ClientDaemontools) USR2(ctx context.Context) error

USR2 sends SIGUSR2 to the service process

func (*ClientDaemontools) Up

func (cd *ClientDaemontools) Up(ctx context.Context) error

Up starts the service (sets want up)

func (*ClientDaemontools) Wait

func (c *ClientDaemontools) Wait(ctx context.Context, states []State) (Status, error)

Wait for ClientDaemontools

func (*ClientDaemontools) Watch

Watch for ClientDaemontools monitors the service's status file for changes

type ClientRunit

type ClientRunit struct {
	// ServiceDir is the canonical path to the service directory
	ServiceDir string

	// DialTimeout is the timeout for establishing control socket connections
	DialTimeout time.Duration

	// WriteTimeout is the timeout for writing control commands
	WriteTimeout time.Duration

	// ReadTimeout is the timeout for reading status information
	ReadTimeout time.Duration

	// BackoffMin is the minimum duration between retry attempts
	BackoffMin time.Duration

	// BackoffMax is the maximum duration between retry attempts
	BackoffMax time.Duration

	// MaxAttempts is the maximum number of retry attempts for control operations
	MaxAttempts int

	// WatchDebounce is the debounce duration for watch events to coalesce rapid changes
	WatchDebounce time.Duration
	// contains filtered or unexported fields
}

ClientRunit provides control and status operations for a runit service. It communicates directly with the service's supervise process through control sockets/FIFOs and status files, without shelling out to sv.

func NewClientRunit

func NewClientRunit(serviceDir string) (*ClientRunit, error)

NewClientRunit creates a new ClientRunit for the specified service directory. It verifies the service has a supervise directory.

func (*ClientRunit) Alarm

func (rc *ClientRunit) Alarm(ctx context.Context) error

Alarm sends SIGALRM to the service process

func (*ClientRunit) Continue

func (rc *ClientRunit) Continue(ctx context.Context) error

Continue sends SIGCONT to the service process

func (*ClientRunit) Down

func (rc *ClientRunit) Down(ctx context.Context) error

Down stops the service (sets want down)

func (*ClientRunit) ExitSupervise

func (rc *ClientRunit) ExitSupervise(ctx context.Context) error

ExitSupervise terminates the supervise process for this service

func (*ClientRunit) HUP

func (rc *ClientRunit) HUP(ctx context.Context) error

HUP sends SIGHUP to the service process

func (*ClientRunit) Interrupt

func (rc *ClientRunit) Interrupt(ctx context.Context) error

Interrupt sends SIGINT to the service process

func (*ClientRunit) Kill

func (rc *ClientRunit) Kill(ctx context.Context) error

Kill sends SIGKILL to the service process

func (*ClientRunit) Once

func (rc *ClientRunit) Once(ctx context.Context) error

Once starts the service once (does not restart if it exits)

func (*ClientRunit) Pause

func (rc *ClientRunit) Pause(ctx context.Context) error

Pause sends SIGSTOP to the service process

func (*ClientRunit) Quit

func (rc *ClientRunit) Quit(ctx context.Context) error

Quit sends SIGQUIT to the service process

func (*ClientRunit) Restart

func (rc *ClientRunit) Restart(ctx context.Context) error

Restart restarts the service by sending Down then Up

func (*ClientRunit) Start

func (rc *ClientRunit) Start(ctx context.Context) error

Start is an alias for Up

func (*ClientRunit) Status

func (rc *ClientRunit) Status(_ context.Context) (Status, error)

Status reads and decodes the service's binary status file. It returns typed Status information without shelling out to sv.

func (*ClientRunit) Stop

func (rc *ClientRunit) Stop(ctx context.Context) error

Stop is an alias for Down

func (*ClientRunit) Term

func (rc *ClientRunit) Term(ctx context.Context) error

Term sends SIGTERM to the service process

func (*ClientRunit) USR1

func (rc *ClientRunit) USR1(ctx context.Context) error

USR1 sends SIGUSR1 to the service process

func (*ClientRunit) USR2

func (rc *ClientRunit) USR2(ctx context.Context) error

USR2 sends SIGUSR2 to the service process

func (*ClientRunit) Up

func (rc *ClientRunit) Up(ctx context.Context) error

Up starts the service (sets want up)

func (*ClientRunit) Wait

func (c *ClientRunit) Wait(ctx context.Context, states []State) (Status, error)

Wait blocks until the service reaches one of the specified states or context is cancelled. Returns the status when one of the states is reached, or an error if one occurred. If states is nil or empty, waits for any state change.

Example:

// Wait for any change
status, err := client.Wait(ctx, nil)

// Wait for specific states
status, err := client.Wait(ctx, []State{StateRunning, StateDown})

func (*ClientRunit) Watch

func (c *ClientRunit) Watch(ctx context.Context) (<-chan WatchEvent, WatchCleanupFunc, error)

Watch for ClientRunit monitors the service's status file for changes

type ClientS6

type ClientS6 struct {
	// ServiceDir is the canonical path to the service directory
	ServiceDir string

	// DialTimeout is the timeout for establishing control socket connections
	DialTimeout time.Duration

	// WriteTimeout is the timeout for writing control commands
	WriteTimeout time.Duration

	// ReadTimeout is the timeout for reading status information
	ReadTimeout time.Duration

	// BackoffMin is the minimum duration between retry attempts
	BackoffMin time.Duration

	// BackoffMax is the maximum duration between retry attempts
	BackoffMax time.Duration

	// MaxAttempts is the maximum number of retry attempts for control operations
	MaxAttempts int

	// WatchDebounce is the debounce duration for watch events to coalesce rapid changes
	WatchDebounce time.Duration
	// contains filtered or unexported fields
}

ClientS6 provides control and status operations for an s6 service. It communicates directly with the service's s6-supervise process through control sockets/FIFOs and status files.

func NewClientS6

func NewClientS6(serviceDir string) (*ClientS6, error)

NewClientS6 creates a new ClientS6 for the specified service directory. It verifies the service has a supervise directory.

func (*ClientS6) Alarm

func (cs *ClientS6) Alarm(ctx context.Context) error

Alarm sends SIGALRM to the service process

func (*ClientS6) Continue

func (cs *ClientS6) Continue(_ context.Context) error

Continue sends SIGCONT to the service process

func (*ClientS6) Down

func (cs *ClientS6) Down(ctx context.Context) error

Down stops the service (sets want down)

func (*ClientS6) ExitSupervise

func (cs *ClientS6) ExitSupervise(ctx context.Context) error

ExitSupervise terminates the supervise process for this service

func (*ClientS6) HUP

func (cs *ClientS6) HUP(ctx context.Context) error

HUP sends SIGHUP to the service process

func (*ClientS6) Interrupt

func (cs *ClientS6) Interrupt(ctx context.Context) error

Interrupt sends SIGINT to the service process

func (*ClientS6) Kill

func (cs *ClientS6) Kill(ctx context.Context) error

Kill sends SIGKILL to the service process

func (*ClientS6) Once

func (cs *ClientS6) Once(ctx context.Context) error

Once starts the service once (does not restart if it exits)

func (*ClientS6) Pause

func (cs *ClientS6) Pause(_ context.Context) error

Pause sends SIGSTOP to the service process

func (*ClientS6) Quit

func (cs *ClientS6) Quit(ctx context.Context) error

Quit sends SIGQUIT to the service process

func (*ClientS6) Restart

func (cs *ClientS6) Restart(ctx context.Context) error

Restart restarts the service by sending Down then Up

func (*ClientS6) Start

func (cs *ClientS6) Start(ctx context.Context) error

Start is an alias for Up

func (*ClientS6) Status

func (cs *ClientS6) Status(_ context.Context) (Status, error)

Status reads and decodes the service's binary status file. It returns typed Status information.

func (*ClientS6) Stop

func (cs *ClientS6) Stop(ctx context.Context) error

Stop is an alias for Down

func (*ClientS6) Term

func (cs *ClientS6) Term(ctx context.Context) error

Term sends SIGTERM to the service process

func (*ClientS6) USR1

func (cs *ClientS6) USR1(ctx context.Context) error

USR1 sends SIGUSR1 to the service process

func (*ClientS6) USR2

func (cs *ClientS6) USR2(ctx context.Context) error

USR2 sends SIGUSR2 to the service process

func (*ClientS6) Up

func (cs *ClientS6) Up(ctx context.Context) error

Up starts the service (sets want up)

func (*ClientS6) Wait

func (c *ClientS6) Wait(ctx context.Context, states []State) (Status, error)

Wait for ClientS6

func (*ClientS6) Watch

func (c *ClientS6) Watch(ctx context.Context) (<-chan WatchEvent, WatchCleanupFunc, error)

Watch for ClientS6 monitors the service's status file for changes

type ClientSystemd

type ClientSystemd struct {
	// ServiceName is the name of the systemd service (without .service suffix)
	ServiceName string

	// UseSudo indicates whether to use sudo for systemctl commands
	UseSudo bool

	// SudoCommand is the sudo command to use (default: "sudo")
	SudoCommand string

	// SystemctlPath is the path to systemctl binary
	SystemctlPath string

	// Timeout for systemctl operations
	Timeout time.Duration

	// WatchInterval is the polling interval for Watch when other methods unavailable
	WatchInterval time.Duration
}

ClientSystemd provides control operations for systemd services It implements a similar interface to the runit Client but uses systemctl

func NewClientSystemd

func NewClientSystemd(serviceName string) *ClientSystemd

NewClientSystemd creates a new ClientSystemd for the specified service

func NewClientSystemdWithConfig

func NewClientSystemdWithConfig(serviceName string, config *ServiceConfig) *ClientSystemd

NewClientSystemdWithConfig creates a new systemd client with the specified configuration

func (*ClientSystemd) Alarm

func (c *ClientSystemd) Alarm(ctx context.Context) error

Alarm sends SIGALRM to the service process

func (*ClientSystemd) Continue

func (c *ClientSystemd) Continue(ctx context.Context) error

Continue sends SIGCONT to the service process

func (*ClientSystemd) Disable

func (c *ClientSystemd) Disable(ctx context.Context) error

Disable disables the service from starting on boot

func (*ClientSystemd) Down

func (c *ClientSystemd) Down(ctx context.Context) error

Down stops the service (sets want down)

func (*ClientSystemd) Enable

func (c *ClientSystemd) Enable(ctx context.Context) error

Enable enables the service to start on boot

func (*ClientSystemd) ExitSupervise

func (c *ClientSystemd) ExitSupervise(ctx context.Context) error

ExitSupervise stops and disables the service (no direct systemd equivalent)

func (*ClientSystemd) HUP

func (c *ClientSystemd) HUP(ctx context.Context) error

HUP sends SIGHUP directly to the service's main process. This bypasses systemd's reload mechanism and sends the signal directly. For services with ExecReload= configured, consider using Reload() instead.

func (*ClientSystemd) Interrupt

func (c *ClientSystemd) Interrupt(ctx context.Context) error

Interrupt sends SIGINT to the service process

func (*ClientSystemd) IsRunning

func (c *ClientSystemd) IsRunning(ctx context.Context) (bool, error)

IsRunning checks if the service is currently running

func (*ClientSystemd) Kill

func (c *ClientSystemd) Kill(ctx context.Context) error

Kill sends SIGKILL to the service's main process

func (*ClientSystemd) Once

func (c *ClientSystemd) Once(ctx context.Context) error

Once starts the service once (does not restart if it exits)

func (*ClientSystemd) Pause

func (c *ClientSystemd) Pause(ctx context.Context) error

Pause sends SIGSTOP to the service process

func (*ClientSystemd) Quit

func (c *ClientSystemd) Quit(ctx context.Context) error

Quit sends SIGQUIT to the service process

func (*ClientSystemd) Reload

func (c *ClientSystemd) Reload(ctx context.Context) error

Reload attempts to reload the service using systemctl reload. This uses the service's ExecReload= configuration if defined. If the service doesn't support reload, this will return an error. Note: This is NOT the same as sending SIGHUP - use HUP() for that.

func (*ClientSystemd) Restart

func (c *ClientSystemd) Restart(ctx context.Context) error

Restart restarts the service

func (*ClientSystemd) SendOperation

func (c *ClientSystemd) SendOperation(ctx context.Context, op Operation) error

SendOperation maps runit operations to systemd commands

func (*ClientSystemd) Signal

func (c *ClientSystemd) Signal(ctx context.Context, sig string) error

Signal sends a custom signal to the service's main process

func (*ClientSystemd) Start

func (c *ClientSystemd) Start(ctx context.Context) error

Start starts the service (alias for Up)

func (*ClientSystemd) Status

func (c *ClientSystemd) Status(ctx context.Context) (Status, error)

Status returns the status of the service in runit format for interface compatibility

func (*ClientSystemd) StatusSystemd

func (c *ClientSystemd) StatusSystemd(ctx context.Context) (*StatusSystemd, error)

StatusSystemd returns the systemd-specific status of the service

func (*ClientSystemd) Stop

func (c *ClientSystemd) Stop(ctx context.Context) error

Stop stops the service (alias for Down)

func (*ClientSystemd) Term

func (c *ClientSystemd) Term(ctx context.Context) error

Term sends SIGTERM to the service's main process

func (*ClientSystemd) USR1

func (c *ClientSystemd) USR1(ctx context.Context) error

USR1 sends SIGUSR1 to the service

func (*ClientSystemd) USR2

func (c *ClientSystemd) USR2(ctx context.Context) error

USR2 sends SIGUSR2 to the service

func (*ClientSystemd) Up

func (c *ClientSystemd) Up(ctx context.Context) error

Up starts the service (sets want up)

func (*ClientSystemd) Wait

func (c *ClientSystemd) Wait(ctx context.Context, states []State) (Status, error)

Wait for ClientSystemd

func (*ClientSystemd) WaitForState

func (c *ClientSystemd) WaitForState(ctx context.Context, targetState string, timeout time.Duration) error

WaitForState waits for the service to reach a specific state

func (*ClientSystemd) Watch

func (c *ClientSystemd) Watch(ctx context.Context) (<-chan WatchEvent, WatchCleanupFunc, error)

Watch monitors the systemd service for state changes

func (*ClientSystemd) WithSudo

func (c *ClientSystemd) WithSudo(use bool, command string) *ClientSystemd

WithSudo configures sudo usage

func (*ClientSystemd) WithTimeout

func (c *ClientSystemd) WithTimeout(d time.Duration) *ClientSystemd

WithTimeout sets the timeout for operations

type ConfigSvlogd

type ConfigSvlogd struct {
	// Size is the maximum size of current log file in bytes
	Size int64
	// Num is the number of old log files to keep
	Num int
	// Timeout is the maximum age of current log file in seconds
	Timeout int
	// Processor is an optional processor script for log files
	Processor string
	// Config contains additional svlogd configuration lines
	Config []string
	// Timestamp adds timestamps to log lines
	Timestamp bool
	// Replace replaces non-printable characters
	Replace bool
	// Prefix adds a prefix to each log line
	Prefix string
}

ConfigSvlogd configures svlogd logging options

type DaemontoolsStateParser

type DaemontoolsStateParser struct{}

DaemontoolsStateParser parses Daemontools status files (18 bytes)

func (*DaemontoolsStateParser) Name

func (p *DaemontoolsStateParser) Name() string

Name returns the parser name

func (*DaemontoolsStateParser) Parse

func (p *DaemontoolsStateParser) Parse(data []byte) (Status, error)

Parse parses the status data and returns a Status

func (*DaemontoolsStateParser) ValidateSize

func (p *DaemontoolsStateParser) ValidateSize(size int) bool

ValidateSize checks if the status file size is valid

type DevTree

type DevTree struct {
	Base string
}

DevTree represents a development runit service tree (stub implementation)

func NewDevTree

func NewDevTree(_ string) (*DevTree, error)

NewDevTree creates a new DevTree (stub implementation)

func (*DevTree) DisableService

func (d *DevTree) DisableService(_ string) error

DisableService disables a service in the dev tree

func (*DevTree) EnableService

func (d *DevTree) EnableService(_ string) error

EnableService enables a service in the dev tree

func (*DevTree) EnabledDir

func (d *DevTree) EnabledDir() string

EnabledDir returns the enabled services directory path

func (*DevTree) Ensure

func (d *DevTree) Ensure() error

Ensure ensures the dev tree structure exists

func (*DevTree) EnsureRunsvdir

func (d *DevTree) EnsureRunsvdir() error

EnsureRunsvdir ensures runsvdir is running for the dev tree

func (*DevTree) LogDir

func (d *DevTree) LogDir() string

LogDir returns the log directory path

func (*DevTree) PIDFile

func (d *DevTree) PIDFile() string

PIDFile returns the PID file path

func (*DevTree) ServicesDir

func (d *DevTree) ServicesDir() string

ServicesDir returns the services directory path

func (*DevTree) StopRunsvdir

func (d *DevTree) StopRunsvdir() error

StopRunsvdir stops the runsvdir process for the dev tree

type DiagnosticInfo

type DiagnosticInfo struct {
	ServiceDir   string
	ServiceType  ServiceType
	StatusFile   string
	RunScript    string
	LogScript    string
	StatusHex    string
	StatusError  error
	RunContent   string
	LogContent   string
	ProcessInfo  string
	LastLogLines []string
}

DiagnosticInfo contains detailed diagnostic information about a service failure

func CollectServiceDiagnostics

func CollectServiceDiagnostics(serviceDir string, serviceType ServiceType) (*DiagnosticInfo, error)

CollectServiceDiagnostics gathers comprehensive diagnostic information for debugging

type Flags

type Flags struct {
	// WantUp indicates the service is configured to be up
	WantUp bool
	// WantDown indicates the service is configured to be down
	WantDown bool
	// NormallyUp indicates the service should be started on boot
	NormallyUp bool
}

Flags represents service configuration flags from the status file

type Manager

type Manager struct {
	// Concurrency is the maximum number of concurrent operations
	Concurrency int
	// Timeout is the per-operation timeout
	Timeout time.Duration
}

Manager handles operations on multiple runit services concurrently. It provides bulk operations with configurable concurrency and timeouts.

func NewManager

func NewManager(opts ...ManagerOption) *Manager

NewManager creates a new Manager with default settings

func (*Manager) Down

func (m *Manager) Down(ctx context.Context, services ...string) error

Down stops the specified services

func (*Manager) Kill

func (m *Manager) Kill(ctx context.Context, services ...string) error

Kill sends SIGKILL to the specified services

func (*Manager) Status

func (m *Manager) Status(ctx context.Context, services ...string) (map[string]Status, error)

Status retrieves the status of the specified services

func (*Manager) Term

func (m *Manager) Term(ctx context.Context, services ...string) error

Term sends SIGTERM to the specified services

func (*Manager) Up

func (m *Manager) Up(ctx context.Context, services ...string) error

Up starts the specified services

type ManagerOption

type ManagerOption func(*Manager)

ManagerOption configures a Manager

func WithConcurrency

func WithConcurrency(n int) ManagerOption

WithConcurrency sets the maximum number of concurrent operations

func WithTimeout

func WithTimeout(d time.Duration) ManagerOption

WithTimeout sets the per-operation timeout

type MockSupervisor

type MockSupervisor struct {
	ServiceDir   string
	SuperviseDir string
	ControlFile  string
	StatusFile   string
	ServiceType  ServiceType // Track which supervision system we're mocking
}

MockSupervisor creates a fake supervise directory structure for testing This allows tests to run without actual supervisor processes

func CreateMockService

func CreateMockService(serviceName string, config *ServiceConfig) (serviceDir string, mock *MockSupervisor, cleanup func(), err error)

CreateMockService creates a service with a mock supervisor for testing

func NewMockSupervisor

func NewMockSupervisor(serviceDir string) (*MockSupervisor, error)

NewMockSupervisor creates a mock supervise directory for testing

func NewMockSupervisorWithType

func NewMockSupervisorWithType(serviceDir string, serviceType ServiceType) (*MockSupervisor, error)

NewMockSupervisorWithType creates a mock supervise directory for a specific supervision system

func (*MockSupervisor) Cleanup

func (m *MockSupervisor) Cleanup() error

Cleanup removes the mock supervise directory

func (*MockSupervisor) UpdateStatus

func (m *MockSupervisor) UpdateStatus(running bool, pid int) error

UpdateStatus updates the mock status file

type MultiError

type MultiError struct {
	// Errors contains all accumulated errors
	Errors []error
}

MultiError aggregates multiple errors from bulk operations

func (*MultiError) Add

func (m *MultiError) Add(err error)

Add appends an error to the collection if it's not nil

func (*MultiError) Err

func (m *MultiError) Err() error

Err returns nil if no errors occurred, otherwise returns the MultiError itself

func (*MultiError) Error

func (m *MultiError) Error() string

Error returns a summary of the accumulated errors

type OpError

type OpError struct {
	// Op is the operation that failed
	Op Operation
	// Path is the file path involved in the operation
	Path string
	// Err is the underlying error
	Err error
}

OpError represents an error from a runit operation

func (*OpError) Error

func (e *OpError) Error() string

Error returns a formatted error message

func (*OpError) Unwrap

func (e *OpError) Unwrap() error

Unwrap returns the underlying error for error chain inspection

type Operation

type Operation int

Operation represents a control operation type

const (
	// OpUnknown represents an unknown operation
	OpUnknown Operation = iota
	// OpUp starts the service (want up)
	OpUp
	// OpOnce starts the service once
	OpOnce
	// OpDown stops the service (want down)
	OpDown
	// OpTerm sends SIGTERM to the service
	OpTerm
	// OpInterrupt sends SIGINT to the service
	OpInterrupt
	// OpHUP sends SIGHUP to the service
	OpHUP
	// OpAlarm sends SIGALRM to the service
	OpAlarm
	// OpQuit sends SIGQUIT to the service
	OpQuit
	// OpKill sends SIGKILL to the service
	OpKill
	// OpPause sends SIGSTOP to the service
	OpPause
	// OpCont sends SIGCONT to the service
	OpCont
	// OpUSR1 sends SIGUSR1 to the service
	OpUSR1
	// OpUSR2 sends SIGUSR2 to the service
	OpUSR2
	// OpExit terminates the supervise process
	OpExit
	// OpStatus represents a status query operation
	OpStatus
)

func (Operation) Byte

func (op Operation) Byte() byte

Byte returns the control byte for this operation

func (Operation) String

func (op Operation) String() string

String returns the string representation of an Operation

type RunitStateParser

type RunitStateParser struct{}

RunitStateParser parses Runit status files (20 bytes)

func (*RunitStateParser) Name

func (p *RunitStateParser) Name() string

Name returns the parser name

func (*RunitStateParser) Parse

func (p *RunitStateParser) Parse(data []byte) (Status, error)

Parse parses the status data and returns a Status

func (*RunitStateParser) ValidateSize

func (p *RunitStateParser) ValidateSize(size int) bool

ValidateSize checks if the status file size is valid

type S6FormatVersion

type S6FormatVersion int

S6FormatVersion represents the S6 status file format version

const (
	// S6FormatUnknown indicates the format could not be determined
	S6FormatUnknown S6FormatVersion = iota
	// S6FormatPre220 is the old 35-byte format (S6 < 2.20.x)
	S6FormatPre220
	// S6FormatCurrent is the new 43-byte format (S6 >= 2.20.0)
	S6FormatCurrent
)

type S6StateParserCurrent

type S6StateParserCurrent struct{}

S6StateParserCurrent parses S6 status files for versions >= 2.20.0 (43 bytes)

func (*S6StateParserCurrent) Name

func (p *S6StateParserCurrent) Name() string

Name returns the parser name

func (*S6StateParserCurrent) Parse

func (p *S6StateParserCurrent) Parse(data []byte) (Status, error)

Parse parses the status data and returns a Status

func (*S6StateParserCurrent) ValidateSize

func (p *S6StateParserCurrent) ValidateSize(size int) bool

ValidateSize checks if the status file size is valid

type S6StateParserPre220

type S6StateParserPre220 struct{}

S6StateParserPre220 parses S6 status files for versions < 2.20.0 (35 bytes)

func (*S6StateParserPre220) Name

func (p *S6StateParserPre220) Name() string

Name returns the parser name

func (*S6StateParserPre220) Parse

func (p *S6StateParserPre220) Parse(data []byte) (Status, error)

Parse parses the status data and returns a Status

func (*S6StateParserPre220) ValidateSize

func (p *S6StateParserPre220) ValidateSize(size int) bool

ValidateSize checks if the status file size is valid

type ServiceBuilder

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

ServiceBuilder provides a fluent interface for creating runit service directories with run scripts, environment variables, logging, and process control settings.

func NewServiceBuilder

func NewServiceBuilder(name, dir string) *ServiceBuilder

NewServiceBuilder creates a new ServiceBuilder with default settings

func NewServiceBuilderWithConfig

func NewServiceBuilderWithConfig(name, dir string, config *ServiceConfig) *ServiceBuilder

NewServiceBuilderWithConfig creates a service builder for the specified supervision system

func ServiceBuilderDaemontools

func ServiceBuilderDaemontools(name, dir string) *ServiceBuilder

ServiceBuilderDaemontools creates a service builder configured for daemontools

func ServiceBuilderRunit

func ServiceBuilderRunit(name, dir string) *ServiceBuilder

ServiceBuilderRunit creates a service builder configured for runit

func ServiceBuilderS6

func ServiceBuilderS6(name, dir string) *ServiceBuilder

ServiceBuilderS6 creates a service builder configured for s6

func (*ServiceBuilder) Build

func (b *ServiceBuilder) Build() error

Build creates the service directory structure and scripts

func (*ServiceBuilder) Config

func (b *ServiceBuilder) Config() *ServiceBuilderConfig

Config returns a copy of the current configuration

func (*ServiceBuilder) WithChpst

func (b *ServiceBuilder) WithChpst(fn func(*ChpstConfig)) *ServiceBuilder

WithChpst configures process control settings

func (*ServiceBuilder) WithChpstPath

func (b *ServiceBuilder) WithChpstPath(path string) *ServiceBuilder

WithChpstPath sets the path to the chpst binary

func (*ServiceBuilder) WithCmd

func (b *ServiceBuilder) WithCmd(cmd []string) *ServiceBuilder

WithCmd sets the command to execute

func (*ServiceBuilder) WithCwd

func (b *ServiceBuilder) WithCwd(cwd string) *ServiceBuilder

WithCwd sets the working directory

func (*ServiceBuilder) WithEnv

func (b *ServiceBuilder) WithEnv(key, value string) *ServiceBuilder

WithEnv adds an environment variable

func (*ServiceBuilder) WithEnvMap

func (b *ServiceBuilder) WithEnvMap(env map[string]string) *ServiceBuilder

WithEnvMap adds multiple environment variables from a map

func (*ServiceBuilder) WithFinish

func (b *ServiceBuilder) WithFinish(cmd []string) *ServiceBuilder

WithFinish sets the command to run when the service stops

func (*ServiceBuilder) WithStderrPath

func (b *ServiceBuilder) WithStderrPath(path string) *ServiceBuilder

WithStderrPath sets a separate path for stderr output

func (*ServiceBuilder) WithSvlogd

func (b *ServiceBuilder) WithSvlogd(fn func(*ConfigSvlogd)) *ServiceBuilder

WithSvlogd configures logging settings

func (*ServiceBuilder) WithSvlogdPath

func (b *ServiceBuilder) WithSvlogdPath(path string) *ServiceBuilder

WithSvlogdPath sets the path to the svlogd binary

func (*ServiceBuilder) WithUmask

func (b *ServiceBuilder) WithUmask(umask fs.FileMode) *ServiceBuilder

WithUmask sets the file mode creation mask

type ServiceBuilderConfig

type ServiceBuilderConfig struct {
	// Name is the service name
	Name string
	// Dir is the base directory where the service will be created
	Dir string
	// Cmd is the command and arguments to execute
	Cmd []string
	// Cwd is the working directory for the service
	Cwd string
	// Umask sets the file mode creation mask
	Umask fs.FileMode
	// Env contains environment variables for the service
	Env map[string]string
	// Chpst configures process limits and user context
	Chpst *ChpstConfig
	// Svlogd configures logging
	Svlogd *ConfigSvlogd
	// Finish is the command to run when the service stops
	Finish []string
	// StderrPath is an optional path to redirect stderr (if different from stdout)
	StderrPath string
	// ChpstPath is the path to the chpst binary
	ChpstPath string
	// SvlogdPath is the path to the svlogd binary
	SvlogdPath string
}

ServiceBuilderConfig represents the configuration for a service This struct contains all the settings that can be configured for a service

func (*ServiceBuilderConfig) Clone

Clone creates a deep copy of the ServiceBuilderConfig

type ServiceClient

type ServiceClient interface {
	// Basic operations
	Up(ctx context.Context) error
	Down(ctx context.Context) error
	Status(ctx context.Context) (Status, error)

	// Signal operations
	Term(ctx context.Context) error
	Kill(ctx context.Context) error
	HUP(ctx context.Context) error
	Alarm(ctx context.Context) error
	Interrupt(ctx context.Context) error
	Quit(ctx context.Context) error
	USR1(ctx context.Context) error
	USR2(ctx context.Context) error

	// Control operations
	Once(ctx context.Context) error
	Pause(ctx context.Context) error
	Continue(ctx context.Context) error

	// Aliases
	Start(ctx context.Context) error // Alias for Up
	Stop(ctx context.Context) error  // Alias for Down
	Restart(ctx context.Context) error

	// Supervision control
	ExitSupervise(ctx context.Context) error

	// Watch monitors the service's status for changes
	// Returns a channel of events and a stop function
	Watch(ctx context.Context) (<-chan WatchEvent, WatchCleanupFunc, error)

	// Wait blocks until the service reaches one of the specified states
	// If states is nil or empty, waits for any status change
	Wait(ctx context.Context, states []State) (Status, error)
}

ServiceClient is the main interface all supervision clients implement. It provides a unified API for controlling services across different supervision systems (runit, daemontools, s6, systemd).

func NewClient

func NewClient(serviceDir string, serviceType ServiceType) (ServiceClient, error)

NewClient creates a ServiceClient based on the detected or specified supervision system

type ServiceConfig

type ServiceConfig struct {
	// Type specifies which supervision system this is for
	Type ServiceType
	// ServiceDir is the base service directory (e.g., /etc/service, /service, /run/service)
	ServiceDir string
	// ChpstPath is the path to the privilege/resource control tool
	ChpstPath string
	// LoggerPath is the path to the logger tool
	LoggerPath string
	// RunsvdirPath is the path to the service scanner
	RunsvdirPath string
	// SupportedOps contains the set of supported operations
	SupportedOps map[Operation]struct{}
}

ServiceConfig contains configuration for different supervision systems

func ConfigDaemontools

func ConfigDaemontools() *ServiceConfig

ConfigDaemontools returns the default configuration for daemontools

func ConfigRunit

func ConfigRunit() *ServiceConfig

ConfigRunit returns the default configuration for runit

func ConfigS6

func ConfigS6() *ServiceConfig

ConfigS6 returns the default configuration for s6

func ConfigSystemd

func ConfigSystemd() *ServiceConfig

ConfigSystemd returns the default configuration for systemd

func (*ServiceConfig) IsOperationSupported

func (c *ServiceConfig) IsOperationSupported(op Operation) bool

IsOperationSupported checks if an operation is supported by this service type

type ServiceType

type ServiceType int

ServiceType represents the type of service supervision system

const (
	// ServiceTypeUnknown represents an unknown supervision system
	ServiceTypeUnknown ServiceType = iota
	// ServiceTypeRunit represents runit supervision
	ServiceTypeRunit
	// ServiceTypeDaemontools represents daemontools supervision
	ServiceTypeDaemontools
	// ServiceTypeS6 represents s6 supervision
	ServiceTypeS6
	// ServiceTypeSystemd represents systemd supervision
	ServiceTypeSystemd
)

func (ServiceType) String

func (st ServiceType) String() string

String returns the string representation of ServiceType

type State

type State int

State represents the current state of a runit service

const (
	// StateUnknown indicates the state could not be determined
	StateUnknown State = iota
	// StateDown indicates the service is down and wants to be down
	StateDown
	// StateStarting indicates the service wants to be up but is not running yet
	StateStarting
	// StateRunning indicates the service is running and wants to be up
	StateRunning
	// StatePaused indicates the service is paused (SIGSTOP)
	StatePaused
	// StateStopping indicates the service is running but wants to be down
	StateStopping
	// StateFinishing indicates the finish script is executing
	StateFinishing
	// StateCrashed indicates the service is down but wants to be up
	StateCrashed
	// StateExited indicates the supervise process has exited
	StateExited
)

func (State) String

func (s State) String() string

String returns the string representation of the state

type StateParser

type StateParser interface {
	// Parse parses the raw status file data into a Status struct
	Parse(data []byte) (Status, error)
	// ValidateSize checks if the data size is valid for this parser
	ValidateSize(size int) bool
	// Name returns the name of this parser for debugging
	Name() string
}

StateParser defines the interface for parsing supervision system status files

func GetStateParser

func GetStateParser(serviceType ServiceType, dataSize int) (StateParser, error)

GetStateParser returns the appropriate parser for the given service type and data size

type Status

type Status struct {
	// State is the inferred service state
	State State
	// PID is the process ID of the service (0 if not running)
	PID int
	// Since is the timestamp when the service entered its current state
	Since time.Time
	// Uptime is the duration since the service entered its current state.
	// This field provides a snapshot of the uptime at the moment of status read.
	// Note: This value becomes stale immediately after reading as time progresses.
	// It's included for convenience and compatibility with sv output format.
	// For accurate time calculations, use Since with time.Since(status.Since).
	Uptime time.Duration
	// Ready indicates if the service has signaled readiness (S6 and potentially systemd)
	// For S6: Set when the service has sent a readiness notification
	// For other systems: May indicate similar readiness states if supported
	Ready bool
	// ReadySince is the timestamp when the service became ready (if available)
	// Only populated for S6 currently, zero value for other systems
	ReadySince time.Time
	// Flags contains service configuration flags
	Flags Flags
	// Raw contains the original 20-byte status record as an array (stack allocated)
	Raw [StatusFileSize]byte
	// S6Format indicates which S6 format version was detected (only set for S6 status files)
	S6Format S6FormatVersion
}

Status represents the decoded state of a runit service

func DecodeStatusDaemontools

func DecodeStatusDaemontools(data []byte) (Status, error)

DecodeStatusDaemontools decodes an 18-byte daemontools status file

func DecodeStatusRunit

func DecodeStatusRunit(data []byte) (Status, error)

DecodeStatusRunit decodes a 20-byte runit status file

func DecodeStatusS6

func DecodeStatusS6(data []byte) (Status, error)

DecodeStatusS6 decodes an s6 status file (35 bytes)

type StatusSystemd

type StatusSystemd struct {
	// ActiveState is the active state (active, inactive, failed, etc.)
	ActiveState string

	// SubState is the sub state (running, dead, exited, etc.)
	SubState string

	// LoadState is the load state (loaded, not-found, error, etc.)
	LoadState string

	// Running indicates if the service is currently running
	Running bool

	// MainPID is the main process ID (0 if not running)
	MainPID int

	// StartTime is when the service was started
	StartTime time.Time

	// Uptime is how long the service has been running
	Uptime time.Duration

	// Result is the result of the last run (success, exit-code, signal, etc.)
	Result string

	// Properties contains all properties returned by systemctl show
	Properties map[string]string
}

StatusSystemd represents the status of a systemd service

func (*StatusSystemd) MapToStatus

func (s *StatusSystemd) MapToStatus() *Status

MapToStatus converts StatusSystemd to runit Status for compatibility

func (*StatusSystemd) String

func (s *StatusSystemd) String() string

String returns a human-readable status string

type SupervisionSystem

type SupervisionSystem struct {
	Name      string
	Type      ServiceType
	Available bool
	Config    *ServiceConfig
	Setup     func() error
	Teardown  func() error
}

SupervisionSystem represents a supervision system to test

type SupervisorProcess

type SupervisorProcess struct {
	Type       ServiceType
	ServiceDir string
	Cmd        *exec.Cmd
	Started    bool
}

SupervisorProcess manages a supervisor process for testing

func CreateTestServiceWithSupervisor

func CreateTestServiceWithSupervisor(ctx context.Context, sys SupervisionSystem, serviceName string, _ *TestLogger) (client interface{}, supervisor *SupervisorProcess, cleanup func() error, err error)

CreateTestServiceWithSupervisor creates a service and starts its supervisor

func StartSupervisor

func StartSupervisor(ctx context.Context, serviceType ServiceType, serviceDir string) (*SupervisorProcess, error)

StartSupervisor starts the appropriate supervisor process for the service type

func (*SupervisorProcess) Stop

func (sp *SupervisorProcess) Stop() error

Stop stops the supervisor process

type TestLogger

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

TestLogger handles logging with timestamps

func NewTestLogger

func NewTestLogger(t *testing.T, logFile string, verbose bool) (*TestLogger, error)

NewTestLogger creates a new test logger instance

func (*TestLogger) Close

func (l *TestLogger) Close()

Close closes the log file if it's open

func (*TestLogger) GetLogs

func (l *TestLogger) GetLogs() []string

GetLogs returns all logged messages

func (*TestLogger) Log

func (l *TestLogger) Log(format string, args ...interface{})

Log writes a log message to both test output and log file

type VersionInfo

type VersionInfo struct {
	// Version is the semantic version
	Version string
	// Protocol is the runit protocol version supported
	Protocol string
	// Compatible indicates compatibility with daemontools
	Compatible bool
}

VersionInfo contains detailed version information

func GetVersion

func GetVersion() VersionInfo

GetVersion returns the current version information

type WatchCleanupFunc

type WatchCleanupFunc func() error

WatchCleanupFunc is a function that cleans up watch resources

type WatchEvent

type WatchEvent struct {
	Status Status
	Err    error
}

WatchEvent represents a status change event from watching a service

Directories

Path Synopsis
examples
basic command
Package main provides a basic example of using the go-runit library.
Package main provides a basic example of using the go-runit library.
compat command
Package main demonstrates using go-runit with different supervision systems (runit, daemontools, s6) through the factory functions.
Package main demonstrates using go-runit with different supervision systems (runit, daemontools, s6) through the factory functions.
manager command
Package main provides an example of managing multiple runit services.
Package main provides an example of managing multiple runit services.
wait command
Example demonstrating the Wait() interface for blocking until status changes
Example demonstrating the Wait() interface for blocking until status changes
watch command
Package main provides an example of watching service status changes.
Package main provides an example of watching service status changes.
internal
unix
Package unix provides platform-specific Unix constants.
Package unix provides platform-specific Unix constants.

Jump to

Keyboard shortcuts

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