1use std::path::{Path, PathBuf};
2use std::sync::Arc;
3
4use anyhow::Result;
5
6use crate::cli::CreateArgs;
7use crate::instance::{InstanceManager, VmInfo};
8use crate::ssh::ExecOutput;
9use crate::state::{
10 AccelMode, GuestArch, HardpassState, InstanceStatus, PortForward, validate_name,
11};
12
13pub struct Hardpass {
14 manager: Arc<InstanceManager>,
15}
16
17impl Hardpass {
18 pub async fn load() -> Result<Self> {
19 let state = HardpassState::load().await?;
20 Self::from_state(state).await
21 }
22
23 pub async fn with_root(root: impl AsRef<Path>) -> Result<Self> {
24 let state = HardpassState::load_with_root(root.as_ref().to_path_buf()).await?;
25 Self::from_state(state).await
26 }
27
28 pub async fn doctor(&self) -> Result<()> {
29 self.manager.doctor().await
30 }
31
32 pub async fn create(&self, spec: VmSpec) -> Result<Vm> {
33 let name = spec.name.clone();
34 self.manager.create_silent(spec.into_create_args()).await?;
35 self.vm(name)
36 }
37
38 pub fn vm(&self, name: impl Into<String>) -> Result<Vm> {
39 let name = name.into();
40 validate_name(&name)?;
41 Ok(Vm {
42 manager: Arc::clone(&self.manager),
43 name,
44 })
45 }
46
47 async fn from_state(state: HardpassState) -> Result<Self> {
48 let instance = Self {
49 manager: Arc::new(InstanceManager::new(state)),
50 };
51 instance.manager.auto_configure_ssh_if_enabled().await;
52 Ok(instance)
53 }
54}
55
56#[derive(Debug, Clone, Default)]
57pub struct VmSpec {
58 pub name: String,
59 pub release: Option<String>,
60 pub arch: Option<GuestArch>,
61 pub accel: Option<AccelMode>,
62 pub cpus: Option<u8>,
63 pub memory_mib: Option<u32>,
64 pub disk_gib: Option<u32>,
65 pub ssh_key: Option<PathBuf>,
66 pub forwards: Vec<PortForward>,
67 pub timeout_secs: Option<u64>,
68 pub cloud_init_user_data: Option<PathBuf>,
69 pub cloud_init_network_config: Option<PathBuf>,
70}
71
72impl VmSpec {
73 pub fn new(name: impl Into<String>) -> Self {
74 Self {
75 name: name.into(),
76 ..Self::default()
77 }
78 }
79
80 pub fn release(mut self, release: impl Into<String>) -> Self {
81 self.release = Some(release.into());
82 self
83 }
84
85 pub fn arch(mut self, arch: GuestArch) -> Self {
86 self.arch = Some(arch);
87 self
88 }
89
90 pub fn accel(mut self, accel: AccelMode) -> Self {
91 self.accel = Some(accel);
92 self
93 }
94
95 pub fn cpus(mut self, cpus: u8) -> Self {
96 self.cpus = Some(cpus);
97 self
98 }
99
100 pub fn memory_mib(mut self, memory_mib: u32) -> Self {
101 self.memory_mib = Some(memory_mib);
102 self
103 }
104
105 pub fn disk_gib(mut self, disk_gib: u32) -> Self {
106 self.disk_gib = Some(disk_gib);
107 self
108 }
109
110 pub fn ssh_key(mut self, ssh_key: impl AsRef<Path>) -> Self {
111 self.ssh_key = Some(ssh_key.as_ref().to_path_buf());
112 self
113 }
114
115 pub fn forward(mut self, host: u16, guest: u16) -> Self {
116 self.forwards.push(PortForward { host, guest });
117 self
118 }
119
120 pub fn timeout_secs(mut self, timeout_secs: u64) -> Self {
121 self.timeout_secs = Some(timeout_secs);
122 self
123 }
124
125 pub fn cloud_init_user_data(mut self, path: impl AsRef<Path>) -> Self {
126 self.cloud_init_user_data = Some(path.as_ref().to_path_buf());
127 self
128 }
129
130 pub fn cloud_init_network_config(mut self, path: impl AsRef<Path>) -> Self {
131 self.cloud_init_network_config = Some(path.as_ref().to_path_buf());
132 self
133 }
134
135 fn into_create_args(self) -> CreateArgs {
136 CreateArgs {
137 name: self.name,
138 release: self.release,
139 arch: self.arch,
140 accel: self.accel,
141 cpus: self.cpus,
142 memory_mib: self.memory_mib,
143 disk_gib: self.disk_gib,
144 ssh_key: self.ssh_key.map(|path| path.display().to_string()),
145 forwards: self
146 .forwards
147 .into_iter()
148 .map(|forward| (forward.host, forward.guest))
149 .collect(),
150 timeout_secs: self.timeout_secs,
151 cloud_init_user_data: self
152 .cloud_init_user_data
153 .map(|path| path.display().to_string()),
154 cloud_init_network_config: self
155 .cloud_init_network_config
156 .map(|path| path.display().to_string()),
157 }
158 }
159}
160
161pub struct Vm {
162 manager: Arc<InstanceManager>,
163 name: String,
164}
165
166impl Vm {
167 pub fn name(&self) -> &str {
168 &self.name
169 }
170
171 pub async fn start(&self) -> Result<()> {
172 self.manager.start_silent(&self.name).await?;
173 Ok(())
174 }
175
176 pub async fn info(&self) -> Result<VmInfo> {
177 self.manager.vm_info(&self.name).await
178 }
179
180 pub async fn status(&self) -> Result<InstanceStatus> {
181 self.manager.status(&self.name).await
182 }
183
184 pub async fn wait_for_ssh(&self) -> Result<VmInfo> {
185 self.manager.wait_for_ssh_ready(&self.name).await
186 }
187
188 pub async fn exec<I, S>(&self, command: I) -> Result<ExecOutput>
189 where
190 I: IntoIterator<Item = S>,
191 S: Into<String>,
192 {
193 let command = command.into_iter().map(Into::into).collect::<Vec<_>>();
194 self.manager.exec_capture(&self.name, &command).await
195 }
196
197 pub async fn exec_checked<I, S>(&self, command: I) -> Result<ExecOutput>
198 where
199 I: IntoIterator<Item = S>,
200 S: Into<String>,
201 {
202 let command = command.into_iter().map(Into::into).collect::<Vec<_>>();
203 self.manager.exec_checked(&self.name, &command).await
204 }
205
206 pub async fn stop(&self) -> Result<()> {
207 self.manager.stop_silent(&self.name).await
208 }
209
210 pub async fn delete(&self) -> Result<()> {
211 self.manager.delete_silent(&self.name).await
212 }
213}