shepherd-launcher/crates/shepherd-host-linux/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

206 lines
4.9 KiB
Markdown

# shepherd-host-linux
Linux host adapter for Shepherd.
## Overview
This crate implements the `HostAdapter` trait for Linux systems, providing:
- **Process spawning** with process group isolation
- **Process termination** via graceful (SIGTERM) and forceful (SIGKILL) signals
- **Exit observation** through async process monitoring
- **Snap application support** via systemd scope-based management
- **stdout/stderr capture** to log files
- **Volume control** with auto-detection of sound systems (PipeWire, PulseAudio, ALSA)
## Capabilities
The Linux adapter reports these capabilities:
```rust
HostCapabilities {
// Supported entry kinds
spawn_kind_supported: [Process, Snap],
// Enforcement capabilities
can_kill_forcefully: true, // SIGKILL
can_graceful_stop: true, // SIGTERM
can_group_process_tree: true, // Process groups (pgid)
can_observe_exit: true, // async wait
// Optional features (not yet implemented)
can_observe_window_ready: false,
can_force_foreground: false,
can_force_fullscreen: false,
can_lock_to_single_app: false,
}
```
## Usage
### Creating the Adapter
```rust
use shepherd_host_linux::LinuxHost;
let host = LinuxHost::new();
// Check capabilities
let caps = host.capabilities();
assert!(caps.can_kill_forcefully);
```
### Spawning Processes
```rust
use shepherd_host_api::{SpawnOptions, EntryKind};
let entry_kind = EntryKind::Process {
command: "/usr/bin/game".to_string(),
args: vec!["--fullscreen".to_string()],
env: Default::default(),
cwd: None,
};
let options = SpawnOptions {
capture_stdout: true,
capture_stderr: true,
log_path: Some("/var/log/shepherdd/sessions".into()),
fullscreen: false,
foreground: false,
};
let handle = host.spawn(session_id, &entry_kind, options).await?;
```
### Spawning Snap Applications
Snap applications are managed using systemd scopes for proper process tracking:
```rust
let entry_kind = EntryKind::Snap {
snap_name: "mc-installer".to_string(),
command: None, // Defaults to snap_name
args: vec![],
env: Default::default(),
};
// Spawns via: snap run mc-installer
// Process group is isolated within a systemd scope
let handle = host.spawn(session_id, &entry_kind, options).await?;
```
### Spawning Steam Games
Steam games are launched via the Steam snap:
```rust
let entry_kind = EntryKind::Steam {
app_id: 504230,
args: vec![],
env: Default::default(),
};
// Spawns via: snap run steam steam://rungameid/504230
let handle = host.spawn(session_id, &entry_kind, options).await?;
```
### Stopping Sessions
```rust
use shepherd_host_api::StopMode;
use std::time::Duration;
// Graceful: SIGTERM, wait 5s, then SIGKILL
host.stop(&handle, StopMode::Graceful {
timeout: Duration::from_secs(5),
}).await?;
// Force: immediate SIGKILL
host.stop(&handle, StopMode::Force).await?;
```
### Monitoring Exits
```rust
let mut events = host.subscribe();
tokio::spawn(async move {
while let Some(event) = events.recv().await {
match event {
HostEvent::Exited { handle, status } => {
println!("Session {} exited: {:?}", handle.session_id(), status);
}
_ => {}
}
}
});
```
## Volume Control
The crate includes `LinuxVolumeController` which auto-detects the available sound system:
```rust
use shepherd_host_linux::LinuxVolumeController;
let controller = LinuxVolumeController::new().await?;
// Get current volume (0-100)
let volume = controller.get_volume().await?;
// Set volume with enforcement of configured maximum
controller.set_volume(75).await?;
// Mute/unmute
controller.set_muted(true).await?;
```
### Sound System Detection Order
1. **PipeWire** (`wpctl` or `pw-cli`) - Modern default on Ubuntu 22.04+, Fedora
2. **PulseAudio** (`pactl`) - Legacy but widely available
3. **ALSA** (`amixer`) - Fallback for systems without a sound server
## Process Group Handling
All spawned processes are placed in their own process group:
```rust
// Internally uses setsid() or setpgid()
// This allows killing the entire process tree
```
When stopping a session:
1. SIGTERM is sent to the process group (`-pgid`)
2. After timeout, SIGKILL is sent to the process group
3. Orphaned children are cleaned up
## Log Capture
stdout and stderr can be captured to session log files:
```
/var/log/shepherdd/sessions/
├── 2025-01-15-abc123-minecraft.log
├── 2025-01-15-def456-gcompris.log
└── ...
```
## Future Enhancements
Planned features (hooks are designed in):
- **cgroups v2** - CPU/memory/IO limits per session
- **Namespace isolation** - Optional sandboxing
- **Sway/Wayland integration** - Focus and fullscreen control
- **D-Bus monitoring** - Window readiness detection
## Dependencies
- `nix` - Unix system calls
- `tokio` - Async runtime
- `tracing` - Logging
- `serde` - Serialization
- `shepherd-host-api` - Trait definitions
- `shepherd-api` - Entry types