1use std::sync::Arc;
11
12#[cfg(target_os = "linux")]
21struct PostureKernelVerifier {
22 strict: bool,
23 signing_keys: Vec<String>,
24 allowed_hashes: Vec<String>,
25 daemon: Option<Arc<boatramp_server::DaemonRuntime>>,
26}
27
28#[cfg(target_os = "linux")]
31impl std::fmt::Debug for PostureKernelVerifier {
32 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
33 f.debug_struct("PostureKernelVerifier")
34 .field("strict", &self.strict)
35 .field("signing_keys", &self.signing_keys.len())
36 .field("allowed_hashes", &self.allowed_hashes.len())
37 .field("has_daemon", &self.daemon.is_some())
38 .finish()
39 }
40}
41
42#[cfg(target_os = "linux")]
43impl boatramp_firecracker::KernelVerifier for PostureKernelVerifier {
44 fn verify(&self, bytes: &[u8], expected_hash: &str) -> std::result::Result<(), String> {
46 let sig = self
50 .daemon
51 .as_ref()
52 .and_then(|d| d.effective().default_kernel.clone())
53 .filter(|dk| dk.sha256 == expected_hash)
54 .and_then(|dk| dk.sig);
55 let kref = boatramp_core::daemon_config::KernelRef {
56 source: expected_hash.to_string(),
57 sha256: expected_hash.to_string(),
58 sig,
59 };
60 boatramp_core::kernel_trust::verify_kernel(
61 bytes,
62 &kref,
63 self.strict,
64 &self.signing_keys,
65 &self.allowed_hashes,
66 )
67 .map_err(|e| e.to_string())
68 }
69}
70
71pub async fn build_compute(
78 cfg: Option<&crate::config::ComputeConfig>,
79 storage: std::sync::Arc<dyn boatramp_core::Storage>,
80 data_dir: &std::path::Path,
81 node_id: u64,
82 strict: bool,
83 daemon: Option<Arc<boatramp_server::DaemonRuntime>>,
84) -> (
85 boatramp_core::compute::BackendRegistry,
86 boatramp_core::compute::Node,
87) {
88 use boatramp_core::compute::{BackendKind, BackendRegistry, Node};
89 let mut backends: BackendRegistry = std::collections::BTreeMap::new();
90 let empty_node = |id| Node {
91 id,
92 region: None,
93 labels: std::collections::BTreeMap::new(),
94 free_vcpus: 0,
95 free_mem_mib: 0,
96 backends: Vec::new(),
97 };
98 let Some(cfg) = cfg else {
99 return (backends, empty_node(node_id));
100 };
101
102 match boatramp_docker::DockerBackend::connect() {
104 Ok(docker) => {
105 let docker = docker
108 .with_endpoint(cfg.docker_endpoint)
109 .with_volume_mode(cfg.docker_volume_mode)
110 .with_data_dir(data_dir)
111 .with_writable_root_allowed(!strict);
112 if docker.reachable().await {
113 backends.insert("docker".to_string(), std::sync::Arc::new(docker));
114 } else {
115 tracing::debug!("no reachable docker daemon; skipping docker backend");
116 }
117 }
118 Err(e) => tracing::debug!(%e, "docker backend unavailable"),
119 }
120
121 #[cfg(target_os = "linux")]
123 match std::env::current_exe() {
124 Ok(self_exe) => match boatramp_container::ContainerBackend::new(
125 storage.clone(),
126 data_dir.to_path_buf(),
127 cfg.bridge.clone(),
128 &cfg.subnet,
129 self_exe,
130 ) {
131 Ok(c) => {
132 backends.insert("container".to_string(), std::sync::Arc::new(c));
133 }
134 Err(e) => tracing::warn!(%e, "container backend unavailable"),
135 },
136 Err(e) => tracing::warn!(%e, "current_exe for container backend"),
137 }
138 #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
144 if std::path::Path::new("/dev/kvm").exists() {
145 match (
146 std::env::current_exe(),
147 boatramp_core::ipam::IpPool::new(&cfg.subnet),
148 ) {
149 (Ok(self_exe), Ok(pool)) => {
150 let gateway = pool.gateway().to_string();
151 let verifier: Arc<dyn boatramp_firecracker::KernelVerifier> =
153 Arc::new(PostureKernelVerifier {
154 strict,
155 signing_keys: cfg.kernel_signing_pubkeys.clone(),
156 allowed_hashes: cfg.kernel_allowed_hashes.clone(),
157 daemon: daemon.clone(),
158 });
159 match boatramp_firecracker::EmbeddedVmmBackend::new(
160 storage.clone(),
161 self_exe, data_dir.to_path_buf(),
163 cfg.bridge.clone(),
164 gateway,
165 &cfg.subnet,
166 verifier,
167 ) {
168 Ok(vmm) => {
169 backends.insert("vmm-embedded".to_string(), std::sync::Arc::new(vmm));
170 }
171 Err(e) => tracing::warn!(%e, "embedded VMM backend unavailable"),
172 }
173 }
174 (Err(e), _) => tracing::warn!(%e, "current_exe for VMM backend"),
175 (_, Err(e)) => tracing::warn!(%e, "bad compute subnet for VMM backend"),
176 }
177 } else {
178 tracing::debug!("no /dev/kvm; skipping embedded VMM backend");
179 }
180
181 let _ = (&storage, data_dir); #[cfg(not(all(target_os = "linux", target_arch = "x86_64")))]
185 let _ = (strict, &daemon);
186
187 let free_vcpus = if cfg.vcpus > 0 {
188 cfg.vcpus
189 } else {
190 std::thread::available_parallelism()
191 .map(|n| n.get() as u32)
192 .unwrap_or(1)
193 };
194 let free_mem_mib = if cfg.mem_mib > 0 { cfg.mem_mib } else { 1024 };
195 let advertised: Vec<BackendKind> = backends
196 .iter()
197 .map(|(id, b)| {
198 let caps = b.capabilities();
199 BackendKind {
200 id: id.clone(),
201 isolation: caps.isolation,
202 persistent_volumes: caps.persistent_volumes,
203 scale_to_zero: caps.scale_to_zero,
204 }
205 })
206 .collect();
207 tracing::info!(backends = ?advertised, free_vcpus, free_mem_mib, "compute node inventory");
208 let node = Node {
209 id: node_id,
210 region: cfg.region.clone(),
211 labels: std::collections::BTreeMap::new(),
212 free_vcpus,
213 free_mem_mib,
214 backends: advertised,
215 };
216 (backends, node)
217}