shepherdd shouldn't require root to run
This commit is contained in:
parent
91e1547902
commit
3b1a2fb166
13 changed files with 208 additions and 39 deletions
|
|
@ -4,10 +4,13 @@
|
||||||
config_version = 1
|
config_version = 1
|
||||||
|
|
||||||
[service]
|
[service]
|
||||||
# Uncomment to customize paths
|
# Uncomment to customize paths (defaults use XDG directories for user-writable access)
|
||||||
# socket_path = "/run/shepherdd/shepherdd.sock"
|
# socket_path defaults to $XDG_RUNTIME_DIR/shepherdd/shepherdd.sock
|
||||||
# log_dir = "/var/log/shepherdd"
|
# log_dir defaults to $XDG_STATE_HOME/shepherdd or ~/.local/state/shepherdd
|
||||||
# data_dir = "/var/lib/shepherdd"
|
# data_dir defaults to $XDG_DATA_HOME/shepherdd or ~/.local/share/shepherdd
|
||||||
|
# socket_path = "/run/user/1000/shepherdd/shepherdd.sock"
|
||||||
|
# log_dir = "~/.local/state/shepherdd"
|
||||||
|
# data_dir = "~/.local/share/shepherdd"
|
||||||
|
|
||||||
# Default max run duration if not specified per entry (1 hour)
|
# Default max run duration if not specified per entry (1 hour)
|
||||||
# Set to 0 for unlimited (no time limit)
|
# Set to 0 for unlimited (no time limit)
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
use crate::schema::{RawConfig, RawEntry, RawEntryKind, RawVolumeConfig, RawServiceConfig, RawWarningThreshold};
|
use crate::schema::{RawConfig, RawEntry, RawEntryKind, RawVolumeConfig, RawServiceConfig, RawWarningThreshold};
|
||||||
use crate::validation::{parse_days, parse_time};
|
use crate::validation::{parse_days, parse_time};
|
||||||
use shepherd_api::{EntryKind, WarningSeverity, WarningThreshold};
|
use shepherd_api::{EntryKind, WarningSeverity, WarningThreshold};
|
||||||
use shepherd_util::{DaysOfWeek, EntryId, TimeWindow, WallClock};
|
use shepherd_util::{DaysOfWeek, EntryId, TimeWindow, WallClock, default_data_dir, default_log_dir, socket_path_without_env};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
|
@ -84,13 +84,13 @@ impl ServiceConfig {
|
||||||
Self {
|
Self {
|
||||||
socket_path: raw
|
socket_path: raw
|
||||||
.socket_path
|
.socket_path
|
||||||
.unwrap_or_else(|| PathBuf::from("/run/shepherdd/shepherdd.sock")),
|
.unwrap_or_else(socket_path_without_env),
|
||||||
log_dir: raw
|
log_dir: raw
|
||||||
.log_dir
|
.log_dir
|
||||||
.unwrap_or_else(|| PathBuf::from("/var/log/shepherdd")),
|
.unwrap_or_else(default_log_dir),
|
||||||
data_dir: raw
|
data_dir: raw
|
||||||
.data_dir
|
.data_dir
|
||||||
.unwrap_or_else(|| PathBuf::from("/var/lib/shepherdd")),
|
.unwrap_or_else(default_data_dir),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -98,9 +98,9 @@ impl ServiceConfig {
|
||||||
impl Default for ServiceConfig {
|
impl Default for ServiceConfig {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
socket_path: PathBuf::from("/run/shepherdd/shepherdd.sock"),
|
socket_path: socket_path_without_env(),
|
||||||
log_dir: PathBuf::from("/var/log/shepherdd"),
|
log_dir: default_log_dir(),
|
||||||
data_dir: PathBuf::from("/var/lib/shepherdd"),
|
data_dir: default_data_dir(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,13 +22,13 @@ pub struct RawConfig {
|
||||||
/// Service-level settings
|
/// Service-level settings
|
||||||
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
|
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
|
||||||
pub struct RawServiceConfig {
|
pub struct RawServiceConfig {
|
||||||
/// IPC socket path (default: /run/shepherdd/shepherdd.sock)
|
/// IPC socket path (default: $XDG_RUNTIME_DIR/shepherdd/shepherdd.sock)
|
||||||
pub socket_path: Option<PathBuf>,
|
pub socket_path: Option<PathBuf>,
|
||||||
|
|
||||||
/// Log directory
|
/// Log directory (default: $XDG_STATE_HOME/shepherdd)
|
||||||
pub log_dir: Option<PathBuf>,
|
pub log_dir: Option<PathBuf>,
|
||||||
|
|
||||||
/// Data directory for store
|
/// Data directory for store (default: $XDG_DATA_HOME/shepherdd)
|
||||||
pub data_dir: Option<PathBuf>,
|
pub data_dir: Option<PathBuf>,
|
||||||
|
|
||||||
/// Default warning thresholds (can be overridden per entry)
|
/// Default warning thresholds (can be overridden per entry)
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@ shepherd-hud --anchor top --height 48
|
||||||
|
|
||||||
| Option | Default | Description |
|
| Option | Default | Description |
|
||||||
|--------|---------|-------------|
|
|--------|---------|-------------|
|
||||||
| `-s, --socket` | `/run/shepherdd/shepherdd.sock` | Service socket path |
|
| `-s, --socket` | `$XDG_RUNTIME_DIR/shepherdd/shepherdd.sock` | Service socket path |
|
||||||
| `-l, --log-level` | `info` | Log verbosity |
|
| `-l, --log-level` | `info` | Log verbosity |
|
||||||
| `-a, --anchor` | `top` | Screen edge (`top` or `bottom`) |
|
| `-a, --anchor` | `top` | Screen edge (`top` or `bottom`) |
|
||||||
| `--height` | `48` | HUD bar height in pixels |
|
| `--height` | `48` | HUD bar height in pixels |
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ use gtk4::prelude::*;
|
||||||
use gtk4_layer_shell::{Edge, Layer, LayerShell};
|
use gtk4_layer_shell::{Edge, Layer, LayerShell};
|
||||||
use shepherd_api::Command;
|
use shepherd_api::Command;
|
||||||
use shepherd_ipc::IpcClient;
|
use shepherd_ipc::IpcClient;
|
||||||
|
use shepherd_util::default_socket_path;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
@ -329,12 +330,11 @@ fn build_hud_content(state: SharedState) -> gtk4::Box {
|
||||||
if let Some(session_id) = session_state.session_id() {
|
if let Some(session_id) = session_state.session_id() {
|
||||||
tracing::info!("Requesting end session for {}", session_id);
|
tracing::info!("Requesting end session for {}", session_id);
|
||||||
// Send StopCurrent command to shepherdd
|
// Send StopCurrent command to shepherdd
|
||||||
let socket_path = std::env::var("SHEPHERD_SOCKET")
|
let socket_path = default_socket_path();
|
||||||
.unwrap_or_else(|_| "./dev-runtime/shepherd.sock".to_string());
|
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
let rt = Runtime::new().expect("Failed to create runtime");
|
let rt = Runtime::new().expect("Failed to create runtime");
|
||||||
rt.block_on(async {
|
rt.block_on(async {
|
||||||
match IpcClient::connect(std::path::PathBuf::from(&socket_path)).await {
|
match IpcClient::connect(&socket_path).await {
|
||||||
Ok(mut client) => {
|
Ok(mut client) => {
|
||||||
let cmd = Command::StopCurrent {
|
let cmd = Command::StopCurrent {
|
||||||
mode: shepherd_api::StopMode::Graceful,
|
mode: shepherd_api::StopMode::Graceful,
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ mod volume;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
use shepherd_util::default_socket_path;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use tracing_subscriber::EnvFilter;
|
use tracing_subscriber::EnvFilter;
|
||||||
|
|
||||||
|
|
@ -20,8 +21,8 @@ use tracing_subscriber::EnvFilter;
|
||||||
#[command(about = "GTK4 layer-shell HUD for shepherdd", long_about = None)]
|
#[command(about = "GTK4 layer-shell HUD for shepherdd", long_about = None)]
|
||||||
struct Args {
|
struct Args {
|
||||||
/// Socket path for shepherdd connection (or set SHEPHERD_SOCKET env var)
|
/// Socket path for shepherdd connection (or set SHEPHERD_SOCKET env var)
|
||||||
#[arg(short, long, env = "SHEPHERD_SOCKET", default_value = "/run/shepherdd/shepherdd.sock")]
|
#[arg(short, long, env = "SHEPHERD_SOCKET")]
|
||||||
socket: PathBuf,
|
socket: Option<PathBuf>,
|
||||||
|
|
||||||
/// Log level
|
/// Log level
|
||||||
#[arg(short, long, default_value = "info")]
|
#[arg(short, long, default_value = "info")]
|
||||||
|
|
@ -49,8 +50,11 @@ fn main() -> Result<()> {
|
||||||
|
|
||||||
tracing::info!("Starting Shepherd HUD");
|
tracing::info!("Starting Shepherd HUD");
|
||||||
|
|
||||||
|
// Determine socket path with fallback to default
|
||||||
|
let socket_path = args.socket.unwrap_or_else(default_socket_path);
|
||||||
|
|
||||||
// Run GTK application
|
// Run GTK application
|
||||||
let application = app::HudApp::new(args.socket, args.anchor, args.height);
|
let application = app::HudApp::new(socket_path, args.anchor, args.height);
|
||||||
let exit_code = application.run();
|
let exit_code = application.run();
|
||||||
|
|
||||||
std::process::exit(exit_code);
|
std::process::exit(exit_code);
|
||||||
|
|
|
||||||
|
|
@ -5,19 +5,12 @@
|
||||||
|
|
||||||
use shepherd_api::{Command, ResponsePayload, VolumeInfo};
|
use shepherd_api::{Command, ResponsePayload, VolumeInfo};
|
||||||
use shepherd_ipc::IpcClient;
|
use shepherd_ipc::IpcClient;
|
||||||
use std::path::PathBuf;
|
use shepherd_util::default_socket_path;
|
||||||
use tokio::runtime::Runtime;
|
use tokio::runtime::Runtime;
|
||||||
|
|
||||||
/// Get the default socket path from environment or fallback
|
|
||||||
fn get_socket_path() -> PathBuf {
|
|
||||||
std::env::var("SHEPHERD_SOCKET")
|
|
||||||
.map(PathBuf::from)
|
|
||||||
.unwrap_or_else(|_| PathBuf::from("./dev-runtime/shepherd.sock"))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get current volume status from shepherdd
|
/// Get current volume status from shepherdd
|
||||||
pub fn get_volume_status() -> Option<VolumeInfo> {
|
pub fn get_volume_status() -> Option<VolumeInfo> {
|
||||||
let socket_path = get_socket_path();
|
let socket_path = default_socket_path();
|
||||||
|
|
||||||
let rt = match Runtime::new() {
|
let rt = match Runtime::new() {
|
||||||
Ok(rt) => rt,
|
Ok(rt) => rt,
|
||||||
|
|
@ -54,7 +47,7 @@ pub fn get_volume_status() -> Option<VolumeInfo> {
|
||||||
|
|
||||||
/// Toggle mute state via shepherdd
|
/// Toggle mute state via shepherdd
|
||||||
pub fn toggle_mute() -> anyhow::Result<()> {
|
pub fn toggle_mute() -> anyhow::Result<()> {
|
||||||
let socket_path = get_socket_path();
|
let socket_path = default_socket_path();
|
||||||
|
|
||||||
let rt = Runtime::new()?;
|
let rt = Runtime::new()?;
|
||||||
|
|
||||||
|
|
@ -77,7 +70,7 @@ pub fn toggle_mute() -> anyhow::Result<()> {
|
||||||
|
|
||||||
/// Set volume to a specific percentage via shepherdd
|
/// Set volume to a specific percentage via shepherdd
|
||||||
pub fn set_volume(percent: u8) -> anyhow::Result<()> {
|
pub fn set_volume(percent: u8) -> anyhow::Result<()> {
|
||||||
let socket_path = get_socket_path();
|
let socket_path = default_socket_path();
|
||||||
|
|
||||||
let rt = Runtime::new()?;
|
let rt = Runtime::new()?;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -49,14 +49,14 @@ This is what users see when no session is active—the "home screen" of the envi
|
||||||
shepherd-launcher
|
shepherd-launcher
|
||||||
|
|
||||||
# With custom socket path
|
# With custom socket path
|
||||||
shepherd-launcher --socket /run/shepherdd/shepherdd.sock
|
shepherd-launcher --socket /custom/path/shepherdd.sock
|
||||||
```
|
```
|
||||||
|
|
||||||
### Command-Line Options
|
### Command-Line Options
|
||||||
|
|
||||||
| Option | Default | Description |
|
| Option | Default | Description |
|
||||||
|--------|---------|-------------|
|
|--------|---------|-------------|
|
||||||
| `-s, --socket` | `/run/shepherdd/shepherdd.sock` | Service socket path |
|
| `-s, --socket` | `$XDG_RUNTIME_DIR/shepherdd/shepherdd.sock` | Service socket path |
|
||||||
| `-l, --log-level` | `info` | Log verbosity |
|
| `-l, --log-level` | `info` | Log verbosity |
|
||||||
|
|
||||||
## Grid Behavior
|
## Grid Behavior
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ mod tile;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
use shepherd_util::default_socket_path;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use tracing_subscriber::EnvFilter;
|
use tracing_subscriber::EnvFilter;
|
||||||
|
|
||||||
|
|
@ -20,8 +21,8 @@ use tracing_subscriber::EnvFilter;
|
||||||
#[command(about = "GTK4 launcher UI for shepherdd", long_about = None)]
|
#[command(about = "GTK4 launcher UI for shepherdd", long_about = None)]
|
||||||
struct Args {
|
struct Args {
|
||||||
/// Socket path for shepherdd connection (or set SHEPHERD_SOCKET env var)
|
/// Socket path for shepherdd connection (or set SHEPHERD_SOCKET env var)
|
||||||
#[arg(short, long, env = "SHEPHERD_SOCKET", default_value = "/run/shepherdd/shepherdd.sock")]
|
#[arg(short, long, env = "SHEPHERD_SOCKET")]
|
||||||
socket: PathBuf,
|
socket: Option<PathBuf>,
|
||||||
|
|
||||||
/// Log level
|
/// Log level
|
||||||
#[arg(short, long, default_value = "info")]
|
#[arg(short, long, default_value = "info")]
|
||||||
|
|
@ -41,8 +42,11 @@ fn main() -> Result<()> {
|
||||||
|
|
||||||
tracing::info!("Starting Shepherd Launcher UI");
|
tracing::info!("Starting Shepherd Launcher UI");
|
||||||
|
|
||||||
|
// Determine socket path with fallback to default
|
||||||
|
let socket_path = args.socket.unwrap_or_else(default_socket_path);
|
||||||
|
|
||||||
// Run GTK application
|
// Run GTK application
|
||||||
let application = app::LauncherApp::new(args.socket);
|
let application = app::LauncherApp::new(socket_path);
|
||||||
let exit_code = application.run();
|
let exit_code = application.run();
|
||||||
|
|
||||||
std::process::exit(exit_code);
|
std::process::exit(exit_code);
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ This crate provides common utilities and types used across all Shepherd crates,
|
||||||
- **Time utilities** - Monotonic time handling and duration helpers
|
- **Time utilities** - Monotonic time handling and duration helpers
|
||||||
- **Error types** - Common error definitions
|
- **Error types** - Common error definitions
|
||||||
- **Rate limiting** - Helpers for command rate limiting
|
- **Rate limiting** - Helpers for command rate limiting
|
||||||
|
- **Default paths** - XDG-compliant default paths for socket, data, and log directories
|
||||||
|
|
||||||
## Purpose
|
## Purpose
|
||||||
|
|
||||||
|
|
@ -56,6 +57,19 @@ if limiter.check(&client_id) {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Default Paths
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use shepherd_util::{default_socket_path, default_data_dir, default_log_dir};
|
||||||
|
|
||||||
|
// Get XDG-compliant paths (no root required)
|
||||||
|
let socket = default_socket_path(); // $XDG_RUNTIME_DIR/shepherdd/shepherdd.sock
|
||||||
|
let data = default_data_dir(); // $XDG_DATA_HOME/shepherdd or ~/.local/share/shepherdd
|
||||||
|
let logs = default_log_dir(); // $XDG_STATE_HOME/shepherdd or ~/.local/state/shepherdd
|
||||||
|
```
|
||||||
|
|
||||||
|
Environment variables `SHEPHERD_SOCKET` and `SHEPHERD_DATA_DIR` can override the defaults.
|
||||||
|
|
||||||
## Design Philosophy
|
## Design Philosophy
|
||||||
|
|
||||||
- **No platform-specific code** - Pure Rust, works everywhere
|
- **No platform-specific code** - Pure Rust, works everywhere
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,16 @@
|
||||||
//! - Time utilities (monotonic time, duration helpers)
|
//! - Time utilities (monotonic time, duration helpers)
|
||||||
//! - Error types
|
//! - Error types
|
||||||
//! - Rate limiting helpers
|
//! - Rate limiting helpers
|
||||||
|
//! - Default paths for socket, data, and log directories
|
||||||
|
|
||||||
mod error;
|
mod error;
|
||||||
mod ids;
|
mod ids;
|
||||||
|
mod paths;
|
||||||
mod rate_limit;
|
mod rate_limit;
|
||||||
mod time;
|
mod time;
|
||||||
|
|
||||||
pub use error::*;
|
pub use error::*;
|
||||||
pub use ids::*;
|
pub use ids::*;
|
||||||
|
pub use paths::*;
|
||||||
pub use rate_limit::*;
|
pub use rate_limit::*;
|
||||||
pub use time::*;
|
pub use time::*;
|
||||||
|
|
|
||||||
148
crates/shepherd-util/src/paths.rs
Normal file
148
crates/shepherd-util/src/paths.rs
Normal file
|
|
@ -0,0 +1,148 @@
|
||||||
|
//! Default paths for shepherdd components
|
||||||
|
//!
|
||||||
|
//! Provides centralized path defaults that all crates can use.
|
||||||
|
//! Paths are user-writable by default (no root required):
|
||||||
|
//! - Socket: `$XDG_RUNTIME_DIR/shepherdd/shepherdd.sock` or `/tmp/shepherdd-$USER/shepherdd.sock`
|
||||||
|
//! - Data: `$XDG_DATA_HOME/shepherdd` or `~/.local/share/shepherdd`
|
||||||
|
//! - Logs: `$XDG_STATE_HOME/shepherdd` or `~/.local/state/shepherdd`
|
||||||
|
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
/// Environment variable for overriding the socket path
|
||||||
|
pub const SHEPHERD_SOCKET_ENV: &str = "SHEPHERD_SOCKET";
|
||||||
|
|
||||||
|
/// Environment variable for overriding the data directory
|
||||||
|
pub const SHEPHERD_DATA_DIR_ENV: &str = "SHEPHERD_DATA_DIR";
|
||||||
|
|
||||||
|
/// Socket filename within the socket directory
|
||||||
|
const SOCKET_FILENAME: &str = "shepherdd.sock";
|
||||||
|
|
||||||
|
/// Application subdirectory name
|
||||||
|
const APP_DIR: &str = "shepherdd";
|
||||||
|
|
||||||
|
/// Get the default socket path.
|
||||||
|
///
|
||||||
|
/// Order of precedence:
|
||||||
|
/// 1. `$SHEPHERD_SOCKET` environment variable (if set)
|
||||||
|
/// 2. `$XDG_RUNTIME_DIR/shepherdd/shepherdd.sock` (if XDG_RUNTIME_DIR is set)
|
||||||
|
/// 3. `/tmp/shepherdd-$USER/shepherdd.sock` (fallback)
|
||||||
|
pub fn default_socket_path() -> PathBuf {
|
||||||
|
// Check environment override first
|
||||||
|
if let Ok(path) = std::env::var(SHEPHERD_SOCKET_ENV) {
|
||||||
|
return PathBuf::from(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
socket_path_without_env()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the socket path without checking SHEPHERD_SOCKET env var.
|
||||||
|
/// Used for default values in configs where the env var is checked separately.
|
||||||
|
pub fn socket_path_without_env() -> PathBuf {
|
||||||
|
// Try XDG_RUNTIME_DIR first (typically /run/user/<uid>)
|
||||||
|
if let Ok(runtime_dir) = std::env::var("XDG_RUNTIME_DIR") {
|
||||||
|
return PathBuf::from(runtime_dir).join(APP_DIR).join(SOCKET_FILENAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to /tmp with username
|
||||||
|
let username = std::env::var("USER").unwrap_or_else(|_| "unknown".to_string());
|
||||||
|
PathBuf::from(format!("/tmp/{}-{}", APP_DIR, username)).join(SOCKET_FILENAME)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the default data directory.
|
||||||
|
///
|
||||||
|
/// Order of precedence:
|
||||||
|
/// 1. `$SHEPHERD_DATA_DIR` environment variable (if set)
|
||||||
|
/// 2. `$XDG_DATA_HOME/shepherdd` (if XDG_DATA_HOME is set)
|
||||||
|
/// 3. `~/.local/share/shepherdd` (fallback)
|
||||||
|
pub fn default_data_dir() -> PathBuf {
|
||||||
|
// Check environment override first
|
||||||
|
if let Ok(path) = std::env::var(SHEPHERD_DATA_DIR_ENV) {
|
||||||
|
return PathBuf::from(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
data_dir_without_env()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the data directory without checking SHEPHERD_DATA_DIR env var.
|
||||||
|
/// Used for default values in configs where the env var is checked separately.
|
||||||
|
pub fn data_dir_without_env() -> PathBuf {
|
||||||
|
// Try XDG_DATA_HOME first
|
||||||
|
if let Ok(data_home) = std::env::var("XDG_DATA_HOME") {
|
||||||
|
return PathBuf::from(data_home).join(APP_DIR);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to ~/.local/share/shepherdd
|
||||||
|
if let Ok(home) = std::env::var("HOME") {
|
||||||
|
return PathBuf::from(home)
|
||||||
|
.join(".local")
|
||||||
|
.join("share")
|
||||||
|
.join(APP_DIR);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Last resort
|
||||||
|
PathBuf::from("/tmp").join(APP_DIR).join("data")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the default log directory.
|
||||||
|
///
|
||||||
|
/// Order of precedence:
|
||||||
|
/// 1. `$XDG_STATE_HOME/shepherdd` (if XDG_STATE_HOME is set)
|
||||||
|
/// 2. `~/.local/state/shepherdd` (fallback)
|
||||||
|
pub fn default_log_dir() -> PathBuf {
|
||||||
|
// Try XDG_STATE_HOME first
|
||||||
|
if let Ok(state_home) = std::env::var("XDG_STATE_HOME") {
|
||||||
|
return PathBuf::from(state_home).join(APP_DIR);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to ~/.local/state/shepherdd
|
||||||
|
if let Ok(home) = std::env::var("HOME") {
|
||||||
|
return PathBuf::from(home)
|
||||||
|
.join(".local")
|
||||||
|
.join("state")
|
||||||
|
.join(APP_DIR);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Last resort
|
||||||
|
PathBuf::from("/tmp").join(APP_DIR).join("logs")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the parent directory of the socket (for creating it)
|
||||||
|
pub fn socket_dir() -> PathBuf {
|
||||||
|
let socket_path = socket_path_without_env();
|
||||||
|
socket_path.parent().map(|p| p.to_path_buf()).unwrap_or_else(|| {
|
||||||
|
// Should never happen with our paths, but just in case
|
||||||
|
PathBuf::from("/tmp").join(APP_DIR)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn socket_path_contains_shepherdd() {
|
||||||
|
// The socket path should always contain "shepherdd" regardless of environment
|
||||||
|
let path = socket_path_without_env();
|
||||||
|
assert!(path.to_string_lossy().contains("shepherdd"));
|
||||||
|
assert!(path.to_string_lossy().contains(".sock"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn data_dir_contains_shepherdd() {
|
||||||
|
let path = data_dir_without_env();
|
||||||
|
assert!(path.to_string_lossy().contains("shepherdd"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn log_dir_contains_shepherdd() {
|
||||||
|
let path = default_log_dir();
|
||||||
|
assert!(path.to_string_lossy().contains("shepherdd"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn socket_dir_is_parent_of_socket_path() {
|
||||||
|
let socket = socket_path_without_env();
|
||||||
|
let dir = socket_dir();
|
||||||
|
assert_eq!(socket.parent().unwrap(), dir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -74,8 +74,8 @@ shepherdd --log-level debug
|
||||||
|
|
||||||
| Variable | Description |
|
| Variable | Description |
|
||||||
|----------|-------------|
|
|----------|-------------|
|
||||||
| `SHEPHERD_SOCKET` | Override socket path |
|
| `SHEPHERD_SOCKET` | Override socket path (default: `$XDG_RUNTIME_DIR/shepherdd/shepherdd.sock`) |
|
||||||
| `SHEPHERD_DATA_DIR` | Override data directory |
|
| `SHEPHERD_DATA_DIR` | Override data directory (default: `$XDG_DATA_HOME/shepherdd`) |
|
||||||
| `RUST_LOG` | Tracing filter (e.g., `shepherdd=debug`) |
|
| `RUST_LOG` | Tracing filter (e.g., `shepherdd=debug`) |
|
||||||
|
|
||||||
## Main Loop
|
## Main Loop
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue