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}