Skip to main content

paygress/
compute.rs

1// Compute Backend Trait
2//
3// Abstracts the underlying container/VM platform (Proxmox vs LXD vs
4// Docker). The Docker backend (src/docker.rs) is the one that uses
5// ports + env in `ContainerConfig`; LXD/Proxmox backends ignore
6// those fields today and only use the SSH-style fields.
7
8use std::collections::HashMap;
9
10use anyhow::Result;
11use async_trait::async_trait;
12use serde::{Deserialize, Serialize};
13
14#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct NodeStatus {
16    pub cpu_usage: f64,    // 0.0 to 1.0
17    pub memory_used: u64,  // bytes
18    pub memory_total: u64, // bytes
19    pub disk_used: u64,    // bytes
20    pub disk_total: u64,   // bytes
21}
22
23/// One published port mapping. The Docker backend translates this to
24/// a `-p host_port:container_port` flag; LXD/Proxmox can ignore
25/// (they expose the SSH port via the existing host_port field).
26#[derive(Debug, Clone)]
27pub struct PortMapping {
28    pub host_port: u16,
29    pub container_port: u16,
30    pub protocol: &'static str, // "tcp" | "udp"
31}
32
33#[derive(Debug, Clone)]
34pub struct ContainerConfig {
35    pub id: u32,
36    pub name: String,
37    pub image: String,
38    pub cpu_cores: u32,
39    pub memory_mb: u32,
40    pub storage_gb: u32,
41    pub password: String,
42    pub ssh_key: Option<String>,
43    /// SSH host-port forwarding (LXD/Proxmox: SSH access). Distinct
44    /// from `template_ports` which are workload-specific.
45    pub host_port: Option<u16>,
46    /// Workload ports the consumer reaches (e.g. nostr-relay 7777,
47    /// bitcoind RPC 18443). Empty for non-template spawns. Docker
48    /// backend translates each to `-p host:container`; LXD/Proxmox
49    /// backends ignore for now.
50    pub template_ports: Vec<PortMapping>,
51    /// Workload environment variables (template defaults +
52    /// consumer overrides). Docker backend passes via `-e KEY=VAL`.
53    pub template_env: HashMap<String, String>,
54
55    /// Extra `docker run` flags from the template definition (e.g.
56    /// `--ulimit nofile=1048576:1048576` for strfry). LXD/Proxmox
57    /// backends ignore these.
58    pub extra_runtime_args: Vec<String>,
59
60    /// In-container path for the workload's persistent state.
61    /// Docker backend mounts a vmid-scoped volume there.
62    /// `None` = stateless (no volume created).
63    pub data_path: Option<String>,
64
65    /// Optional 32-byte LUKS key for the persistent data volume.
66    /// When set (Phase 2 of consumer-encrypted-volumes), the
67    /// `DockerBackend` creates a LUKS-on-loop file instead of a
68    /// plain Docker named volume; the key is fed to `cryptsetup
69    /// luksFormat`/`luksOpen` over stdin and never persisted to
70    /// disk. On `delete_container` the LUKS header is erased
71    /// (`cryptsetup luksErase`) so the keyslots are unrecoverable
72    /// even if the operator forensically extracts the underlying
73    /// file.
74    ///
75    /// Provider populates this from
76    /// `EncryptedSpawnPodRequest.volume_encryption.decoded_key()`
77    /// when present; `None` means a plain volume (today's default).
78    /// `data_path: None` makes this field a no-op (stateless
79    /// workloads have nothing to encrypt).
80    pub volume_encryption_key: Option<[u8; 32]>,
81}
82
83#[async_trait]
84pub trait ComputeBackend: Send + Sync {
85    /// Find an available ID in the given range
86    async fn find_available_id(&self, range_start: u32, range_end: u32) -> Result<u32>;
87
88    /// Create a new container
89    async fn create_container(&self, config: &ContainerConfig) -> Result<String>; // Returns container ID/Name
90
91    /// Start a container
92    async fn start_container(&self, id: u32) -> Result<()>;
93
94    /// Stop a container
95    async fn stop_container(&self, id: u32) -> Result<()>;
96
97    /// Delete a container
98    async fn delete_container(&self, id: u32) -> Result<()>;
99
100    /// Get node resource usage
101    async fn get_node_status(&self) -> Result<NodeStatus>;
102
103    /// Get public IP of the container/VM
104    async fn get_container_ip(&self, id: u32) -> Result<Option<String>>;
105}