| .. | ||
| src | ||
| tests | ||
| Cargo.toml | ||
| README.md | ||
shepherdd
The Shepherd background service.
Overview
shepherdd is the authoritative policy and enforcement service for the Shepherd ecosystem. It is the central coordinator that:
- Loads and validates configuration
- Evaluates policy to determine availability
- Manages session lifecycles
- Enforces time limits
- Emits warnings and events
- Serves multiple clients via IPC
Key principle: shepherdd is the single source of truth. User interfaces only request actions and display state—they never enforce policy independently.
Architecture
┌──────────────────────────────────────────────────────────────┐
│ shepherdd │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌────────────────────┐ │
│ │ Config │ │ Store │ │ Core Engine │ │
│ │ Loader │──▶│ (SQLite) │──▶│ (Policy + Session) │ │
│ └─────────────┘ └─────────────┘ └──────────┬─────────┘ │
│ │ │
│ ┌─────────────┐ ┌─────────────┐ │ │
│ │ Host │ │ IPC │◀──────────────┘ │
│ │ Adapter │◀─│ Server │ │
│ │ (Linux) │ │ │ │
│ └──────┬──────┘ └──────┬──────┘ │
│ │ │ │
│ │ Unix Domain Socket │
│ │ │ │
└─────────┼────────────────┼───────────────────────────────────┘
│ │
▼ ▼
Supervised ┌─────────┐ ┌─────────┐ ┌─────────┐
Applications │Launcher │ │ HUD │ │ Admin │
│ UI │ │ Overlay │ │ Tools │
└─────────┘ └─────────┘ └─────────┘
Usage
Running
# With default config location
shepherdd
# With custom config
shepherdd --config /path/to/config.toml
# Override socket and data paths
shepherdd --socket /tmp/shepherdd.sock --data-dir /tmp/shepherdd-data
# Debug logging
shepherdd --log-level debug
Command-Line Options
| Option | Default | Description |
|---|---|---|
-c, --config |
~/.config/shepherd/config.toml |
Configuration file path |
-s, --socket |
From config | IPC socket path |
-d, --data-dir |
From config | Data directory |
-l, --log-level |
info |
Log verbosity |
Environment Variables
| Variable | Description |
|---|---|
SHEPHERD_SOCKET |
Override socket path (default: $XDG_RUNTIME_DIR/shepherdd/shepherdd.sock) |
SHEPHERD_DATA_DIR |
Override data directory (default: $XDG_DATA_HOME/shepherdd) |
RUST_LOG |
Tracing filter (e.g., shepherdd=debug) |
Main Loop
The service runs an async event loop that processes:
- IPC messages - Commands from clients
- Host events - Process exits, window events
- Timer ticks - Check for warnings and expiry
- Signals - SIGHUP for config reload, SIGTERM for shutdown
┌────────────────────────────────────────────────────┐
│ Main Loop │
│ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ IPC │ │ Host │ │ Timer │ │ Signal │ │
│ │ Channel │ │ Events │ │ Tick │ │ Handler │ │
│ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘ │
│ │ │ │ │ │
│ └───────────┴─────┬─────┴───────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────┐ │
│ │ Process Event │ │
│ └──────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────┐ │
│ │ Broadcast Events │ │
│ └──────────────────┘ │
└────────────────────────────────────────────────────┘
Command Handling
Client Commands
| Command | Description | Role Required |
|---|---|---|
GetState |
Get full state snapshot | Any |
ListEntries |
Get available entries | Any |
Launch |
Start a session | Shell/Admin |
StopCurrent |
End current session | Shell/Admin |
ReloadConfig |
Hot-reload configuration | Admin |
SubscribeEvents |
Subscribe to event stream | Any |
GetHealth |
Health check | Any |
SetVolume |
Set system volume | Shell/Admin |
GetVolume |
Get volume info | Any |
Response Flow
Client Request
│
▼
Role Check ──────▶ Denied Response
│
▼
Command Handler
│
▼
Core Engine
│
▼
Response + Events ──────▶ Broadcast to Subscribers
Session Lifecycle
Launch
- Client sends
Launch { entry_id } - Core engine evaluates policy
- If denied: respond with reasons
- If approved: create session plan
- Host adapter spawns process
- Session transitions to Running
SessionStartedevent broadcast
Enforcement
- Timer ticks every 100ms
- Core engine checks warnings and expiry
- At warning thresholds:
WarningIssuedevent - At deadline: initiate graceful stop
- After grace period: force kill
SessionEndedevent broadcast
Termination
- Stop triggered (expiry, user, admin, process exit)
- Host adapter signals process (SIGTERM)
- Wait for grace period
- Force kill if needed (SIGKILL)
- Record usage in store
- Set cooldown if configured
- Clear session state
Configuration Reload
On SIGHUP or ReloadConfig command:
- Parse new configuration file
- Validate completely
- If invalid: keep old config, log error
- If valid: atomic swap to new policy
- Emit
PolicyReloadedevent - Current session continues with original plan
Health Monitoring
The service exposes health status via GetHealth:
{
"status": "healthy",
"policy_loaded": true,
"store_healthy": true,
"host_healthy": true,
"uptime_seconds": 3600,
"current_session": null
}
Logging
Uses structured logging via tracing:
2025-01-15T14:30:00.000Z INFO shepherdd: Starting shepherd service
2025-01-15T14:30:00.050Z INFO shepherd_config: Configuration loaded entries=5
2025-01-15T14:30:00.100Z INFO shepherd_ipc: IPC server listening path=/run/shepherdd/shepherdd.sock
2025-01-15T14:30:15.000Z INFO shepherd_core: Session started session_id=abc123 entry_id=minecraft
2025-01-15T14:59:45.000Z WARN shepherd_core: Warning issued session_id=abc123 threshold=60
2025-01-15T15:00:45.000Z INFO shepherd_core: Session expired session_id=abc123
Persistence
State is persisted to SQLite:
/var/lib/shepherdd/
├── shepherdd.db # SQLite database
└── logs/
└── sessions/ # Session stdout/stderr
Signals
| Signal | Action |
|---|---|
SIGHUP |
Reload configuration |
SIGTERM |
Graceful shutdown |
SIGINT |
Graceful shutdown |
Dependencies
This binary wires together all the library crates:
shepherd-config- Configuration loadingshepherd-core- Policy engineshepherd-host-api- Host adapter traitshepherd-host-linux- Linux implementationshepherd-ipc- IPC servershepherd-store- Persistenceshepherd-api- Protocol typesshepherd-util- Utilitiestokio- Async runtimeclap- CLI parsingtracing- Logginganyhow- Error handling
Building
cargo build --release -p shepherdd
Installation
The service is typically started by the compositor:
sway.conf
# Start shepherdd FIRST - it needs to create the socket before HUD/launcher connect
# Running inside sway ensures all spawned processes use the nested compositor
exec ./target/debug/shepherdd -c ./config.example.toml
See CONTRIBUTING.md for development setup.