# 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
