| .cargo | ||
| .claude | ||
| .forgejo/workflows | ||
| _archive/.serena | ||
| ai_instructions | ||
| docs | ||
| example | ||
| rhaiexamples | ||
| scripts | ||
| specs | ||
| tests | ||
| zinit_client | ||
| zinit_server | ||
| zinit_updater | ||
| .gitignore | ||
| build.sh | ||
| build_package.sh | ||
| build_publish.sh | ||
| Cargo.lock | ||
| Cargo.toml | ||
| HEROLIB_INTEGRATION.md | ||
| install.sh | ||
| LICENSE | ||
| README.md | ||
| run.sh | ||
Zinit
A lightweight process supervisor with Rhai scripting support, designed for both standalone and container deployments.
Zinit consists of three components:
- zinit-server - The daemon that manages processes
- zinit - The CLI client with REPL, TUI, and Rhai scripting
- zinit-updater - Auto-updater and PID 1 init shim for containers
Features
- Process Supervision - Automatic restarts, dependency ordering, health checks
- Container Support - Run as PID 1 with proper signal handling and zombie reaping
- Live Updates - Update zinit binaries without restarting services
- On-Demand Services - Xinit proxy starts services only when connections arrive
- Rhai Scripting - Powerful scripting for automation
- Interactive REPL - Tab completion, syntax highlighting, command history
- Full-screen TUI - Monitor and control services visually
- Resource Stats - CPU and memory usage per service
- Simple Architecture - JSON-RPC over Unix socket
Installation
Quick Install (Linux/macOS)
curl -sSL https://forge.ourworld.tf/geomind_code/zinit/raw/branch/main/scripts/install.sh | bash
From Crates.io
cargo install zinit_server
cargo install zinit
cargo install zinit-updater
From Source
git clone https://forge.ourworld.tf/geomind_code/zinit
cd zinit
cargo build --release
# Install binaries
cp target/release/zinit-server /usr/local/bin/
cp target/release/zinit /usr/local/bin/
cp target/release/zinit-updater /usr/local/bin/
Quick Start
1. Start the Server
# Run in background (daemon mode)
zinit-server -bg
# Or run in foreground
zinit-server
2. Use the Client
# Check connection
zinit ping
# List services
zinit list
# Start interactive REPL
zinit repl
# Start full-screen TUI
zinit tui
Container / PID 1 Mode
For container deployments, use zinit-updater as your entrypoint. It acts as a proper init process (PID 1) and manages zinit-server as a child:
ENTRYPOINT ["/zinit-updater"]
Architecture
PID 1: zinit-updater
│
├── Reaps orphan zombie processes
├── Forwards signals to zinit-server
├── Auto-restarts zinit-server on crash
├── Periodically checks for updates
│
└── zinit-server (child process)
└── services...
Signal Handling
| Signal | Behavior |
|---|---|
| SIGTERM | Full shutdown - stops all services, then exits |
| SIGINT | Same as SIGTERM |
| SIGUSR1 | Soft restart - zinit-server restarts, services keep running |
Environment Variables
| Variable | Default | Description |
|---|---|---|
ZINIT_UPDATE_INTERVAL |
300 | Seconds between update checks |
Testing Init Mode
# Test without being PID 1
zinit-updater --init
# In another terminal
pgrep -a zinit
# Test soft restart
kill -USR1 $(pgrep -x zinit-server)
# Test shutdown
kill -TERM $(pgrep -f 'zinit-updater.*--init')
Live Updates
When zinit-updater detects a new version:
- Sends SIGUSR1 to zinit-server (soft restart)
- zinit-server exits without stopping services
- Downloads and installs new binaries
- Spawns new zinit-server
- New server rediscovers running services via /proc
Services continue running throughout the update process.
Usage
Server Commands
zinit-server # Run in foreground
zinit-server -bg # Run in background (daemon mode)
zinit-server --port 9123 # Also listen on TCP port
zinit-server --help # Show help
Updater Commands
zinit-updater check # Check for updates
zinit-updater update # Download and apply updates
zinit-updater watch [s] # Poll for updates every N seconds
zinit-updater add <url> # Track a Rhai script URL
zinit-updater remove <url> # Stop tracking a script
zinit-updater list # List tracked scripts
zinit-updater --init # Run as init (for testing)
Client Commands
# Service management
zinit list # List all services
zinit status <name> # Get service status
zinit start <name> # Start a service
zinit stop <name> # Stop a service
zinit restart <name> # Restart a service
zinit delete <name> # Delete a service
zinit kill <name> [sig] # Send signal to service
zinit stats <name> # Get resource usage
# Logs
zinit logs # Show all logs
zinit logs <name> # Show logs for service
# Scripting
zinit script.rhai # Run a Rhai script file
zinit scripts/ # Run all .rhai files in directory
zinit -c 'zinit_ping()' # Execute inline script
zinit -i # Read script from stdin
# Interactive
zinit repl # Interactive REPL
zinit tui # Full-screen TUI
zinit ping # Check server connection
Rhai Scripting
Inline Scripts
# Simple ping
zinit -c 'zinit_ping()'
# List services
zinit -c 'print(zinit_list())'
# Create and start a service
zinit -c '
let svc = new_service("myapp")
.exec("python3 -m http.server 8080")
.oneshot(false);
start(svc, 5);
'
From Stdin
echo 'zinit_list()' | zinit -i
zinit -i << 'EOF'
let status = zinit_status("myapp");
print("State: " + status.state);
print("PID: " + status.pid);
EOF
Script Files
// myservice.rhai
let svc = new_service("webserver")
.exec("python3 -m http.server 8080")
.dir("/var/www")
.env("PORT", "8080")
.after("database")
.oneshot(false);
start(svc, 10); // Start and wait up to 10s
print("Webserver started!");
print("State: " + zinit_status("webserver").state);
zinit run myservice.rhai
Service Builder API
// Create and register a service
let svc = zinit_service_new()
.name("myapp") // Service name
.exec("python3 -m http.server 8080") // Required: command to run
.test_cmd("curl -s localhost:8080") // Command-based health check
.test_tcp("localhost:8080") // TCP port health check
.test_http("http://localhost:8080/") // HTTP health check
.tcp_kill() // Kill processes on port before starting
.oneshot(false) // Restart on exit (default)
.after("database") // Wait for dependency
.env("PORT", "8080") // Environment variable
.dir("/var/www") // Working directory
.log("ring") // Logging: "ring", "stdout", "none"
.shutdown_timeout(10) // Seconds before SIGKILL
.signal_stop("SIGTERM") // Stop signal
.register(); // Register with zinit
// Wait for service to start
svc.wait(30); // Wait up to 30 seconds
Xinit: On-Demand Service Startup
Xinit is a socket proxy feature that enables on-demand service startup. Instead of running services continuously, xinit creates a proxy socket that starts the service only when a client connects.
How It Works
- When zinit starts, services with
xinit.enabled: truedon't auto-start - Xinit creates a proxy socket at the
listenaddress - When a client connects to the proxy, xinit:
- Starts the backend service
- Waits for the backend socket to become available
- Forwards the connection to the backend
- After
idle_timeoutseconds with no connections, the service stops
Configuration
Add xinit configuration to your service YAML:
# ~/hero/cfg/zinit/postgres.yaml
exec: "postgres -D /var/lib/postgresql/data"
# Health check - used as default backend if xinit.backend not set
health:
tcp: "localhost:5432"
# Xinit configuration
xinit:
enabled: true
listen: "/tmp/pg-proxy.sock" # Proxy socket (Unix or TCP)
backend: "localhost:5432" # Backend address (optional, defaults to health.tcp)
connect_timeout: 30 # Seconds to wait for backend after service start
idle_timeout: 300 # Stop service after N seconds of inactivity (0=never)
single_connection: false # Allow only one connection at a time
Examples
Unix Socket Proxy (PostgreSQL):
exec: "postgres -D /var/lib/postgresql/data"
health:
tcp: "localhost:5432"
xinit:
enabled: true
listen: "/tmp/pg-proxy.sock"
idle_timeout: 300
Clients connect to /tmp/pg-proxy.sock instead of localhost:5432.
TCP Proxy (Redis):
exec: "redis-server --port 6379"
health:
tcp: "localhost:6379"
xinit:
enabled: true
listen: "0.0.0.0:16379" # External proxy port
backend: "localhost:6379" # Internal backend
idle_timeout: 600
Clients connect to port 16379, which proxies to Redis on 6379.
Xinit Builder API (Rhai)
// 1. First, create the backend service (not started yet)
zinit_service_new()
.name("echo_server")
.exec("socat TCP-LISTEN:9999,reuseaddr,fork EXEC:cat")
.test_tcp("localhost:9999")
.register();
// 2. Create xinit proxy that starts the service on demand
xinit_proxy_new()
.name("echo_proxy")
.listen("unix:///tmp/echo.sock") // Listen on Unix socket or "tcp://0.0.0.0:8080"
.backend("tcp://127.0.0.1:9999") // Forward to backend
.service("echo_server") // Service to start on connection
.idle_timeout(60) // Stop after 60s idle (0=never)
.connect_timeout(10) // Wait 10s for backend
.reset() // Delete existing proxy first
.register();
// Proxy management
xinit_list(); // List all proxies
xinit_status("echo_proxy"); // Get proxy status
xinit_unregister("echo_proxy"); // Remove proxy
Use Cases
- Development environments: Start heavy services only when needed
- Resource-constrained systems: Run services on-demand to save memory
- Rarely-used services: Start database only when accessed
- Cold start testing: Simulate service startup latency
Rhai Functions
Service Control
zinit_start(name)- Start a servicezinit_stop(name)- Stop a servicezinit_restart(name)- Restart a servicezinit_delete(name)- Delete a servicezinit_kill(name, signal)- Send signal.register()- Register service (builder method).wait(secs)- Wait for service to start (builder method)
Service Info
zinit_list()- Array of service nameszinit_status(name)- Map: name, pid, state, targetzinit_stats(name)- Map: pid, memory_usage, cpu_usagezinit_is_running(name)- Boolean
Logs
zinit_logs()- Array of all log lineszinit_logs_filter(name)- Logs for specific service
Batch Operations
zinit_start_all()- Start all serviceszinit_stop_all()- Stop all serviceszinit_shutdown()- Shutdown server
System
zinit_ping()- Check server connection
Xinit (On-Demand Proxy)
xinit_list()- Array of proxy namesxinit_register(name, listen, backend, service)- Register a proxyxinit_unregister(name)- Unregister a proxyxinit_status(name)- Proxy status (connections, bytes, etc.)xinit_status_all()- Status of all proxies
Utilities
print(msg)- Print outputsleep(secs)- Sleep secondssleep_ms(ms)- Sleep millisecondsget_env(key)- Get environment variableset_env(key, val)- Set environment variablefile_exists(path)- Check if path existscheck_tcp(addr)- Check TCP portcheck_http(url)- Check HTTP endpointkill_port(port)- Kill process on port
Paths
| Path | Purpose |
|---|---|
~/hero/cfg/zinit/ |
Service YAML files |
~/hero/var/zinit.sock |
Unix socket |
~/hero/cfg/zinit_latest.toml |
Updater state |
Service States
| State | Description |
|---|---|
Running |
Process is running |
Success |
Oneshot completed successfully |
Failed |
Process exited with error |
Blocked |
Waiting for dependencies |
Spawned |
Process started, not yet confirmed |
Stopped |
Manually stopped |
Interactive REPL
$ zinit repl
╔═══════════════════════════════════════════════════════════╗
║ Zinit Interactive Shell ║
╠═══════════════════════════════════════════════════════════╣
║ Tab: completion | Up/Down: history | Ctrl+D: exit ║
║ Type /help for commands, /functions for API ║
╚═══════════════════════════════════════════════════════════╝
Connected to zinit server: pong v0.3.0
zinit> zinit_list()
=> ["myapp", "database"]
zinit> zinit_status("myapp")
=> #{"name": "myapp", "pid": 1234, "state": "Running", "target": "Up"}
zinit> /quit
Goodbye!
REPL Commands
/help- Show help/functions- List all functions/tui- Switch to TUI mode/clear- Clear screen/quit- Exit
Architecture
Standalone Mode
┌─────────────┐ JSON-RPC ┌──────────────┐
│ zinit │ ◄───────────────► │ zinit-server │
│ (client) │ Unix Socket │ (daemon) │
└─────────────┘ └──────────────┘
│ │
├── CLI commands ├── Process management
├── REPL ├── Service lifecycle
├── TUI ├── Dependency ordering
└── Rhai scripting └── Resource monitoring
Container Mode (PID 1)
┌────────────────────────────────────────────────────────────┐
│ Container │
│ ┌──────────────┐ │
│ │zinit-updater │ ◄── PID 1 (reaps zombies, handles sigs) │
│ │ (init shim) │ │
│ └──────┬───────┘ │
│ │ spawns/monitors │
│ ▼ │
│ ┌──────────────┐ JSON-RPC ┌─────────────┐ │
│ │ zinit-server │ ◄───────────────► │ zinit │ │
│ │ (daemon) │ Unix Socket │ (client) │ │
│ └──────┬───────┘ └─────────────┘ │
│ │ manages │
│ ▼ │
│ ┌──────────────┐ │
│ │ services... │ │
│ └──────────────┘ │
└────────────────────────────────────────────────────────────┘
Testing
# Run all tests
cargo test --workspace
# Run init mode tests (requires release build)
cargo build --release
cargo test -p zinit-updater --test init_mode -- --test-threads=1
License
Apache-2.0