psi

package module
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Feb 6, 2026 License: MIT Imports: 12 Imported by: 3

README

psi

psi (pkt.systems init) is a tiny PID1 wrapper for single-process containers. It provides sane PID1 behavior for FROM scratch images without a dedicated init: signal forwarding to the child process group, zombie reaping, and a configurable forced shutdown timeout.

Why psi

When your process is PID1 inside a container, Linux changes signal and child reaping semantics. Many applications aren’t written with PID1 behavior in mind, and that can lead to stuck shutdowns or zombie processes. psi makes PID1 act like a minimal init while keeping your app unchanged.

Behavior

  • Non-PID1: runs your submain directly
  • PID1: re-execs itself as a child and:
    • forwards signals to the child process group
    • reaps zombies
    • enforces a forced shutdown timeout
    • makes the child the foreground process group on the controlling TTY, so interactive reads work correctly

Usage

Wrap your existing main in a submain:

package main

import (
    "context"
    "os"

    "pkt.systems/psi"
)

func submain(ctx context.Context) int {
    // Your old main logic.
    // ctx is cancelled on SIGTERM/SIGINT/SIGQUIT/SIGHUP.
    return 0
}

func main() {
    psi.Run(submain)
}

Configuration

PSI_STOP_TIMEOUT controls how long PSI waits after the first terminate-like signal before sending SIGKILL to the child’s process group.

Examples:

  • PSI_STOP_TIMEOUT=30s (default)
  • PSI_STOP_TIMEOUT=1m15s
  • PSI_STOP_TIMEOUT=10 (interpreted as seconds)

Example container

The example/ module builds a scratch container with psi as PID1.

Build and run:

make -C example build
make -C example run

Interactive demo (TTY foreground behavior):

make -C example run-interactive

Or directly:

podman run -ti --rm localhost/psi-example:latest interactive

Notes

  • psi forwards signals to the child’s process group. If your app spawns subprocesses, they will receive signals as expected.
  • Interactive reads from a TTY are supported by setting the child’s process group as the foreground process group for the controlling TTY.

Documentation

Overview

Package psi (pkt.systems init) is a tiny PID1 wrapper for single-process containers. It provides sane PID1 behavior for scratch images without a dedicated init by re-execing a managed child process and handling the responsibilities PID1 typically lacks in a regular app.

Behavior summary:

  • If the process is not PID1, Run executes your submain directly.
  • If the process is PID1, Run re-execs itself as a child process group and the parent becomes a minimal init that:
  • forwards signals to the child's process group
  • reaps orphaned child processes
  • enforces a forced shutdown timeout
  • makes the child the foreground process group on the controlling TTY

The child process receives a context that is cancelled on terminate-like signals (SIGTERM/SIGINT/SIGQUIT/SIGHUP). Your submain should return an exit code; psi will exit with the same code.

Configuration:

PSI_STOP_TIMEOUT sets the duration between the first terminate-like signal
and a forced SIGKILL to the child's process group. Accepts Go duration
strings like "30s" or "1m15s". Bare numbers like "10" are treated as
seconds. Default is 30s.

Example:

package main

import (
    "context"
    "os"

    "pkt.systems/psi"
)

func submain(ctx context.Context) int {
    // your old main logic here
    // ctx is cancelled on SIGTERM/SIGINT/SIGQUIT/SIGHUP
    return 0
}

func main() {
    psi.Run(submain)
}

psi (pkt.systems init) is a tiny PID1 wrapper for single-process containers. It runs your application's "submain" and, when running as PID 1, provides proper signal forwarding (to the child's process group), zombie reaping, and a configurable forced-shutdown timeout via PSI_STOP_TIMEOUT (default 30s).

Usage:

func submain(ctx context.Context) int { /* your old main */ }
func main() { psi.Run(submain) }

Build statically for scratch images:

CGO_ENABLED=0 GOOS=linux go build -trimpath -ldflags="-s -w"

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ChildPIDEnv

func ChildPIDEnv() (string, bool)

ChildPIDEnv returns the PSI_CHILD env var as seen by the current process.

func EffectiveTimeout

func EffectiveTimeout() time.Duration

EffectiveTimeout exposes the parsed PSI_STOP_TIMEOUT (or default). Useful in unit tests.

func IsPID1

func IsPID1() bool

IsPID1 reports whether the current process is PID 1.

func Run

func Run(submain SubMain)

Run wraps submain with PID1 responsibilities when needed. If PID != 1 and PSI_CHILD not set: runs submain directly (nice for local dev). If PID == 1 and PSI_CHILD not set: forks/execs itself; parent becomes init, child runs submain. If PSI_CHILD == "1": executes submain path (child).

Types

type SubMain

type SubMain func(ctx context.Context) int

SubMain is your application's entrypoint (old main), returning an exit code. The provided context is cancelled when a termination signal is received.

Directories

Path Synopsis
internal

Jump to

Keyboard shortcuts

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