Go SDK
The Runix Go SDK (github.com/runixio/runix/sdk) lets you embed a full process supervisor directly into your Go application — no CLI binary or separate daemon required.
Installation
go get github.com/runixio/runix/sdk
Architecture
The SDK wraps internal/supervisor directly — the same code path as the CLI. No subprocess spawning, no daemon required.
Quick Start
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/runixio/runix/sdk"
)
func main() {
mgr, err := sdk.New(sdk.Config{
LogDir: "/tmp/myapp-runix",
})
if err != nil {
log.Fatal(err)
}
defer mgr.Close()
ctx := context.Background()
// Start a Python script.
id, err := mgr.AddProcess(ctx, sdk.ProcessConfig{
Name: "api",
Script: "main.py",
Runtime: "python",
Env: map[string]string{"PORT": "8080"},
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("Started: %s\n", id)
// Inspect it.
info, _ := mgr.Inspect(id)
fmt.Printf("PID: %d, State: %s\n", info.PID, info.State)
// Stop it gracefully.
mgr.Stop(id, 5*time.Second)
}
Manager API
Create a manager with sdk.New():
mgr, err := sdk.New(sdk.Config{
LogDir: "/var/lib/myapp/runix",
Defaults: sdk.DefaultsConfig{
RestartPolicy: "on-failure",
MaxRestarts: 5,
StopTimeout: 10 * time.Second,
},
})
Methods
| Method | Signature | Description |
|---|---|---|
| New | New(cfg Config) (*Manager, error) | Create a new manager |
| AddProcess | AddProcess(ctx, cfg ProcessConfig) (string, error) | Start a process, returns its ID |
| Stop | Stop(id string, timeout time.Duration) error | Graceful stop (SIGTERM → SIGKILL) |
| ForceStop | ForceStop(id string) error | Immediate kill (SIGKILL) |
| Restart | Restart(ctx context.Context, id string) error | Stop and restart (fires restart hooks) |
| Reload | Reload(ctx context.Context, id string) error | Graceful reload (fires reload hooks) |
| Remove | Remove(id string) error | Stop and unregister from manager |
| List | List() []ProcessInfo | List all managed processes |
| Inspect | Inspect(id string) (*ProcessInfo, error) | Get detailed process info |
| Logs | Logs(ctx context.Context, id string, opts LogOptions) (io.ReadCloser, error) | Stream process logs |
| Save | Save() error | Persist process state to disk |
| Resurrect | Resurrect() error | Restore saved processes |
| Close | Close() error | Stop all processes and clean up (safe to call multiple times) |
| LogPath | LogPath(id string) string | Returns stdout log file path |
| LogPathStderr | LogPathStderr(id string) string | Returns stderr log file path |
ProcessConfig
sdk.ProcessConfig{
Name: "my-worker", // required
// Use Script for interpreted files, Binary for compiled executables:
Script: "worker.py", // → maps to entrypoint
// -- or --
Binary: "./bin/worker", // → maps to entrypoint
Runtime: "python", // "go", "python", "node", "bun", "deno", "ruby", "php", "auto"
Interpreter: "/usr/bin/python3.12", // explicit interpreter (optional)
UseBundle: false, // wrap with "bundle exec" (Ruby)
Args: []string{"--verbose"},
Cwd: "/app",
Env: map[string]string{"PORT": "3000"},
RestartPolicy: "on-failure", // "always", "on-failure", "never"
MaxRestarts: 10,
RestartWindow: 5 * time.Minute,
StopSignal: "SIGTERM",
StopTimeout: 10 * time.Second,
Autostart: true,
Namespace: "backend",
Labels: map[string]string{"team": "platform"},
Tags: []string{"critical"},
Instances: 3,
DependsOn: []string{"database"},
Priority: 10,
Watch: &sdk.WatchConfig{Enabled: true, Paths: []string{"./src"}},
HealthCheck: &sdk.HealthCheckConfig{Type: "http", URL: "http://localhost:3000/health"},
Hooks: &sdk.HooksConfig{PreStart: &sdk.HookConfig{Command: "echo starting"}},
}
Script vs Binary
Both Script and Binary map to the same underlying entrypoint. Use whichever reads naturally:
| Field | When to Use | Examples |
|---|---|---|
Script | Interpreted files | main.py, server.js, app.rb |
Binary | Compiled executables or system commands | ./bin/server, sleep, nginx |
All ProcessConfig Fields
| Field | Type | Description |
|---|---|---|
Name | string | Required. Unique process identifier |
Script | string | Path to an interpreted script file |
Binary | string | Path to a compiled executable or command |
Runtime | string | Runtime: go, python, node, bun, deno, ruby, php, auto |
Interpreter | string | Explicit interpreter override (e.g., /usr/bin/python3.11) |
UseBundle | bool | Wrap command with bundle exec (Ruby) |
Args | []string | Additional arguments |
Cwd | string | Working directory |
Env | map[string]string | Environment variable overlay |
Autostart | bool | Auto-start on resurrect |
RestartPolicy | string | "always", "on-failure", "never" |
MaxRestarts | int | Maximum restart attempts |
RestartWindow | time.Duration | Time window for counting restarts |
StopSignal | string | Signal for stopping (default: SIGTERM) |
StopTimeout | time.Duration | Grace period before force kill |
Watch | *WatchConfig | File watching configuration |
HealthCheck | *HealthCheckConfig | Health check configuration |
Hooks | *HooksConfig | Lifecycle hook commands |
Instances | int | Number of copies to run |
Namespace | string | Namespace for grouping |
Labels | map[string]string | Key-value pairs for filtering |
Tags | []string | Tags for categorization |
DependsOn | []string | Process names this depends on |
Priority | int | Startup ordering (lower starts first) |
Supported Runtimes
The SDK supports the same runtimes as the CLI:
| Runtime | Detection Files | Command |
|---|---|---|
go | go.mod | go run . or direct binary |
python | requirements.txt, pyproject.toml, *.py | python3 <script> |
node | package.json | node <script> / npx tsx <script> |
bun | bun.lockb, bunfig.toml | bun run <script> |
deno | deno.json, deno.jsonc | deno run <script> |
ruby | Gemfile | ruby <script> / bundle exec ruby <script> |
php | composer.json, *.php | php <script> |
auto | (detects in order above) | Auto-selected |
When Runtime is set, the SDK resolves the correct interpreter and arguments automatically. Set Runtime: "auto" or leave empty to auto-detect from project files.
Runtime Resolution
The SDK uses runtime.NewDetector() to resolve runtimes:
- If
Runtimeis set to a specific value, it looks up that runtime adapter - The adapter's
StartCmd()method resolves the interpreter and entrypoint - If
Interpreteris set explicitly, it takes precedence - If
Runtimeis"auto"or empty, auto-detection scans the working directory
ProcessInfo
type ProcessInfo struct {
ID string // Internal UUID
NumericID int // Sequential number
Name string
Namespace string
InstanceIndex int
Runtime string
State string // "starting", "running", "stopping", "stopped", "crashed", "errored", "waiting"
PID int
ExitCode int
Restarts int
CreatedAt time.Time
StartedAt *time.Time
FinishedAt *time.Time
Uptime time.Duration
Config ProcessConfig
CPUPercent float64
MemBytes int64
MemPercent float64
Threads int
FDs int
Tags []string
}
Lookup
Inspect(id) accepts multiple formats:
| Format | Example |
|---|---|
| UUID | "abc123-def456-..." |
| Numeric ID | "1" |
| Name | "api" |
| Unique ID prefix | "abc" |
If a prefix matches multiple processes, an error is returned.
Log Streaming
logCtx, cancel := context.WithCancel(context.Background())
defer cancel()
reader, err := mgr.Logs(logCtx, processID, sdk.LogOptions{
Tail: 50, // show last 50 lines first
Follow: true, // then stream new output
Stderr: false, // false = stdout, true = stderr
})
if err != nil {
log.Fatal(err)
}
defer reader.Close()
// Read logs like any io.Reader.
buf := make([]byte, 4096)
for {
n, err := reader.Read(buf)
if n > 0 {
os.Stdout.Write(buf[:n])
}
if err != nil {
break
}
}
LogOptions
| Field | Type | Default | Description |
|---|---|---|---|
Tail | int | 0 | Show last N lines (0 = all) |
Follow | bool | false | Stream new entries as written |
Stderr | bool | false | Read stderr instead of stdout |
How It Works
- Opens the log file (
~/.runix/apps/<name>/stdout.logorstderr.log) - If
Tail > 0, reads the last N lines using a circular buffer - If
Follow, seeks to end and polls every 200ms for new content - Returns an
io.PipeReader— the caller reads from it, the goroutine writes to it - Context cancellation stops the streaming goroutine
Hooks Configuration
sdk.ProcessConfig{
Hooks: &sdk.HooksConfig{
PreStart: &sdk.HookConfig{Command: "echo starting", Timeout: 5 * time.Second},
PostStart: &sdk.HookConfig{Command: "curl http://localhost:8080/warm"},
PreStop: &sdk.HookConfig{Command: "curl -X PUT http://localhost:8080/drain"},
PostStop: &sdk.HookConfig{Command: "echo stopped"},
PreRestart: &sdk.HookConfig{Command: "echo restarting"},
PostRestart: &sdk.HookConfig{Command: "echo restarted"},
PreReload: &sdk.HookConfig{Command: "echo reloading"},
PostReload: &sdk.HookConfig{Command: "echo reloaded"},
PreHealthCheck: &sdk.HookConfig{Command: "echo checking"},
PostHealthCheck: &sdk.HookConfig{Command: "echo checked"},
},
}
HookConfig Fields
| Field | Type | Description |
|---|---|---|
Command | string | Shell command (sh -c) |
Timeout | time.Duration | Execution timeout |
IgnoreFailure | bool | Don't block lifecycle on failure |
Health Check Configuration
sdk.ProcessConfig{
HealthCheck: &sdk.HealthCheckConfig{
Type: "http", // "http", "tcp", "command"
URL: "http://localhost:8080/health",
Interval: "10s",
Timeout: "5s",
Retries: 3,
GracePeriod: "5s",
},
}
Watch Configuration
sdk.ProcessConfig{
Watch: &sdk.WatchConfig{
Enabled: true,
Paths: []string{"./src", "./config"},
Ignore: []string{".git", "node_modules"},
Debounce: "200ms",
},
}
Multi-Instance
id, err := mgr.AddProcess(ctx, sdk.ProcessConfig{
Name: "worker",
Binary: "python",
Args: []string{"worker.py"},
Instances: 3, // Starts worker:0, worker:1, worker:2
})
When Instances > 1, processes are named <name>:<index>. The first process ID is returned.
Save and Resurrect
Persist process state across restarts:
// Session 1: start processes and save.
mgr, _ := sdk.New(sdk.Config{LogDir: "/var/lib/myapp/runix"})
mgr.AddProcess(ctx, sdk.ProcessConfig{
Name: "api",
Binary: "sleep",
Args: []string{"300"},
})
mgr.Save()
mgr.Close()
// Session 2: restore from saved state.
mgr2, _ := sdk.New(sdk.Config{LogDir: "/var/lib/myapp/runix"})
defer mgr2.Close()
mgr2.Resurrect()
for _, p := range mgr2.List() {
fmt.Printf("Restored: %s (PID %d)\n", p.Name, p.PID)
}
Config Reference
sdk.Config
| Field | Type | Default | Description |
|---|---|---|---|
LogDir | string | $TMPDIR/runix | Directory for logs and state |
Defaults | DefaultsConfig | Default values for all processes |
sdk.DefaultsConfig
| Field | Type | Description |
|---|---|---|
RestartPolicy | string | "always", "on-failure", "never" |
MaxRestarts | int | Maximum restart attempts |
RestWindow | time.Duration | Time window for counting restarts |
StopTimeout | time.Duration | Default grace period before force kill |
What's Next
- Architecture Overview — How the supervisor works
- Process Lifecycle — Full lifecycle with hooks
- Restart Policies — Configurable restart behavior
- Health Checks — HTTP/TCP/command monitoring
- SDK Source —
sdk/package on GitHub