shepherd-launcher/crates/shepherd-config/README.md
Albert Armea 9da95a27b3 Add "steam"-specific type
This implementation allows each platform to choose how to launch Steam (on Linux, we use the snap as the examples suggested before), and keeps Steam alive after an activity exits so that save sync, game updates, etc. can continue to run.

Change written by Codex 5.2 on medium:

Consider this GitHub issue https://github.com/aarmea/shepherd-launcher/issues/4. On Linux, an activity that uses the "steam" type should launch Steam via the snap as shown in the example configuration in this repository.

Go ahead and implement the feature. I'm expecting one of the tricky bits to be killing the activity while keeping Steam alive, as we can no longer just kill the Steam snap cgroup.
2026-02-07 16:22:55 -05:00

207 lines
5.3 KiB
Markdown

# shepherd-config
Configuration parsing and validation for Shepherd.
## Overview
This crate handles loading, parsing, and validating the TOML configuration that defines what entries are available, when they're available, and for how long. It provides:
- **Schema definitions** - Raw configuration structure as parsed from TOML
- **Policy objects** - Validated, ready-to-use policy structures
- **Validation** - Detailed error messages for misconfiguration
- **Hot reload support** - Configuration can be reloaded at runtime
## Configuration Format
Shepherd uses TOML for configuration. Here's a complete example:
```toml
config_version = 1
[service]
socket_path = "/run/shepherdd/shepherdd.sock"
data_dir = "/var/lib/shepherdd"
default_max_run_seconds = 1800 # 30 minutes default
# Global volume restrictions
[service.volume]
max_volume = 80
allow_unmute = true
# Default warning thresholds (seconds before expiry)
[[service.default_warnings]]
seconds_before = 300 # 5 minutes
severity = "info"
[[service.default_warnings]]
seconds_before = 60 # 1 minute
severity = "warn"
[[service.default_warnings]]
seconds_before = 10
severity = "critical"
message_template = "Closing in {remaining} seconds!"
# Entry definitions
[[entries]]
id = "minecraft"
label = "Minecraft"
icon = "minecraft"
kind = { type = "snap", snap_name = "mc-installer" }
[entries.availability]
[[entries.availability.windows]]
days = "weekdays"
start = "15:00"
end = "18:00"
[[entries.availability.windows]]
days = "weekends"
start = "10:00"
end = "20:00"
[entries.limits]
max_run_seconds = 1800 # 30 minutes per session
daily_quota_seconds = 7200 # 2 hours per day
cooldown_seconds = 600 # 10 minutes between sessions
[[entries]]
id = "educational-game"
label = "GCompris"
icon = "gcompris-qt"
kind = { type = "process", command = "gcompris-qt" }
[entries.availability]
always = true # Always available
[entries.limits]
max_run_seconds = 3600 # 1 hour
```
## Usage
### Loading Configuration
```rust
use shepherd_config::{load_config, parse_config, Policy};
use std::path::Path;
// Load from file (typically ~/.config/shepherd/config.toml)
let policy = load_config("config.toml")?;
// Parse from string
let toml_content = std::fs::read_to_string("config.toml")?;
let policy = parse_config(&toml_content)?;
// Access entries
for entry in &policy.entries {
println!("{}: {:?}", entry.label, entry.kind);
}
```
### Entry Kinds
Entries can be of several types:
```toml
# Regular process
kind = { type = "process", command = "/usr/bin/game", args = ["--fullscreen"] }
# Snap application
kind = { type = "snap", snap_name = "mc-installer" }
# Steam game (via Steam snap)
kind = { type = "steam", app_id = 504230 }
# Virtual machine (future)
kind = { type = "vm", driver = "qemu", args = { disk = "game.qcow2" } }
# Media playback (future)
kind = { type = "media", library_id = "movies" }
# Custom type
kind = { type = "custom", type_name = "my-launcher", payload = { ... } }
```
### Time Windows
Time windows control when entries are available:
```toml
[entries.availability]
[[entries.availability.windows]]
days = "weekdays" # or "weekends", "all"
start = "15:00"
end = "18:00"
[[entries.availability.windows]]
days = ["sat", "sun"] # Specific days
start = "09:00"
end = "21:00"
```
### Limits
Control session duration and frequency:
```toml
[entries.limits]
max_run_seconds = 1800 # Max duration per session
daily_quota_seconds = 7200 # Total daily limit
cooldown_seconds = 600 # Wait time between sessions
```
## Validation
The configuration is validated at load time. Validation catches:
- **Duplicate entry IDs** - Each entry must have a unique ID
- **Empty commands** - Process entries must specify a command
- **Invalid time windows** - Start time must be before end time
- **Invalid thresholds** - Warning thresholds must be less than max run time
- **Negative durations** - All durations must be positive
- **Unknown kinds** - Entry types must be recognized (unless Custom)
```rust
use shepherd_config::{parse_config, ConfigError};
let result = parse_config(toml_str);
match result {
Ok(policy) => { /* Use policy */ }
Err(ConfigError::ValidationFailed { errors }) => {
for error in errors {
eprintln!("Config error: {}", error);
}
}
Err(e) => eprintln!("Failed to load config: {}", e),
}
```
## Hot Reload
Configuration can be reloaded at runtime via the service's `ReloadConfig` command or by sending `SIGHUP` to the service process. Reload is atomic: either the new configuration is fully applied or the old one remains.
Active sessions continue with their original time limits when configuration is reloaded.
## Key Types
- `Policy` - Validated policy ready for the core engine
- `Entry` - A launchable entry definition
- `AvailabilityPolicy` - Time window rules
- `LimitsPolicy` - Duration and quota limits
- `WarningPolicy` - Warning threshold configuration
- `VolumePolicy` - Volume restrictions
## Design Philosophy
- **Human-readable** - TOML is easy to read and write
- **Strict validation** - Catch errors at load time, not runtime
- **Versioned schema** - `config_version` enables future migrations
- **Sensible defaults** - Minimal config is valid
## Dependencies
- `toml` - TOML parsing
- `serde` - Deserialization
- `chrono` - Time types
- `thiserror` - Error types