use anyhow::Context;
use clap::{Parser, Subcommand, ValueEnum};
use serde::{Deserialize, Serialize};
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, ValueEnum, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum ApiGroup {
GpuControl,
GpuRead,
CpuRead,
}
impl ApiGroup {
pub fn requires_root(&self) -> bool {
matches!(self, ApiGroup::GpuControl | ApiGroup::CpuRead)
}
}
impl std::fmt::Display for ApiGroup {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ApiGroup::GpuControl => write!(f, "gpu-control"),
ApiGroup::GpuRead => write!(f, "gpu-read"),
ApiGroup::CpuRead => write!(f, "cpu-read"),
}
}
}
#[derive(Parser, Debug)]
#[command(version)]
pub struct Cli {
#[command(subcommand)]
pub command: Command,
}
#[derive(Subcommand, Debug)]
pub enum Command {
Serve(ServeConfig),
Token {
#[command(subcommand)]
action: TokenCommand,
},
}
#[derive(Subcommand, Debug)]
pub enum TokenCommand {
Issue(TokenIssueConfig),
}
#[derive(Parser, Debug)]
pub struct ServeConfig {
#[clap(long, default_value = "uds")]
pub mode: ConnectionMode,
#[clap(long, default_value = "/var/run/zeusd.sock")]
pub socket_path: String,
#[clap(long, default_value = "666")]
socket_permissions: String,
#[clap(long)]
pub socket_uid: Option<u32>,
#[clap(long)]
pub socket_gid: Option<u32>,
#[clap(long, default_value = "127.0.0.1:4938")]
pub tcp_bind_address: String,
#[clap(long)]
pub num_workers: Option<usize>,
#[clap(long, default_value = "20")]
pub gpu_power_poll_hz: u32,
#[clap(long, default_value = "10")]
pub cpu_power_poll_hz: u32,
#[clap(
long,
value_delimiter = ',',
default_values_t = [ApiGroup::GpuControl, ApiGroup::GpuRead, ApiGroup::CpuRead],
)]
pub enable: Vec<ApiGroup>,
#[clap(long)]
pub signing_key_path: Option<String>,
}
impl ServeConfig {
pub fn socket_permissions(&self) -> anyhow::Result<u32> {
u32::from_str_radix(&self.socket_permissions, 8)
.context("Failed to parse socket permissions")
}
pub fn is_enabled(&self, group: ApiGroup) -> bool {
self.enable.contains(&group)
}
pub fn needs_gpu(&self) -> bool {
self.is_enabled(ApiGroup::GpuControl) || self.is_enabled(ApiGroup::GpuRead)
}
pub fn needs_cpu(&self) -> bool {
self.is_enabled(ApiGroup::CpuRead)
}
}
#[derive(Parser, Debug)]
pub struct TokenIssueConfig {
#[clap(long)]
pub signing_key_path: String,
#[clap(long)]
pub user: String,
#[clap(long, value_delimiter = ',')]
pub scope: Vec<ApiGroup>,
#[clap(long)]
pub expires: String,
}
impl TokenIssueConfig {
pub fn expires_at(&self) -> anyhow::Result<Option<usize>> {
let s = self.expires.trim().to_lowercase();
if s == "never" || s == "0" {
return Ok(None);
}
let duration: std::time::Duration = s
.parse::<humantime::Duration>()
.context(format!(
"Invalid duration '{}'. Use e.g. '1h', '7d', '30d', or 'never'.",
self.expires
))?
.into();
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.context("System clock error")?;
Ok(Some((now + duration).as_secs() as usize))
}
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Debug)]
pub enum ConnectionMode {
UDS,
TCP,
}
pub fn get_cli() -> Cli {
Cli::parse()
}