234 lines
6.8 KiB
Markdown
234 lines
6.8 KiB
Markdown
# shepherd-ipc
|
|
|
|
IPC layer for Shepherd.
|
|
|
|
## Overview
|
|
|
|
This crate provides the local inter-process communication infrastructure between the Shepherd service (`shepherdd`) and its clients (launcher UI, HUD overlay, admin tools). It includes:
|
|
|
|
- **Unix domain socket server** - Listens for client connections
|
|
- **NDJSON protocol** - Newline-delimited JSON message framing
|
|
- **Client management** - Connection tracking and cleanup
|
|
- **Peer authentication** - UID-based role assignment
|
|
- **Event broadcasting** - Push events to subscribed clients
|
|
|
|
## Architecture
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────┐
|
|
│ shepherdd │
|
|
│ ┌──────────────────────────────────────────────────┐ │
|
|
│ │ IpcServer │ │
|
|
│ │ ┌──────────┐ ┌─────────┐ ┌─────────┐ │ │
|
|
│ │ │Client 1 │ │Client 2 │ │Client 3 │ ... │ │
|
|
│ │ │(Launcher)│ │ (HUD) │ │ (Admin) │ │ │
|
|
│ │ └────┬─────┘ └────┬────┘ └────┬────┘ │ │
|
|
│ │ │ │ │ │ │
|
|
│ │ └────────────┴───────────┘ │ │
|
|
│ │ Unix Domain Socket │ │
|
|
│ └──────────────────────────────────────────────────┘ │
|
|
└─────────────────────────────────────────────────────────┘
|
|
│ │ │
|
|
┌────┴────┐ ┌────┴────┐ ┌────┴────┐
|
|
│Launcher │ │ HUD │ │ Admin │
|
|
│ UI │ │ Overlay │ │ Tool │
|
|
└─────────┘ └─────────┘ └─────────┘
|
|
```
|
|
|
|
## Server Usage
|
|
|
|
### Starting the Server
|
|
|
|
```rust
|
|
use shepherd_ipc::IpcServer;
|
|
|
|
let mut server = IpcServer::new("/run/shepherdd/shepherdd.sock");
|
|
server.start().await?;
|
|
|
|
// Get message receiver for the main loop
|
|
let mut messages = server.take_message_receiver().await.unwrap();
|
|
|
|
// Accept connections in background
|
|
tokio::spawn(async move {
|
|
server.run().await
|
|
});
|
|
|
|
// Process messages in main loop
|
|
while let Some(msg) = messages.recv().await {
|
|
match msg {
|
|
ServerMessage::Request { client_id, request } => {
|
|
// Handle request, send response
|
|
let response = handle_request(request);
|
|
server.send_response(&client_id, response).await?;
|
|
}
|
|
ServerMessage::ClientConnected { client_id, info } => {
|
|
println!("Client {} connected as {:?}", client_id, info.role);
|
|
}
|
|
ServerMessage::ClientDisconnected { client_id } => {
|
|
println!("Client {} disconnected", client_id);
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### Broadcasting Events
|
|
|
|
```rust
|
|
use shepherd_api::Event;
|
|
|
|
// Send to all subscribed clients
|
|
server.broadcast_event(Event::new(EventPayload::StateChanged(snapshot))).await;
|
|
```
|
|
|
|
### Client Roles
|
|
|
|
Clients are assigned roles based on their peer UID:
|
|
|
|
| UID | Role | Permissions |
|
|
|-----|------|-------------|
|
|
| root (0) | `Admin` | All commands |
|
|
| Service user | `Admin` | All commands |
|
|
| Other | `Shell` | Read + Launch/Stop |
|
|
|
|
```rust
|
|
// Role-based command filtering
|
|
match (request.command, client_info.role) {
|
|
(Command::ReloadConfig, ClientRole::Admin) => { /* allowed */ }
|
|
(Command::ReloadConfig, ClientRole::Shell) => { /* denied */ }
|
|
(Command::Launch { .. }, _) => { /* allowed for all */ }
|
|
// ...
|
|
}
|
|
```
|
|
|
|
## Client Usage
|
|
|
|
### Connecting
|
|
|
|
```rust
|
|
use shepherd_ipc::IpcClient;
|
|
|
|
let mut client = IpcClient::connect("/run/shepherdd/shepherdd.sock").await?;
|
|
```
|
|
|
|
### Sending Commands
|
|
|
|
```rust
|
|
use shepherd_api::{Command, Response};
|
|
|
|
// Request current state
|
|
client.send(Command::GetState).await?;
|
|
let response: Response = client.recv().await?;
|
|
|
|
// Launch an entry
|
|
client.send(Command::Launch {
|
|
entry_id: "minecraft".into()
|
|
}).await?;
|
|
let response = client.recv().await?;
|
|
```
|
|
|
|
### Subscribing to Events
|
|
|
|
```rust
|
|
// Subscribe to event stream
|
|
client.send(Command::SubscribeEvents).await?;
|
|
|
|
// Receive events
|
|
loop {
|
|
match client.recv_event().await {
|
|
Ok(event) => {
|
|
match event.payload {
|
|
EventPayload::WarningIssued { remaining, .. } => {
|
|
println!("Warning: {} seconds remaining", remaining.as_secs());
|
|
}
|
|
EventPayload::SessionEnded { .. } => {
|
|
println!("Session ended");
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
Err(IpcError::ConnectionClosed) => break,
|
|
Err(e) => eprintln!("Error: {}", e),
|
|
}
|
|
}
|
|
```
|
|
|
|
## Protocol
|
|
|
|
### Message Format
|
|
|
|
Messages use NDJSON (newline-delimited JSON):
|
|
|
|
```
|
|
{"type":"request","id":1,"command":"get_state"}\n
|
|
{"type":"response","id":1,"payload":{"api_version":1,...}}\n
|
|
{"type":"event","payload":{"type":"state_changed",...}}\n
|
|
```
|
|
|
|
### Request/Response
|
|
|
|
Each request has an ID, matched in the response:
|
|
|
|
```json
|
|
// Request
|
|
{"type":"request","id":42,"command":{"type":"launch","entry_id":"minecraft"}}
|
|
|
|
// Response
|
|
{"type":"response","id":42,"success":true,"payload":{...}}
|
|
```
|
|
|
|
### Events
|
|
|
|
Events are pushed without request IDs:
|
|
|
|
```json
|
|
{"type":"event","payload":{"type":"warning_issued","threshold":60,"remaining":{"secs":60}}}
|
|
```
|
|
|
|
## Socket Permissions
|
|
|
|
The socket is created with mode `0660`:
|
|
- Owner can read/write
|
|
- Group can read/write
|
|
- Others have no access
|
|
|
|
This allows the service to run as a dedicated user while permitting group members (e.g., `shepherd` group) to connect.
|
|
|
|
## Rate Limiting
|
|
|
|
Per-client rate limiting prevents buggy or malicious clients from overwhelming the service:
|
|
|
|
```rust
|
|
// Default: 10 commands per second per client
|
|
if rate_limiter.check(&client_id) {
|
|
// Process command
|
|
} else {
|
|
// Respond with rate limit error
|
|
}
|
|
```
|
|
|
|
## Error Handling
|
|
|
|
```rust
|
|
use shepherd_ipc::IpcError;
|
|
|
|
match result {
|
|
Err(IpcError::ConnectionClosed) => {
|
|
// Client disconnected
|
|
}
|
|
Err(IpcError::Json(e)) => {
|
|
// Protocol error
|
|
}
|
|
Err(IpcError::Io(e)) => {
|
|
// Socket error
|
|
}
|
|
_ => {}
|
|
}
|
|
```
|
|
|
|
## Dependencies
|
|
|
|
- `tokio` - Async runtime
|
|
- `serde` / `serde_json` - JSON serialization
|
|
- `nix` - Unix socket peer credentials
|
|
- `shepherd-api` - Message types
|
|
- `shepherd-util` - Client IDs
|