Skip to main content

zoi_package/
bwrap.rs

1use anyhow::{Result, anyhow};
2use std::path::Path;
3
4pub fn run(
5    package_file: &Path,
6    build_type: Option<&str>,
7    platforms: &[String],
8    sign_key: Option<String>,
9    output_dir: Option<&Path>,
10    version_override: Option<&str>,
11    sub_packages: Option<Vec<String>>,
12    fakeroot: bool,
13    install_deps: bool,
14    test: bool,
15) -> Result<()> {
16    #[cfg(not(target_os = "linux"))]
17    {
18        let _ = (
19            package_file,
20            build_type,
21            platforms,
22            sign_key,
23            output_dir,
24            version_override,
25            sub_packages,
26            fakeroot,
27            install_deps,
28            test,
29        );
30        return Err(anyhow!("Bubblewrap ('bwrap') is only supported on Linux."));
31    }
32
33    #[cfg(target_os = "linux")]
34    {
35        use colored::*;
36        use std::path::PathBuf;
37        use std::process::Command;
38        use zoi_core::utils;
39
40        println!(
41            "{} Building package using Bubblewrap sandbox...",
42            "::".bold().blue()
43        );
44
45        if !utils::command_exists("bwrap") {
46            return Err(anyhow!(
47                "Bubblewrap ('bwrap') is not installed or not in PATH. Please install it to use this method."
48            ));
49        }
50
51        let abs_package_file = package_file.canonicalize()?;
52        let package_dir = abs_package_file
53            .parent()
54            .ok_or_else(|| anyhow!("Could not get parent directory of package file"))?;
55
56        let abs_output_dir = if let Some(dir) = output_dir {
57            if !dir.exists() {
58                std::fs::create_dir_all(dir)?;
59            }
60            dir.canonicalize()?
61        } else {
62            package_dir.to_path_buf()
63        };
64
65        // We use a temporary directory for the build inside the sandbox
66        let container_workdir = "/work";
67        let container_output_dir = "/output";
68
69        let zoi_exe = std::env::current_exe()?;
70        let zoi_exe_dir = zoi_exe
71            .parent()
72            .ok_or_else(|| anyhow!("Could not get zoi executable directory"))?;
73
74        let home_dir = zoi_core::utils::get_user_home()
75            .ok_or_else(|| anyhow!("Could not get home directory"))?;
76        let zoi_home = home_dir.join(".zoi");
77
78        let package_filename = abs_package_file
79            .file_name()
80            .ok_or_else(|| anyhow!("Invalid package file name"))?
81            .to_string_lossy();
82
83        // Construct the inner zoi command
84        let mut inner_cmd = format!(
85            "zoi package build {} --output-dir {} --method native",
86            package_filename, container_output_dir
87        );
88
89        if let Some(bt) = build_type {
90            inner_cmd.push_str(&format!(" --type {}", bt));
91        }
92
93        for p in platforms {
94            inner_cmd.push_str(&format!(" --platform {}", p));
95        }
96
97        if let Some(sk) = sign_key {
98            inner_cmd.push_str(&format!(" --sign {}", sk));
99        }
100
101        if let Some(v) = version_override {
102            inner_cmd.push_str(&format!(" --version-override {}", v));
103        }
104
105        if let Some(subs) = sub_packages {
106            for s in subs {
107                inner_cmd.push_str(&format!(" --sub {}", s));
108            }
109        }
110
111        if fakeroot {
112            inner_cmd.push_str(" --fakeroot");
113        }
114
115        if install_deps {
116            inner_cmd.push_str(" --install-deps");
117        }
118
119        if test {
120            inner_cmd.push_str(" --test");
121        }
122
123        // Base bwrap arguments
124        let sysroot = zoi_core::sysroot::get_sysroot();
125
126        let mut envs = std::collections::HashMap::new();
127        envs.insert(
128            "PATH".to_string(),
129            "/zoi_bin:/usr/bin:/bin:/usr/sbin:/sbin".to_string(),
130        );
131        envs.insert("ZOI_SKIP_LOCK".to_string(), "1".to_string());
132        envs.insert("HOME".to_string(), home_dir.display().to_string());
133
134        let status = if let Some(root) = &sysroot {
135            println!(
136                "{} Isolated build using sysroot: {}",
137                "::".bold().yellow(),
138                root.display()
139            );
140
141            let extra_binds = vec![
142                (package_dir.to_path_buf(), PathBuf::from(container_workdir)),
143                (abs_output_dir.clone(), PathBuf::from(container_output_dir)),
144                (zoi_exe_dir.to_path_buf(), PathBuf::from("/zoi_bin")),
145            ];
146
147            let mut cmd = zoi_sandbox::wrap_command_in_root(
148                root,
149                &PathBuf::from("/bin/bash"),
150                &["-c".to_string(), inner_cmd],
151                &envs,
152                &extra_binds,
153                fakeroot,
154            )?;
155            cmd.status()?
156        } else {
157            // Base bwrap arguments for non-sysroot build
158            let mut bwrap_args = vec![
159                "--unshare-all".to_string(),
160                "--share-net".to_string(),
161                "--hostname".to_string(),
162                "zoi-build".to_string(),
163                "--dev".to_string(),
164                "/dev".to_string(),
165                "--proc".to_string(),
166                "/proc".to_string(),
167                "--tmpfs".to_string(),
168                "/tmp".to_string(),
169                "--tmpfs".to_string(),
170                "/run".to_string(),
171                "--tmpfs".to_string(),
172                "/var".to_string(),
173                "--ro-bind".to_string(),
174                "/usr".to_string(),
175                "/usr".to_string(),
176                "--symlink".to_string(),
177                "/usr/bin".to_string(),
178                "/bin".to_string(),
179                "--symlink".to_string(),
180                "/usr/lib".to_string(),
181                "/lib".to_string(),
182                "--symlink".to_string(),
183                "/usr/lib64".to_string(),
184                "/lib64".to_string(),
185                "--symlink".to_string(),
186                "/usr/sbin".to_string(),
187                "/sbin".to_string(),
188                "--ro-bind".to_string(),
189                "/etc".to_string(),
190                "/etc".to_string(),
191                "--bind".to_string(),
192                package_dir.display().to_string(),
193                container_workdir.to_string(),
194                "--bind".to_string(),
195                abs_output_dir.display().to_string(),
196                container_output_dir.to_string(),
197                "--ro-bind".to_string(),
198                zoi_exe_dir.display().to_string(),
199                "/zoi_bin".to_string(),
200                "--chdir".to_string(),
201                container_workdir.to_string(),
202                "--setenv".to_string(),
203                "PATH".to_string(),
204                "/zoi_bin:/usr/bin:/bin:/usr/sbin:/sbin".to_string(),
205                "--setenv".to_string(),
206                "ZOI_SKIP_LOCK".to_string(),
207                "1".to_string(),
208            ];
209
210            if zoi_home.exists() {
211                bwrap_args.push("--bind".to_string());
212                bwrap_args.push(zoi_home.display().to_string());
213                bwrap_args.push(zoi_home.display().to_string());
214            }
215
216            let system_zoi = Path::new("/var/lib/zoi");
217            if system_zoi.exists() {
218                bwrap_args.push("--bind".to_string());
219                bwrap_args.push(system_zoi.display().to_string());
220                bwrap_args.push(system_zoi.display().to_string());
221            }
222
223            if fakeroot {
224                bwrap_args.push("--uid".to_string());
225                bwrap_args.push("0".to_string());
226                bwrap_args.push("--gid".to_string());
227                bwrap_args.push("0".to_string());
228            } else {
229                let uid = nix::unistd::getuid().as_raw();
230                let gid = nix::unistd::getgid().as_raw();
231                bwrap_args.push("--uid".to_string());
232                bwrap_args.push(uid.to_string());
233                bwrap_args.push("--gid".to_string());
234                bwrap_args.push(gid.to_string());
235            }
236
237            bwrap_args.push("--setenv".to_string());
238            bwrap_args.push("HOME".to_string());
239            bwrap_args.push(home_dir.display().to_string());
240
241            bwrap_args.push("bash".to_string());
242            bwrap_args.push("-c".to_string());
243            bwrap_args.push(inner_cmd);
244
245            Command::new("bwrap").args(&bwrap_args).status()?
246        };
247
248        if !status.success() {
249            return Err(anyhow!(
250                "Bubblewrap build failed with exit code {:?}",
251                status.code()
252            ));
253        }
254
255        println!("{}", "Bubblewrap build successful!".green());
256
257        Ok(())
258    }
259}