1use serde::{Deserialize, Serialize};
2use std::collections::BTreeMap;
3
4#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
5#[serde(rename_all = "camelCase", deny_unknown_fields)]
6pub struct DeploymentConfig {
7 pub target_ip: String,
8 #[serde(default)]
9 pub ssh_proxy_jump: String,
10 #[serde(default)]
11 pub ssh_identity_public_key: String,
12 pub builder: String,
13 #[serde(default = "default_build_on")]
14 pub build_on: String,
15 #[serde(default)]
16 pub bootstrap_user: String,
17 #[serde(default)]
18 pub require_secrets: bool,
19 pub low_mem: String,
20 #[serde(default = "default_substitute_on_destination")]
21 pub substitute_on_destination: bool,
22 #[serde(default, skip_serializing_if = "Option::is_none")]
23 pub binary_cache: Option<BinaryCacheConfig>,
24 #[serde(default = "default_local_eval")]
25 pub local_eval: bool,
26 pub vmid: String,
27 pub disk_size: String,
28 #[serde(default)]
29 pub nameservers: Vec<String>,
30 pub proxmox: ProxmoxConfig,
31 pub digitalocean: DigitalOceanConfig,
32 pub vmware: VmwareConfig,
33 #[serde(default)]
34 pub wsl: WslConfig,
35}
36
37fn default_build_on() -> String {
38 "auto".to_string()
39}
40
41fn default_substitute_on_destination() -> bool {
42 true
43}
44
45#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
46#[serde(rename_all = "camelCase", deny_unknown_fields)]
47pub struct BinaryCacheConfig {
48 pub url: String,
49 pub public_key: String,
50 #[serde(default, skip_serializing_if = "String::is_empty")]
51 pub ca_certificate: String,
52}
53
54fn default_local_eval() -> bool {
55 true
56}
57
58#[cfg(test)]
59mod tests {
60 use super::*;
61
62 #[test]
63 fn destination_substitution_preserves_the_legacy_enabled_default() {
64 assert!(default_substitute_on_destination());
65 }
66
67 #[test]
70 fn absent_cache_policy_parses_as_no_cache() {
71 let parsed: NxdMetadata =
72 serde_json::from_value(serde_json::json!({})).expect("empty cache policy parses");
73 assert!(parsed.binary_cache.is_none());
74 }
75}
76
77#[derive(Deserialize, Serialize, Debug, Clone, Default, PartialEq, Eq)]
78#[serde(rename_all = "camelCase", deny_unknown_fields)]
79pub struct ProxmoxBootstrapConfig {
80 #[serde(default = "default_bootstrap_interface")]
81 pub interface: String,
82 #[serde(default)]
83 pub static_ip: String,
84 #[serde(default)]
85 pub subnet: String,
86 #[serde(default)]
87 pub gateway: String,
88 #[serde(default)]
89 pub vlan: Option<u16>,
90}
91
92fn default_bootstrap_interface() -> String {
93 "net0".to_string()
94}
95
96#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
97#[serde(rename_all = "camelCase", deny_unknown_fields)]
98pub struct ProxmoxConfig {
99 #[serde(default)]
100 pub provider: String,
101 #[serde(default)]
102 pub node: String,
103 pub host: String,
104 pub bios: String,
105 pub disk_bus: String,
106 pub scsi_hw: String,
107 pub disk_storage: String,
108 #[serde(default)]
109 pub discovery_subnets: Vec<String>,
110 #[serde(default)]
111 pub net0: String,
112 #[serde(default)]
113 pub net1: String,
114 #[serde(default)]
115 pub bootstrap: ProxmoxBootstrapConfig,
116 #[serde(default)]
117 pub extra_networks: Vec<String>,
118 #[serde(default)]
119 pub pxe: bool,
120 pub cores: String,
121 pub memory: String,
122 pub iso: ProxmoxIsoConfig,
123 pub cloud_init: CloudInitConfig,
124}
125
126#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
127#[serde(rename_all = "camelCase", deny_unknown_fields)]
128pub struct ProxmoxIsoConfig {
129 #[serde(rename = "type")]
131 pub flavor: String,
132 pub storage: String,
133 pub custom_path: String,
135}
136
137#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
138#[serde(rename_all = "camelCase", deny_unknown_fields)]
139pub struct CloudInitConfig {
140 pub image: String,
141 pub user: String,
142 pub ipconfig0: String,
143 pub ipconfig1: String,
144}
145
146#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
147#[serde(rename_all = "camelCase", deny_unknown_fields)]
148pub struct DigitalOceanConfig {
149 pub region: String,
150 pub size: String,
151 pub image: String,
152}
153
154#[derive(Deserialize, Serialize, Debug, Clone, Default, PartialEq, Eq)]
155#[serde(rename_all = "camelCase", deny_unknown_fields)]
156pub struct VmwareConfig {
157 pub vmx_path: String,
158 #[serde(default)]
159 pub iso_directory: String,
160 #[serde(default)]
161 pub cores: u16,
162 #[serde(default, rename = "memoryMiB")]
163 pub memory_mib: u32,
164}
165
166#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
167#[serde(rename_all = "camelCase", deny_unknown_fields)]
168pub struct WslConfig {
169 #[serde(default)]
170 pub enable: bool,
171 #[serde(default)]
172 pub windows_host: String,
173 #[serde(default)]
174 pub windows_user: String,
175 #[serde(default = "default_wsl_distribution")]
176 pub distribution: String,
177 #[serde(default)]
178 pub install_root: String,
179 #[serde(default = "default_wsl_bootstrap_user")]
180 pub bootstrap_user: String,
181 #[serde(default)]
182 pub guest_host: String,
183 #[serde(default = "default_wsl_transport")]
184 pub transport: String,
185}
186
187impl Default for WslConfig {
188 fn default() -> Self {
189 Self {
190 enable: false,
191 windows_host: String::new(),
192 windows_user: String::new(),
193 distribution: default_wsl_distribution(),
194 install_root: String::new(),
195 bootstrap_user: default_wsl_bootstrap_user(),
196 guest_host: String::new(),
197 transport: default_wsl_transport(),
198 }
199 }
200}
201
202fn default_wsl_distribution() -> String {
203 "NixOS".to_string()
204}
205
206fn default_wsl_bootstrap_user() -> String {
207 "nixos".to_string()
208}
209
210fn default_wsl_transport() -> String {
211 "auto".to_string()
212}
213
214#[derive(Deserialize, Serialize, Debug, Clone, Default, PartialEq, Eq)]
215#[serde(rename_all = "camelCase", deny_unknown_fields)]
216pub struct CrossConfig {
217 #[serde(default)]
218 pub local_system: Option<serde_json::Value>,
219 #[serde(default)]
220 pub cross_system: Option<serde_json::Value>,
221}
222
223fn default_build_system() -> bool {
224 true
225}
226
227#[derive(Deserialize, Serialize, Debug, Clone, Default, PartialEq, Eq)]
230#[serde(rename_all = "camelCase", deny_unknown_fields)]
231pub struct NxdMetadata {
232 #[serde(default, skip_serializing_if = "Option::is_none")]
233 pub binary_cache: Option<BinaryCacheConfig>,
234 #[serde(default, skip_serializing_if = "String::is_empty")]
238 pub secrets_site: String,
239}
240
241#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
247#[serde(rename_all = "camelCase", deny_unknown_fields)]
248pub struct RuntimeSecretInput {
249 pub path: String,
251 pub activation_unit: String,
253}
254
255#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq, Default)]
256#[serde(rename_all = "camelCase", deny_unknown_fields)]
257pub struct HostPlaneAttachment {
258 #[serde(default)]
259 pub enable: bool,
260 #[serde(default)]
262 pub provider: String,
263 #[serde(default, skip_serializing_if = "String::is_empty")]
265 pub secret_binding: String,
266 #[serde(default, skip_serializing_if = "Option::is_none")]
269 pub runtime_secret: Option<RuntimeSecretInput>,
270 #[serde(default, skip_serializing_if = "is_null_or_empty_object")]
272 pub policy: serde_json::Value,
273}
274
275fn is_null_or_empty_object(value: &serde_json::Value) -> bool {
276 value.is_null() || value.as_object().is_some_and(|object| object.is_empty())
277}
278
279#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
280#[serde(rename_all = "camelCase", deny_unknown_fields)]
281pub struct FlakeMetadata {
282 pub deployment: DeploymentConfig,
283 #[serde(default, skip_serializing_if = "Option::is_none")]
284 pub evaluation_source: Option<String>,
285 #[serde(default, skip_serializing_if = "Vec::is_empty")]
286 pub evaluation_source_inputs: Vec<String>,
287 #[serde(default, skip_serializing_if = "Option::is_none")]
288 pub evaluation_secret_source: Option<String>,
289 #[serde(default)]
290 pub nxd: NxdMetadata,
291 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
294 pub plane_attachments: BTreeMap<String, HostPlaneAttachment>,
295 pub system: String,
296 #[serde(default, skip_serializing_if = "Option::is_none")]
297 pub system_output: Option<String>,
298 #[serde(default, skip_serializing_if = "Option::is_none")]
299 pub system_derivation: Option<String>,
300 #[serde(default, skip_serializing_if = "Option::is_none")]
301 pub disko_output: Option<String>,
302 pub user: String,
303 pub has_disko: bool,
304 #[serde(default = "default_build_system")]
305 pub build_system: bool,
306 #[serde(default)]
307 pub wsl: bool,
308 #[serde(default)]
309 pub class: String,
310 #[serde(default)]
311 pub server: bool,
312 #[serde(default)]
313 pub home: bool,
314 #[serde(default)]
315 pub role: Option<String>,
316 #[serde(default)]
317 pub tags: Vec<String>,
318 #[serde(default)]
319 pub cross: Option<CrossConfig>,
320 #[serde(default)]
321 pub features: Vec<serde_json::Value>,
322 #[serde(default)]
323 pub os_features: Vec<serde_json::Value>,
324 #[serde(default)]
325 pub hm_features: Vec<serde_json::Value>,
326}