Skip to main content

podbox/config/
types.rs

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, Default, Serialize, Deserialize)]
113pub struct HostExecConfig {
114    #[serde(default)]
115    pub enabled: bool,
116    #[serde(default, skip_serializing_if = "Option::is_none")]
117    pub allowlist: Option<std::collections::HashMap<String, String>>,
118}
119
120impl HostExecConfig {
121    pub fn resolve<'a>(&'a self, cmd: &'a str) -> Option<&'a str> {
122        if !self.enabled {
123            return None;
124        }
125        match &self.allowlist {
126            Some(map) => map.get(cmd).map(|s| s.as_str()),
127            None => Some(cmd),
128        }
129    }
130}
131
132#[derive(Debug, Deserialize, Serialize, Clone)]
133pub struct IntegrationConfig {
134    #[serde(default = "default_true", skip_serializing_if = "is_true")]
135    pub wayland: bool,
136    #[serde(default = "default_true", skip_serializing_if = "is_true")]
137    pub audio: bool,
138    #[serde(default, skip_serializing_if = "is_default_gpu")]
139    pub gpu: GpuMode,
140    #[serde(default = "default_true", skip_serializing_if = "is_true")]
141    pub dbus: bool,
142    #[serde(default = "default_true", skip_serializing_if = "is_true")]
143    pub notify: bool,
144    #[serde(default = "default_true", skip_serializing_if = "is_true")]
145    pub xdg_open: bool,
146    #[serde(default = "default_true", skip_serializing_if = "is_true")]
147    pub clipboard: bool,
148    #[serde(default, skip_serializing_if = "is_default_host_exec")]
149    pub host_exec: HostExecConfig,
150    #[serde(default, skip_serializing_if = "is_false")]
151    pub ssh_agent: bool,
152    #[serde(default, skip_serializing_if = "is_false")]
153    pub gpg_agent: bool,
154    #[serde(default = "default_true", skip_serializing_if = "is_true")]
155    pub sync_fonts: bool,
156    #[serde(default = "default_true", skip_serializing_if = "is_true")]
157    pub sync_icons: bool,
158    #[serde(default = "default_true", skip_serializing_if = "is_true")]
159    pub sync_themes: bool,
160    #[serde(default)]
161    pub xdg_dirs: XdgDirConfig,
162    #[serde(default)]
163    pub export: ExportConfig,
164}
165
166impl Default for IntegrationConfig {
167    fn default() -> Self {
168        IntegrationConfig {
169            wayland: true,
170            audio: true,
171            gpu: GpuMode::Auto,
172            dbus: true,
173            notify: true,
174            xdg_open: true,
175            clipboard: true,
176            host_exec: HostExecConfig::default(),
177            ssh_agent: false,
178            gpg_agent: false,
179            sync_fonts: true,
180            sync_icons: true,
181            sync_themes: true,
182            xdg_dirs: XdgDirConfig::default(),
183            export: ExportConfig::default(),
184        }
185    }
186}
187
188#[derive(Debug, Deserialize, Serialize, Clone, Default)]
189pub struct XdgDirConfig {
190    #[serde(default)]
191    pub documents: XdgDirValue,
192    #[serde(default)]
193    pub downloads: XdgDirValue,
194    #[serde(default)]
195    pub pictures: XdgDirValue,
196    #[serde(default)]
197    pub music: XdgDirValue,
198    #[serde(default)]
199    pub videos: XdgDirValue,
200    #[serde(default)]
201    pub desktop: XdgDirValue,
202    #[serde(default)]
203    pub projects: XdgDirValue,
204}
205
206#[derive(Debug, Deserialize, Serialize, Clone, Default)]
207pub struct ExportConfig {
208    #[serde(default)]
209    pub apps: Vec<String>,
210    #[serde(default)]
211    pub bins: Vec<String>,
212}
213
214#[derive(Debug, Deserialize, Serialize, Clone)]
215pub struct LifecycleConfig {
216    #[serde(default)]
217    pub quadlet: bool,
218    #[serde(default)]
219    pub autostart: bool,
220    #[serde(default)]
221    pub on_stop: OnStop,
222    #[serde(default)]
223    pub auto_update: bool,
224    #[serde(default = "crate::config::defaults::default_idle_timeout")]
225    pub idle_timeout: String,
226}
227
228impl Default for LifecycleConfig {
229    fn default() -> Self {
230        LifecycleConfig {
231            quadlet: false,
232            autostart: false,
233            on_stop: OnStop::Keep,
234            auto_update: false,
235            idle_timeout: crate::config::defaults::default_idle_timeout(),
236        }
237    }
238}
239
240#[derive(Debug, Deserialize, Serialize, Clone, Default)]
241pub struct SystemdConfig {
242    #[serde(default)]
243    pub requires: Vec<String>,
244    #[serde(default)]
245    pub after: Vec<String>,
246}
247
248#[derive(Debug, Deserialize, Serialize, Clone)]
249pub struct WaylandConfig {
250    #[serde(default = "default_true", skip_serializing_if = "is_true")]
251    pub firewall: bool,
252    #[serde(default)]
253    pub blocked_interfaces: Vec<String>,
254}
255
256impl Default for WaylandConfig {
257    fn default() -> Self {
258        Self {
259            firewall: true,
260            blocked_interfaces: vec![
261                "zwlr_screencopy_manager_v1".into(),
262                "ext_image_copy_capture_v1".into(),
263                "ext_foreign_toplevel_list_v1".into(),
264                "zwlr_virtual_pointer_manager_v1".into(),
265                "zwlr_virtual_pointer_unstable_v1".into(),
266                "zwp_virtual_keyboard_manager_v1".into(),
267                "zwp_input_method_v1".into(),
268                "zwp_input_method_v2".into(),
269                "ext_input_method_v1".into(),
270                "org_kde_kwin_fake_input".into(),
271            ],
272        }
273    }
274}
275
276#[derive(Debug, Deserialize, Serialize, Clone, Default)]
277pub struct DbusConfig {
278    #[serde(default)]
279    pub preset: String,
280    #[serde(default)]
281    pub talk: Vec<String>,
282    #[serde(default)]
283    pub own: Vec<String>,
284}
285
286impl DbusConfig {
287    pub fn effective_talk(&self) -> Vec<String> {
288        let mut result = self.talk.clone();
289        if !self.preset.is_empty() && self.preset != "none" {
290            for svc in dbus_preset_talk(&self.preset) {
291                if !result.contains(&svc.to_string()) {
292                    result.push(svc.to_string());
293                }
294            }
295        }
296        result
297    }
298}
299
300pub fn dbus_preset_talk(preset: &str) -> &[&str] {
301    match preset {
302        "flatpak" => &["org.freedesktop.Flatpak", "org.freedesktop.Flatpak.*"],
303        "gnome" => &[
304            "org.gnome.Shell",
305            "org.gnome.Shell.*",
306            "org.gnome.ScreenSaver",
307            "org.gnome.Mutter.*",
308            "org.gnome.keyring.*",
309        ],
310        "kde" => &["org.kde.*"],
311        _ => &[],
312    }
313}
314
315#[derive(Debug, Deserialize, Serialize, Clone)]
316pub struct NetworkConfig {
317    #[serde(default = "default_network_mode")]
318    pub mode: String,
319    #[serde(default, skip_serializing_if = "Vec::is_empty")]
320    pub ports: Vec<String>,
321}
322
323impl Default for NetworkConfig {
324    fn default() -> Self {
325        Self {
326            mode: default_network_mode(),
327            ports: Vec::new(),
328        }
329    }
330}
331
332#[derive(Debug, Deserialize, Serialize, Clone)]
333pub struct SecurityConfig {
334    #[serde(default)]
335    pub apparmor: Option<String>,
336    #[serde(default)]
337    pub seccomp: Option<String>,
338    #[serde(default = "default_true", skip_serializing_if = "is_true")]
339    pub security_label_disable: bool,
340    #[serde(default = "default_true", skip_serializing_if = "is_true")]
341    pub no_new_privileges: bool,
342    #[serde(default, skip_serializing_if = "is_false")]
343    pub read_only_rootfs: bool,
344    #[serde(default, skip_serializing_if = "Option::is_none")]
345    pub userns: Option<String>,
346    #[serde(default, skip_serializing_if = "is_default_cap_preset")]
347    pub cap_preset: CapPreset,
348    #[serde(default, skip_serializing_if = "Vec::is_empty")]
349    pub cap_add: Vec<String>,
350}
351
352fn is_default_cap_preset(v: &CapPreset) -> bool {
353    *v == CapPreset::Default
354}
355
356impl Default for SecurityConfig {
357    fn default() -> Self {
358        SecurityConfig {
359            apparmor: None,
360            seccomp: None,
361            security_label_disable: true,
362            no_new_privileges: true,
363            read_only_rootfs: false,
364            userns: None,
365            cap_preset: CapPreset::Default,
366            cap_add: Vec::new(),
367        }
368    }
369}