zinit 0.3.8

Process supervisor with dependency management
Documentation
# zinit

A lightweight process supervisor with dependency management, similar to systemd but simpler.

## Features

- **Dependency Graph**: Services declare dependencies (`requires`, `after`, `wants`, `conflicts`)
- **State Machine**: 7 explicit states (Inactive, Blocked, Starting, Running, Stopping, Exited, Failed)
- **Process Groups**: Signals sent to process groups, handling `sh -c` child processes correctly
- **Health Checks**: TCP, HTTP, and exec-based health checks with retries
- **Ordered Shutdown**: Dependents stop before their dependencies
- **Hot Reload**: Reload configuration without full restart
- **Multi-Environment**: Works in containers, VMs, and bare-metal

## Deployment Modes

zinit adapts its behavior based on deployment environment:

### Container Mode

Use `zinit-pid1` as your container entrypoint:

```dockerfile
ENTRYPOINT ["/usr/bin/zinit-pid1", "--container"]
```

Or set the environment variable:

```bash
ZINIT_CONTAINER=1 zinit-pid1
```

Behavior:

- Loads services from `/etc/zinit/services/`
- Clean exit on shutdown (no reboot syscall)
- No system services directory

### VM / Bare-Metal Mode

Use `zinit-pid1` as your init system (PID 1):

```
# In /sbin/init or kernel cmdline: init=/usr/bin/zinit-pid1
```

Behavior:

- Loads system services from `/etc/zinit/system/` first (auto-assigned `class=system`)
- Loads user services from `/etc/zinit/services/` second
- Handles reboot/poweroff via syscalls (SIGINT=reboot, SIGTERM=poweroff)
- Never exits (kernel panic prevention)

### Standalone Mode

Run `zinit-server` directly (not as PID 1):

```bash
zinit-server --config-dir /etc/zinit/services
```

Optionally enable system services directory:

```bash
zinit-server --config-dir /etc/zinit/services --pid1-mode
```

## Quick Start

```bash
# Build all binaries
cargo build --release --features full

# Or build specific components
cargo build --release --features client   # CLI only
cargo build --release --features server   # Server only

# Run the server
zinit-server --config-dir /etc/zinit/services

# Use the CLI
zinit list
zinit status my-service
zinit start my-service
zinit stop my-service
```

## Architecture

```
zinit-pid1 (PID 1 shim)
    | spawns/monitors
    v
zinit-server (daemon)
    | unix socket
    v
zinit (CLI/TUI)
```

### Crate Structure

zinit is a single crate with feature flags:

```
src/
  lib.rs              # Library entry point
  bin/
    zinit.rs          # CLI binary
    zinit-server.rs   # Server binary
    zinit-pid1.rs     # PID 1 binary (Linux only)
  sdk/                # Shared types, config, protocol, clients
  server/             # Supervisor, dependency graph, process management
  client/             # CLI interface
```

### Features

| Feature  | Description                                      |
| -------- | ------------------------------------------------ |
| `sdk`    | Shared types, config, protocol (always included) |
| `client` | CLI interface                                    |
| `server` | Supervisor daemon                                |
| `pid1`   | PID 1 init shim (Linux only)                     |
| `tui`    | Terminal UI for client                           |
| `repl`   | Interactive REPL for client                      |
| `async`  | Async client support                             |
| `full`   | Build everything                                 |

Default features: `client`, `server`

## Configuration

Service configs are TOML files in the config directory (default: `/etc/zinit/services/`):

```toml
[service]
name = "my-app"
exec = "/usr/bin/my-app --daemon"
dir = "/var/lib/my-app"     # optional working directory
oneshot = false              # exit after success (default: false)
status = "start"             # start | stop | ignore (default: start)
class = "user"               # user | system (default: user)

[dependencies]
requires = ["database"]      # must be running
after = ["logger"]           # start order only
wants = ["metrics"]          # soft dependency
conflicts = ["legacy-app"]   # mutual exclusion

[lifecycle]
restart = "on-failure"       # always | on-failure | never
stop_signal = "SIGTERM"
start_timeout_ms = 30000
stop_timeout_ms = 10000
restart_delay_ms = 1000
restart_delay_max_ms = 60000
max_restarts = 0             # 0 = unlimited

[health]
type = "http"
endpoint = "http://localhost:8080/health"
interval_ms = 10000
retries = 3

[logging]
buffer_lines = 1000
```

### Targets

Virtual services for grouping:

```toml
[target]
name = "multi-user"

[dependencies]
requires = ["network", "logger", "database"]
```

### Service Status

The `status` field controls supervisor behavior:

- `start` (default): Automatically start and keep running
- `stop`: Keep stopped (won't auto-start)
- `ignore`: Supervisor ignores this service

### Service Class

The `class` field protects critical services from bulk operations:

- `user` (default): Normal service, affected by `*_all` commands
- `system`: Protected service, skipped by bulk operations

System-class services are immune to `start_all`, `stop_all`, and `delete_all` commands.

## CLI Commands

```bash
zinit list                    # List all services
zinit status <name>           # Show service status
zinit start <name>            # Start a service
zinit stop <name>             # Stop (cascades to dependents)
zinit restart <name>          # Restart a service
zinit kill <name> [signal]    # Send signal to service
zinit logs <name> [-n N]      # View service logs
zinit why <name>              # Show why service is blocked
zinit tree                    # Show dependency tree
zinit reload                  # Reload configuration
zinit add-service <toml>      # Add service at runtime
zinit remove-service <name>   # Remove a service
zinit start-all               # Start all user-class services
zinit stop-all                # Stop all user-class services
zinit delete-all              # Delete all user-class services
zinit shutdown                # Stop all services, exit daemon
zinit poweroff                # Power off system (signals init)
zinit reboot                  # Reboot system (signals init)

# Debug commands
zinit debug-state             # Full graph state dump
zinit debug-procs <name>      # Process tree for a service
```

## Path Configuration

zinit uses platform-specific default paths:

### Linux (System/PID1 mode)

- Config directory: `/etc/zinit/services`
- System services: `/etc/zinit/system` (PID1 mode only)
- Socket: `/run/zinit.sock`

### macOS / Windows (Standalone mode)

- Config directory: `$HOME/hero/cfg/zinit`
- Socket: `$HOME/hero/var/zinit.sock`

You can override these with environment variables (see below).

See [PATHS.md](PATHS.md) for detailed path configuration documentation.

## Environment Variables

| Variable           | Default                       | Description                                          |
| ------------------ | ----------------------------- | ---------------------------------------------------- |
| `ZINIT_LOG_LEVEL`  | `info`                        | Log level: `trace`, `debug`, `info`, `warn`, `error` |
| `ZINIT_CONFIG_DIR` | Platform-specific (see above) | Service config directory                             |
| `ZINIT_SOCKET`     | Platform-specific (see above) | Unix socket path                                     |
| `ZINIT_CONTAINER`  | unset                         | If set, zinit-pid1 runs in container mode            |

### Example: Custom Paths

```bash
# Use custom config and socket directories
export ZINIT_CONFIG_DIR=/opt/my-services
export ZINIT_SOCKET=/tmp/my-zinit.sock

# Start server
zinit-server

# Connect with CLI
zinit list
```

## Library Usage

zinit can be used as a library:

```rust
use zinit::{ZinitClient, ServiceConfig};

// Blocking client
let mut client = ZinitClient::connect_unix("/var/run/zinit.sock")?;
let services = client.list()?;

// Async client (requires "async" feature)
use zinit::AsyncZinitClient;
let mut client = AsyncZinitClient::connect_unix("/var/run/zinit.sock").await?;
let status = client.status("my-service").await?;
```

## Docker Usage

```bash
# Build test image
docker build -t zinit-test -f docker/Dockerfile .

# Run (uses container mode automatically)
docker run -it --rm zinit-test

# With debug logging
docker run -it --rm -e ZINIT_LOG_LEVEL=debug zinit-test

# Explicit container mode
docker run -it --rm -e ZINIT_CONTAINER=1 zinit-test
```

## Shutdown Ordering

Services are stopped in reverse dependency order:

```
Example: database <- app <- worker

Startup order:   database -> app -> worker
Shutdown order:  worker -> app -> database
```

When stopping a single service, dependents are stopped first:

- `zinit stop database` stops worker, then app, then database
- Dependencies are NOT auto-stopped (other services may need them)

## Development

```bash
cargo check --features full       # Verify builds
cargo test --features full        # Run tests
cargo build --release --features full  # Build all binaries
cargo clippy --features full      # Lint

# Run integration tests
cd tests/integration
./run.sh
```

## License

See LICENSE file.