1use std::collections::HashMap;
2use std::path::PathBuf;
3
4use serde::{Deserialize, Serialize};
5
6use crate::config::defaults::{
7 default_network_mode, default_package_manager, default_pull_retry, default_pull_retry_delay,
8 default_shell, default_true, is_default_gpu, is_default_host_exec, is_default_mounts,
9 is_default_packages, is_default_pkg_mgr, is_default_pull_retry, is_default_pull_retry_delay,
10 is_default_run, is_default_shell, is_empty_hashmap, is_false, is_true,
11};
12use crate::config::enums::{CapPreset, GpuMode, ImageSource, OnStop, PackageManager, XdgDirValue};
13use crate::config::expand_tilde;
14
15#[derive(Debug, Deserialize, Serialize, Clone)]
16pub struct ImageConfig {
17 pub base: String,
18 pub name: String,
19 #[serde(rename = "image", default)]
20 pub image_ref: Option<String>,
21 #[serde(
22 default = "default_pull_retry",
23 skip_serializing_if = "is_default_pull_retry"
24 )]
25 pub pull_retry: u32,
26 #[serde(
27 default = "default_pull_retry_delay",
28 skip_serializing_if = "is_default_pull_retry_delay"
29 )]
30 pub pull_retry_delay: String,
31 #[serde(default, skip_serializing_if = "is_default_packages")]
32 pub packages: PackageConfig,
33 #[serde(default, skip_serializing_if = "is_default_run")]
34 pub run: RunConfig,
35}
36
37impl ImageConfig {
38 pub fn source(&self) -> ImageSource {
39 match &self.image_ref {
40 Some(ref_str) => ImageSource::Prebuilt {
41 ref_str: ref_str.clone(),
42 },
43 None => ImageSource::Build {
44 base: self.base.clone(),
45 },
46 }
47 }
48}
49
50#[derive(Debug, Deserialize, Serialize, Clone)]
51pub struct PackageConfig {
52 #[serde(default, skip_serializing_if = "Vec::is_empty")]
53 pub install: Vec<String>,
54 #[serde(default, skip_serializing_if = "Vec::is_empty")]
55 pub remove: Vec<String>,
56 #[serde(
57 default = "default_package_manager",
58 skip_serializing_if = "is_default_pkg_mgr"
59 )]
60 pub manager: PackageManager,
61}
62
63impl Default for PackageConfig {
64 fn default() -> Self {
65 Self {
66 install: Vec::new(),
67 remove: Vec::new(),
68 manager: default_package_manager(),
69 }
70 }
71}
72
73#[derive(Debug, Deserialize, Serialize, Clone, Default)]
74pub struct RunConfig {
75 #[serde(default, skip_serializing_if = "Vec::is_empty")]
76 pub commands: Vec<String>,
77}
78
79#[derive(Debug, Deserialize, Serialize, Clone)]
80pub struct ContainerConfig {
81 pub name: String,
82 #[serde(deserialize_with = "deserialize_home")]
83 pub home: PathBuf,
84 #[serde(default = "default_shell", skip_serializing_if = "is_default_shell")]
85 pub shell: String,
86 #[serde(default, skip_serializing_if = "Option::is_none")]
87 pub memory: Option<String>,
88 #[serde(default, skip_serializing_if = "Option::is_none")]
89 pub cpus: Option<String>,
90 #[serde(default, skip_serializing_if = "Option::is_none")]
91 pub reload_cmd: Option<String>,
92 #[serde(default, skip_serializing_if = "is_default_mounts")]
93 pub mounts: MountConfig,
94 #[serde(default, skip_serializing_if = "is_empty_hashmap")]
95 pub env: HashMap<String, String>,
96}
97
98#[derive(Debug, Deserialize, Serialize, Clone, Default)]
99pub struct MountConfig {
100 #[serde(default, skip_serializing_if = "Vec::is_empty")]
101 pub extra: Vec<String>,
102}
103
104fn deserialize_home<'de, D>(deserializer: D) -> std::result::Result<PathBuf, D::Error>
105where
106 D: serde::Deserializer<'de>,
107{
108 let path = String::deserialize(deserializer)?;
109 Ok(expand_tilde(&path))
110}
111
112#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
113#[serde(untagged)]
114pub enum HostExecEntry {
115 Simple(String),
117 Detailed {
119 path: String,
120 #[serde(default = "default_true")]
121 filter: bool,
122 #[serde(default = "default_true")]
123 shim: bool,
124 },
125}
126
127impl HostExecEntry {
128 pub fn path(&self) -> &str {
129 match self {
130 Self::Simple(p) => p.as_str(),
131 Self::Detailed { path, .. } => path.as_str(),
132 }
133 }
134
135 pub fn filter_enabled(&self) -> bool {
136 match self {
137 Self::Simple(_) => true,
138 Self::Detailed { filter, .. } => *filter,
139 }
140 }
141
142 pub fn shim_enabled(&self) -> bool {
143 match self {
144 Self::Simple(_) => true,
145 Self::Detailed { shim, .. } => *shim,
146 }
147 }
148}
149
150#[derive(Debug, Clone, Default, Serialize, Deserialize)]
151pub struct HostExecConfig {
152 #[serde(default)]
153 pub enabled: bool,
154 #[serde(default, skip_serializing_if = "Option::is_none")]
155 pub allowlist: Option<std::collections::HashMap<String, HostExecEntry>>,
156}
157
158impl HostExecConfig {
159 pub fn resolve(&self, cmd: &str) -> Option<&HostExecEntry> {
160 if !self.enabled {
161 return None;
162 }
163 match &self.allowlist {
164 Some(map) => map.get(cmd),
165 None => None,
166 }
167 }
168
169 pub fn guest_shims(&self) -> Vec<String> {
171 if !self.enabled {
172 return Vec::new();
173 }
174 self.allowlist
175 .as_ref()
176 .map(|map| {
177 map.iter()
178 .filter(|(_, entry)| entry.shim_enabled())
179 .map(|(alias, _)| alias.clone())
180 .collect()
181 })
182 .unwrap_or_default()
183 }
184}
185
186#[derive(Debug, Deserialize, Serialize, Clone)]
187pub struct IntegrationConfig {
188 #[serde(default = "default_true", skip_serializing_if = "is_true")]
189 pub wayland: bool,
190 #[serde(default = "default_true", skip_serializing_if = "is_true")]
191 pub audio: bool,
192 #[serde(default, skip_serializing_if = "is_default_gpu")]
193 pub gpu: GpuMode,
194 #[serde(default = "default_true", skip_serializing_if = "is_true")]
195 pub dbus: bool,
196 #[serde(default = "default_true", skip_serializing_if = "is_true")]
197 pub notify: bool,
198 #[serde(default = "default_true", skip_serializing_if = "is_true")]
199 pub xdg_open: bool,
200 #[serde(default = "default_true", skip_serializing_if = "is_true")]
201 pub clipboard: bool,
202 #[serde(default, skip_serializing_if = "is_default_host_exec")]
203 pub host_exec: HostExecConfig,
204 #[serde(default, skip_serializing_if = "is_false")]
205 pub ssh_agent: bool,
206 #[serde(default, skip_serializing_if = "is_false")]
207 pub gpg_agent: bool,
208 #[serde(default = "default_true", skip_serializing_if = "is_true")]
209 pub sync_fonts: bool,
210 #[serde(default = "default_true", skip_serializing_if = "is_true")]
211 pub sync_icons: bool,
212 #[serde(default = "default_true", skip_serializing_if = "is_true")]
213 pub sync_themes: bool,
214 #[serde(default)]
215 pub hardware: HardwareConfig,
216 #[serde(default)]
217 pub xdg_dirs: XdgDirConfig,
218 #[serde(default)]
219 pub export: ExportConfig,
220}
221
222impl Default for IntegrationConfig {
223 fn default() -> Self {
224 IntegrationConfig {
225 wayland: true,
226 audio: true,
227 gpu: GpuMode::Auto,
228 dbus: true,
229 notify: true,
230 xdg_open: true,
231 clipboard: true,
232 host_exec: HostExecConfig::default(),
233 ssh_agent: false,
234 gpg_agent: false,
235 sync_fonts: true,
236 sync_icons: true,
237 sync_themes: true,
238 hardware: HardwareConfig::default(),
239 xdg_dirs: XdgDirConfig::default(),
240 export: ExportConfig::default(),
241 }
242 }
243}
244
245#[derive(Debug, Deserialize, Serialize, Clone, Default)]
246pub struct XdgDirConfig {
247 #[serde(default)]
248 pub documents: XdgDirValue,
249 #[serde(default)]
250 pub downloads: XdgDirValue,
251 #[serde(default)]
252 pub pictures: XdgDirValue,
253 #[serde(default)]
254 pub music: XdgDirValue,
255 #[serde(default)]
256 pub videos: XdgDirValue,
257 #[serde(default)]
258 pub desktop: XdgDirValue,
259 #[serde(default)]
260 pub projects: XdgDirValue,
261}
262
263#[derive(Debug, Deserialize, Serialize, Clone, Default)]
264pub struct ExportConfig {
265 #[serde(default)]
266 pub apps: Vec<String>,
267 #[serde(default)]
268 pub bins: Vec<String>,
269}
270
271#[derive(Debug, Deserialize, Serialize, Clone)]
272pub struct LifecycleConfig {
273 #[serde(default)]
274 pub quadlet: bool,
275 #[serde(default)]
276 pub autostart: bool,
277 #[serde(default)]
278 pub on_stop: OnStop,
279 #[serde(default)]
280 pub auto_update: bool,
281 #[serde(default = "crate::config::defaults::default_idle_timeout")]
282 pub idle_timeout: String,
283}
284
285impl Default for LifecycleConfig {
286 fn default() -> Self {
287 LifecycleConfig {
288 quadlet: false,
289 autostart: false,
290 on_stop: OnStop::Keep,
291 auto_update: false,
292 idle_timeout: crate::config::defaults::default_idle_timeout(),
293 }
294 }
295}
296
297#[derive(Debug, Deserialize, Serialize, Clone, Default)]
298pub struct SystemdConfig {
299 #[serde(default)]
300 pub requires: Vec<String>,
301 #[serde(default)]
302 pub after: Vec<String>,
303}
304
305#[derive(Debug, Deserialize, Serialize, Clone)]
306pub struct WaylandConfig {
307 #[serde(default = "default_true", skip_serializing_if = "is_true")]
308 pub firewall: bool,
309 #[serde(default)]
310 pub blocked_interfaces: Vec<String>,
311}
312
313impl Default for WaylandConfig {
314 fn default() -> Self {
315 Self {
316 firewall: true,
317 blocked_interfaces: vec![
318 "zwlr_screencopy_manager_v1".into(),
319 "ext_image_copy_capture_v1".into(),
320 "ext_foreign_toplevel_list_v1".into(),
321 "zwlr_virtual_pointer_manager_v1".into(),
322 "zwlr_virtual_pointer_unstable_v1".into(),
323 "zwp_virtual_keyboard_manager_v1".into(),
324 "zwp_input_method_v1".into(),
325 "zwp_input_method_v2".into(),
326 "ext_input_method_v1".into(),
327 "org_kde_kwin_fake_input".into(),
328 ],
329 }
330 }
331}
332
333#[derive(Debug, Deserialize, Serialize, Clone, Default)]
334pub struct DbusConfig {
335 #[serde(default)]
336 pub preset: String,
337 #[serde(default)]
338 pub talk: Vec<String>,
339 #[serde(default)]
340 pub own: Vec<String>,
341}
342
343impl DbusConfig {
344 pub fn effective_talk(&self) -> Vec<String> {
345 let mut result = self.talk.clone();
346 if !self.preset.is_empty() && self.preset != "none" {
347 for svc in dbus_preset_talk(&self.preset) {
348 if !result.contains(&svc.to_string()) {
349 result.push(svc.to_string());
350 }
351 }
352 }
353 result
354 }
355}
356
357pub fn dbus_preset_talk(preset: &str) -> &[&str] {
358 match preset {
359 "flatpak" => &["org.freedesktop.Flatpak", "org.freedesktop.Flatpak.*"],
360 "gnome" => &[
361 "org.gnome.Shell",
362 "org.gnome.Shell.*",
363 "org.gnome.ScreenSaver",
364 "org.gnome.Mutter.*",
365 "org.gnome.keyring.*",
366 ],
367 "kde" => &["org.kde.*"],
368 _ => &[],
369 }
370}
371
372#[derive(Debug, Deserialize, Serialize, Clone)]
373pub struct NetworkConfig {
374 #[serde(default = "default_network_mode")]
375 pub mode: String,
376 #[serde(default, skip_serializing_if = "Vec::is_empty")]
377 pub ports: Vec<String>,
378}
379
380impl Default for NetworkConfig {
381 fn default() -> Self {
382 Self {
383 mode: default_network_mode(),
384 ports: Vec::new(),
385 }
386 }
387}
388
389#[derive(Debug, Deserialize, Serialize, Clone, Default, PartialEq, Eq)]
390pub struct HardwareConfig {
391 #[serde(default, skip_serializing_if = "is_false")]
392 pub joystick: bool,
393 #[serde(default, skip_serializing_if = "is_false")]
394 pub webcam: bool,
395 #[serde(default, skip_serializing_if = "is_false")]
396 pub yubikey: bool,
397 #[serde(default, skip_serializing_if = "is_false")]
398 pub serial: bool,
399 #[serde(default, skip_serializing_if = "is_false")]
400 pub kvm: bool,
401}
402
403#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
404#[serde(untagged)]
405pub enum SecretEntry {
406 Simple(String),
407 Detailed {
408 name: String,
409 #[serde(default = "default_secret_type")]
410 #[serde(rename = "type")]
411 secret_type: SecretType,
412 #[serde(default, skip_serializing_if = "Option::is_none")]
413 target: Option<String>,
414 #[serde(default, skip_serializing_if = "Option::is_none")]
415 mode: Option<String>,
416 #[serde(default = "default_secret_source")]
417 source: SecretSource,
418 },
419}
420
421fn default_secret_type() -> SecretType {
422 SecretType::Env
423}
424fn default_secret_source() -> SecretSource {
425 SecretSource::Podman
426}
427
428#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
429#[serde(rename_all = "lowercase")]
430pub enum SecretType {
431 #[default]
432 Env,
433 Mount,
434}
435
436#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
437#[serde(rename_all = "lowercase")]
438pub enum SecretSource {
439 #[default]
440 Podman,
441 Systemd,
442}
443
444#[derive(Debug, Deserialize, Serialize, Clone)]
445pub struct SecurityConfig {
446 #[serde(default)]
447 pub apparmor: Option<String>,
448 #[serde(default)]
449 pub seccomp: Option<String>,
450 #[serde(default = "default_true", skip_serializing_if = "is_true")]
451 pub security_label_disable: bool,
452 #[serde(default = "default_true", skip_serializing_if = "is_true")]
453 pub no_new_privileges: bool,
454 #[serde(default, skip_serializing_if = "is_false")]
455 pub read_only_rootfs: bool,
456 #[serde(default, skip_serializing_if = "Option::is_none")]
457 pub userns: Option<String>,
458 #[serde(default, skip_serializing_if = "is_default_cap_preset")]
459 pub cap_preset: CapPreset,
460 #[serde(default, skip_serializing_if = "Vec::is_empty")]
461 pub cap_add: Vec<String>,
462 #[serde(default, skip_serializing_if = "Vec::is_empty")]
463 pub secrets: Vec<SecretEntry>,
464}
465
466fn is_default_cap_preset(v: &CapPreset) -> bool {
467 *v == CapPreset::Default
468}
469
470impl Default for SecurityConfig {
471 fn default() -> Self {
472 SecurityConfig {
473 apparmor: None,
474 seccomp: None,
475 security_label_disable: true,
476 no_new_privileges: true,
477 read_only_rootfs: false,
478 userns: None,
479 cap_preset: CapPreset::Default,
480 cap_add: Vec::new(),
481 secrets: Vec::new(),
482 }
483 }
484}