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.
4.9 KiB
4.9 KiB
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:
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
use shepherd_host_linux::LinuxHost;
let host = LinuxHost::new();
// Check capabilities
let caps = host.capabilities();
assert!(caps.can_kill_forcefully);
Spawning Processes
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:
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:
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
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
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:
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
- PipeWire (
wpctlorpw-cli) - Modern default on Ubuntu 22.04+, Fedora - PulseAudio (
pactl) - Legacy but widely available - ALSA (
amixer) - Fallback for systems without a sound server
Process Group Handling
All spawned processes are placed in their own process group:
// Internally uses setsid() or setpgid()
// This allows killing the entire process tree
When stopping a session:
- SIGTERM is sent to the process group (
-pgid) - After timeout, SIGKILL is sent to the process group
- 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 callstokio- Async runtimetracing- Loggingserde- Serializationshepherd-host-api- Trait definitionsshepherd-api- Entry types