use serde::{Deserialize, Serialize};
use utoipa::ToSchema;
use crate::api::nodes::GpuInfoSummary;
use crate::spec::{ArchKind, OsKind};
#[derive(Debug, Clone, Deserialize, ToSchema)]
pub struct ClusterJoinRequest {
pub token: String,
pub advertise_addr: String,
pub overlay_port: u16,
pub raft_port: u16,
#[serde(default = "default_api_port")]
pub api_port: u16,
pub wg_public_key: String,
#[serde(default = "default_mode")]
pub mode: String,
pub services: Option<Vec<String>>,
#[serde(default)]
pub cpu_total: f64,
#[serde(default)]
pub memory_total: u64,
#[serde(default)]
pub disk_total: u64,
#[serde(default)]
pub gpus: Vec<GpuInfoSummary>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub os: Option<OsKind>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub arch: Option<ArchKind>,
#[serde(default, skip_serializing_if = "std::collections::HashMap::is_empty")]
pub labels: std::collections::HashMap<String, String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub secrets_pubkey: Option<[u8; 32]>,
}
#[must_use]
pub fn default_mode() -> String {
"full".to_string()
}
#[must_use]
pub fn default_api_port() -> u16 {
3669
}
#[derive(Debug, Clone, Serialize, ToSchema)]
pub struct ClusterJoinResponse {
pub node_id: String,
pub raft_node_id: u64,
pub overlay_ip: String,
#[serde(default)]
pub slice_cidr: String,
pub peers: Vec<ClusterPeer>,
pub role: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub node_jwt: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub wrapped_dek: Option<Vec<u8>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub dek_generation: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub join_secret: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub warnings: Option<Vec<String>>,
}
#[derive(Debug, Clone, Serialize, ToSchema)]
pub struct ClusterPeer {
pub node_id: String,
pub raft_node_id: u64,
pub advertise_addr: String,
pub overlay_port: u16,
pub raft_port: u16,
pub wg_public_key: String,
pub overlay_ip: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct ClusterJoinClaims {
pub api_endpoint: String,
pub raft_endpoint: String,
pub leader_wg_pubkey: String,
pub overlay_cidr: String,
pub exp: String,
pub iat: String,
pub iss: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct SignedClusterJoinToken {
pub v: u32,
pub kid: String,
pub claims: ClusterJoinClaims,
pub sig: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ca_chain: Option<CaCert>,
}
pub const SIGNED_TOKEN_V_WAVE3: u32 = 1;
pub const SIGNED_TOKEN_V_WAVE9: u32 = 2;
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct CaCert {
pub v: u32,
pub active_kid: String,
pub active_pubkey_b64: String,
pub issued_at: String,
pub expires_at: String,
pub cluster_domain: String,
pub sig_by_ca: String,
}
pub const CA_CERT_FORMAT_VERSION: u32 = 1;
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct SigningPubkeyResponse {
pub public_key_b64: String,
pub kid: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct SigningPubkeyEntry {
pub kid: String,
pub public_key_b64: String,
pub status: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub valid_until: Option<String>,
pub created_at: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct SigningPubkeysResponse {
pub keys: Vec<SigningPubkeyEntry>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, ToSchema)]
pub struct RotateSigningKeyRequest {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub grace: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct RotateSigningKeyResponse {
pub kid: String,
pub public_key_b64: String,
pub previous_kid: String,
pub previous_grace_until: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct TrustBundle {
pub v: u32,
pub cluster_domain: String,
pub ca_public_key_b64: String,
pub ca_kid: String,
pub generated_at: String,
}
pub const TRUST_BUNDLE_FORMAT_VERSION: u32 = 1;
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct ImportTrustBundleRequest {
pub bundle: TrustBundle,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub source_url: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct ImportTrustBundleResponse {
pub cluster_domain: String,
pub ca_kid: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct TrustedBundleEntry {
pub cluster_domain: String,
pub ca_kid: String,
pub ca_public_key_b64: String,
pub generated_at: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub source_url: Option<String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, ToSchema)]
pub struct TrustedBundlesResponse {
pub bundles: Vec<TrustedBundleEntry>,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct RevokeTokenRequest {
pub token_or_hash: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub reason: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct RevokeTokenResponse {
pub token_hash: String,
pub expires_at: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct RevocationEntry {
pub token_hash: String,
pub expires_at: String,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, ToSchema)]
pub struct RevocationListResponse {
pub revocations: Vec<RevocationEntry>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema, Default)]
#[serde(rename_all = "lowercase")]
pub enum JwtAlgorithm {
Hs256,
#[default]
Both,
Eddsa,
}
impl JwtAlgorithm {
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
Self::Hs256 => "hs256",
Self::Both => "both",
Self::Eddsa => "eddsa",
}
}
}
impl std::fmt::Display for JwtAlgorithm {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct SetJwtAlgorithmRequest {
pub algorithm: JwtAlgorithm,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct JwtStatusResponse {
pub algorithm: JwtAlgorithm,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub join_secret_wiped_at: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct WorkerSummary {
pub id: u64,
pub api_addr: String,
#[serde(default, skip_serializing_if = "std::collections::HashMap::is_empty")]
pub labels: std::collections::HashMap<String, String>,
pub os: String,
pub last_seen_unix_secs: i64,
pub state: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct GossipPeerSummary {
pub node_id: u64,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub wg_pubkey: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub wg_endpoint: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub overlay_ip: Option<String>,
#[serde(default, skip_serializing_if = "std::collections::HashMap::is_empty")]
pub labels: std::collections::HashMap<String, String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct ClusterNodeSummary {
pub id: String,
pub address: String,
pub advertise_addr: String,
#[serde(default)]
pub api_endpoint: String,
pub status: String,
pub role: String,
pub mode: String,
pub is_leader: bool,
pub overlay_ip: String,
pub cpu_total: f64,
pub cpu_used: f64,
pub memory_total: u64,
pub memory_used: u64,
pub registered_at: u64,
pub last_heartbeat: u64,
}