Skip to main content

podbox/codegen/quadlet/
services.rs

1//! Companion unit generators: `.build`, `.socket`, D-Bus proxy,
2//! Wayland firewall, and host socket server.
3//!
4//! Extracted verbatim from `quadlet.rs`; see `super` for the `.container`
5//! entry point.
6
7use std::path::Path;
8
9use crate::config::Config;
10
11/// Generate the `.build` Quadlet file.
12pub fn generate_build(config: &Config, containerfile_path: &Path) -> String {
13    let mut lines: Vec<String> = Vec::new();
14
15    lines.push("[Build]".into());
16    lines.push(format!(
17        "ImageTag=localhost/podbox-{}:latest",
18        config.image.name
19    ));
20    lines.push(format!("File={}", containerfile_path.to_string_lossy()));
21    lines.push(format!("Retry={}", config.image.pull_retry));
22    lines.push(format!("RetryDelay={}", config.image.pull_retry_delay));
23
24    lines.join("\n")
25}
26
27/// Generate the `.socket` Quadlet file.
28pub fn generate_socket(config: &Config) -> String {
29    let name = &config.container.name;
30    let host_service = format!("{name}-host.service");
31    let mut lines: Vec<String> = Vec::new();
32
33    lines.push("[Unit]".into());
34    lines.push(format!("Description=podbox host-guest socket -- {name}"));
35    lines.push(String::new());
36
37    lines.push("[Socket]".into());
38    lines.push(format!("ListenStream=%t/podbox/{name}.sock"));
39    lines.push(format!("Service={host_service}"));
40    lines.push("SocketMode=0600".into());
41    lines.push("DirectoryMode=0700".into());
42    lines.push("RuntimeDirectory=podbox".into());
43    lines.push("RuntimeDirectoryMode=0700".into());
44    // Keep %t/podbox alive even when no socket unit is active. Without this,
45    // systemd removes the directory when the last requesting unit stops, and
46    // a later recreation can orphan sibling containers' listening sockets.
47    lines.push("RuntimeDirectoryPreserve=yes".into());
48    lines.push(String::new());
49
50    lines.push("[Install]".into());
51    lines.push("WantedBy=sockets.target".into());
52
53    lines.join("\n")
54}
55
56/// Generate the companion D-Bus proxy `.service` unit.
57pub fn generate_dbus_proxy_service(name: &str, config: &Config) -> Option<String> {
58    if !config.use_dbus_proxy() {
59        return None;
60    }
61
62    let mut args = vec![
63        "unix:path=%t/bus".to_string(),
64        format!("%t/podbox/{}-dbus.sock", name),
65    ];
66
67    args.push("--filter".into());
68
69    for service in &config.dbus_effective_talk() {
70        args.push(format!("--talk={service}"));
71    }
72    for rule in config.dbus_portal_calls() {
73        args.push(rule);
74    }
75    for service in &config.dbus.own {
76        args.push(format!("--own={service}"));
77    }
78
79    let exec_start = format!("/usr/bin/xdg-dbus-proxy {}", args.join(" "));
80
81    Some(format!(
82        r#"[Unit]
83Description=D-Bus Proxy for podbox container {name}
84PartOf={name}.service
85
86[Service]
87Type=simple
88ExecStart={exec_start}
89Restart=on-failure
90RestartSec=1s
91
92[Install]
93WantedBy={name}.service
94"#,
95    ))
96}
97
98/// Generate the companion Wayland firewall `.service` unit.
99/// Returns `None` when the Wayland proxy is disabled in config.
100pub fn generate_compositor_service(name: &str, config: &Config) -> Option<String> {
101    if !config.use_wayland_proxy() {
102        return None;
103    }
104    let podbox_bin = std::env::current_exe()
105        .map(|p| p.to_string_lossy().to_string())
106        .unwrap_or_else(|_| "/usr/local/bin/podbox".into());
107
108    Some(format!(
109        r#"[Unit]
110Description=Wayland Firewall Proxy for podbox container {name}
111PartOf={name}.service
112
113[Service]
114Type=simple
115ExecStart={podbox_bin} compositor {name}
116Restart=on-failure
117RestartSec=1s
118
119[Install]
120WantedBy={name}.service
121"#,
122    ))
123}
124
125/// Generate the companion host socket server `.service` unit.
126pub fn generate_host_service(name: &str) -> String {
127    let podbox_bin = std::env::current_exe()
128        .map(|p| p.to_string_lossy().to_string())
129        .unwrap_or_else(|_| "/usr/local/bin/podbox".into());
130
131    format!(
132        r#"[Unit]
133Description=podbox host socket server -- {name}
134
135[Service]
136Type=simple
137ExecStart={podbox_bin} serve {name}
138Restart=on-failure
139RestartSec=2s
140
141[Install]
142WantedBy={name}.socket
143"#,
144    )
145}