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}