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 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 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 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 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}