use crate::utils;
use ar::Archive as ArArchive;
use flate2::read::GzDecoder;
use md5;
use mlua::{self, Lua, LuaSerdeExt, Table, Value};
use sequoia_openpgp::{Cert, parse::Parse};
use serde::Deserialize;
use sevenz_rust;
use sha2::{Digest, Sha256, Sha512};
use std::path::PathBuf;
use std::{fs, path::Path};
use urlencoding;
use walkdir::WalkDir;
use xz2::read::XzDecoder;
use zip::ZipArchive;
use zstd::stream::read::Decoder as ZstdDecoder;
fn add_parse_util(lua: &Lua) -> Result<(), mlua::Error> {
let parse_table = lua.create_table()?;
let json_fn = lua.create_function(|lua, json_str: String| {
let value: serde_json::Value = serde_json::from_str(&json_str)
.map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
lua.to_value(&value)
})?;
parse_table.set("json", json_fn)?;
let yaml_fn = lua.create_function(|lua, yaml_str: String| {
let value: serde_yaml::Value = serde_yaml::from_str(&yaml_str)
.map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
lua.to_value(&value)
})?;
parse_table.set("yaml", yaml_fn)?;
let toml_fn = lua.create_function(|lua, toml_str: String| {
let value: toml::Value =
toml::from_str(&toml_str).map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
lua.to_value(&value)
})?;
parse_table.set("toml", toml_fn)?;
let checksum_fn = lua.create_function(|_, (content, file_name): (String, String)| {
for line in content.lines() {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() == 2 && parts[1] == file_name {
return Ok(Some(parts[0].to_string()));
}
}
Ok(None)
})?;
parse_table.set("checksumFile", checksum_fn)?;
let utils_table: Table = lua.globals().get("UTILS")?;
utils_table.set("PARSE", parse_table)?;
Ok(())
}
fn add_fetch_util(lua: &Lua) -> Result<(), mlua::Error> {
let fetch_table = lua.create_table()?;
let fetch_fn = lua.create_function(|_, url: String| -> Result<String, mlua::Error> {
let response =
reqwest::blocking::get(url).map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
let text = response
.text()
.map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
Ok(text)
})?;
fetch_table.set("url", fetch_fn)?;
let utils_table: Table = lua.globals().get("UTILS")?;
utils_table.set("FETCH", fetch_table)?;
Ok(())
}
#[derive(Deserialize)]
struct GitArgs {
repo: String,
domain: Option<String>,
branch: Option<String>,
}
fn fetch_json(url: &str) -> Result<serde_json::Value, mlua::Error> {
let client = reqwest::blocking::Client::builder()
.user_agent("zoi")
.build()
.map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
let response = client
.get(url)
.send()
.map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
if !response.status().is_success() {
return Err(mlua::Error::RuntimeError(format!(
"Request to {} failed with status: {} and body: {}",
url,
response.status(),
response.text().unwrap_or_else(|_| "N/A".to_string())
)));
}
let text = response
.text()
.map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
serde_json::from_str(&text).map_err(|e| mlua::Error::RuntimeError(e.to_string()))
}
fn add_git_fetch_util(lua: &Lua) -> Result<(), mlua::Error> {
let utils_table: Table = lua.globals().get("UTILS")?;
let fetch_table: Table = utils_table.get("FETCH")?;
for provider in ["GITHUB", "GITLAB", "GITEA", "FORGEJO"] {
let provider_table = lua.create_table()?;
let latest_table = lua.create_table()?;
for what in ["tag", "release", "commit"] {
let get_latest_fn = lua.create_function(move |lua, args: Table| {
let git_args: GitArgs = lua
.from_value(Value::Table(args))
.map_err(|e| mlua::Error::RuntimeError(format!("Invalid arguments: {}", e)))?;
let base_url = match provider {
"GITHUB" => git_args
.domain
.unwrap_or_else(|| "https://api.github.com".to_string()),
"GITLAB" => git_args
.domain
.unwrap_or_else(|| "https://gitlab.com".to_string()),
"GITEA" => git_args
.domain
.unwrap_or_else(|| "https://gitea.com".to_string()),
"FORGEJO" => git_args
.domain
.unwrap_or_else(|| "https://codeberg.org".to_string()),
_ => unreachable!(),
};
let url = match (provider, what) {
("GITHUB", "tag") => format!("{}/repos/{}/tags", base_url, git_args.repo),
("GITHUB", "release") => {
format!("{}/repos/{}/releases/latest", base_url, git_args.repo)
}
("GITHUB", "commit") => format!(
"{}/repos/{}/commits?sha={}",
base_url,
git_args.repo,
git_args.branch.as_deref().unwrap_or("HEAD")
),
("GITLAB", "tag") => format!(
"{}/api/v4/projects/{}/repository/tags",
base_url,
urlencoding::encode(&git_args.repo)
),
("GITLAB", "release") => format!(
"{}/api/v4/projects/{}/releases",
base_url,
urlencoding::encode(&git_args.repo)
),
("GITLAB", "commit") => format!(
"{}/api/v4/projects/{}/repository/commits?ref_name={}",
base_url,
urlencoding::encode(&git_args.repo),
git_args.branch.as_deref().unwrap_or("HEAD")
),
("GITEA" | "FORGEJO", "tag") => {
format!("{}/api/v1/repos/{}/tags", base_url, git_args.repo)
}
("GITEA" | "FORGEJO", "release") => {
format!(
"{}/api/v1/repos/{}/releases/latest",
base_url, git_args.repo
)
}
("GITEA" | "FORGEJO", "commit") => format!(
"{}/api/v1/repos/{}/commits?sha={}",
base_url,
git_args.repo,
git_args.branch.as_deref().unwrap_or("HEAD")
),
_ => unreachable!(),
};
let json = fetch_json(&url)?;
let result = match (provider, what) {
("GITHUB", "tag") | ("GITEA", "tag") | ("FORGEJO", "tag") => json
.as_array()
.and_then(|a| a.first())
.and_then(|t| t["name"].as_str()),
("GITHUB", "release") | ("GITEA", "release") | ("FORGEJO", "release") => {
json["tag_name"].as_str()
}
("GITHUB", "commit") | ("GITEA", "commit") | ("FORGEJO", "commit") => json
.as_array()
.and_then(|a| a.first())
.and_then(|c| c["sha"].as_str()),
("GITLAB", "tag") => json
.as_array()
.and_then(|a| a.first())
.and_then(|t| t["name"].as_str()),
("GITLAB", "release") => json
.as_array()
.and_then(|a| a.first())
.and_then(|r| r["tag_name"].as_str()),
("GITLAB", "commit") => json
.as_array()
.and_then(|a| a.first())
.and_then(|c| c["id"].as_str()),
_ => unreachable!(),
};
result.map(|s| s.to_string()).ok_or_else(|| {
mlua::Error::RuntimeError(
"Could not extract value from API response".to_string(),
)
})
})?;
latest_table.set(what, get_latest_fn)?;
}
provider_table.set("LATEST", latest_table)?;
fetch_table.set(provider, provider_table)?;
}
Ok(())
}
fn add_file_util(lua: &Lua) -> Result<(), mlua::Error> {
let file_fn = lua.create_function(
|_, (url, path): (String, String)| -> Result<(), mlua::Error> {
let response = reqwest::blocking::get(url)
.map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
let content = response
.bytes()
.map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
fs::write(path, content).map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
Ok(())
},
)?;
let utils_table: Table = lua.globals().get("UTILS")?;
utils_table.set("FILE", file_fn)?;
Ok(())
}
fn add_import_util(lua: &Lua, current_path: &Path) -> Result<(), mlua::Error> {
let current_path_buf = current_path.to_path_buf();
let import_fn = lua.create_function(move |lua, file_name: String| {
let path = current_path_buf.parent().unwrap().join(&file_name);
let content =
fs::read_to_string(&path).map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
if let Some(extension) = path.extension().and_then(|s| s.to_str()) {
match extension {
"json" => {
let value: serde_json::Value = serde_json::from_str(&content)
.map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
return lua.to_value(&value);
}
"yaml" | "yml" => {
let value: serde_yaml::Value = serde_yaml::from_str(&content)
.map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
return lua.to_value(&value);
}
"toml" => {
let value: toml::Value = toml::from_str(&content)
.map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
return lua.to_value(&value);
}
_ => {
return lua.to_value(&content);
}
}
}
lua.to_value(&content)
})?;
lua.globals().set("IMPORT", import_fn)?;
Ok(())
}
fn add_include_util(lua: &Lua, current_path: &Path) -> Result<(), mlua::Error> {
let current_path_buf = current_path.to_path_buf();
let include_fn =
lua.create_function(move |lua, file_name: String| -> Result<(), mlua::Error> {
let path = current_path_buf.parent().unwrap().join(file_name);
let code =
fs::read_to_string(path).map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
lua.load(&code).exec()?;
Ok(())
})?;
lua.globals().set("INCLUDE", include_fn)?;
Ok(())
}
fn add_zcp(lua: &Lua) -> Result<(), mlua::Error> {
let zcp_fn = lua.create_function(|lua, (source, destination): (String, String)| {
let ops_table: Table = match lua.globals().get("__ZoiBuildOperations") {
Ok(t) => t,
Err(_) => {
let new_t = lua.create_table()?;
lua.globals().set("__ZoiBuildOperations", new_t.clone())?;
new_t
}
};
let op = lua.create_table()?;
op.set("op", "zcp")?;
op.set("source", source)?;
op.set("destination", destination)?;
ops_table.push(op)?;
Ok(())
})?;
lua.globals().set("zcp", zcp_fn)?;
Ok(())
}
fn add_zln(lua: &Lua) -> Result<(), mlua::Error> {
let zln_fn = lua.create_function(|lua, (target, link): (String, String)| {
let ops_table: Table = match lua.globals().get("__ZoiBuildOperations") {
Ok(t) => t,
Err(_) => {
let new_t = lua.create_table()?;
lua.globals().set("__ZoiBuildOperations", new_t.clone())?;
new_t
}
};
let op = lua.create_table()?;
op.set("op", "zln")?;
op.set("target", target)?;
op.set("link", link)?;
ops_table.push(op)?;
Ok(())
})?;
lua.globals().set("zln", zln_fn)?;
Ok(())
}
fn add_zchmod(lua: &Lua) -> Result<(), mlua::Error> {
let zchmod_fn = lua.create_function(|lua, (path, mode): (String, u32)| {
let ops_table: Table = match lua.globals().get("__ZoiBuildOperations") {
Ok(t) => t,
Err(_) => {
let new_t = lua.create_table()?;
lua.globals().set("__ZoiBuildOperations", new_t.clone())?;
new_t
}
};
let op = lua.create_table()?;
op.set("op", "zchmod")?;
op.set("path", path)?;
op.set("mode", mode)?;
ops_table.push(op)?;
Ok(())
})?;
lua.globals().set("zchmod", zchmod_fn)?;
Ok(())
}
fn add_zchown(lua: &Lua) -> Result<(), mlua::Error> {
let zchown_fn =
lua.create_function(|lua, (path, owner, group): (String, String, String)| {
let ops_table: Table = match lua.globals().get("__ZoiBuildOperations") {
Ok(t) => t,
Err(_) => {
let new_t = lua.create_table()?;
lua.globals().set("__ZoiBuildOperations", new_t.clone())?;
new_t
}
};
let op = lua.create_table()?;
op.set("op", "zchown")?;
op.set("path", path)?;
op.set("owner", owner)?;
op.set("group", group)?;
ops_table.push(op)?;
Ok(())
})?;
lua.globals().set("zchown", zchown_fn)?;
Ok(())
}
fn add_zmkdir(lua: &Lua) -> Result<(), mlua::Error> {
let zmkdir_fn = lua.create_function(|lua, path: String| {
let ops_table: Table = match lua.globals().get("__ZoiBuildOperations") {
Ok(t) => t,
Err(_) => {
let new_t = lua.create_table()?;
lua.globals().set("__ZoiBuildOperations", new_t.clone())?;
new_t
}
};
let op = lua.create_table()?;
op.set("op", "zmkdir")?;
op.set("path", path)?;
ops_table.push(op)?;
Ok(())
})?;
lua.globals().set("zmkdir", zmkdir_fn)?;
Ok(())
}
fn add_verify_hash(lua: &Lua, quiet: bool) -> Result<(), mlua::Error> {
let verify_hash_fn =
lua.create_function(move |_, (file_path, hash_str): (String, String)| {
let parts: Vec<&str> = hash_str.splitn(2, '-').collect();
if parts.len() != 2 {
return Err(mlua::Error::RuntimeError(
"Invalid hash format. Expected 'algo-hash'".to_string(),
));
}
let algo = parts[0];
let expected_hash = parts[1];
let mut file = fs::File::open(&file_path)
.map_err(|e| mlua::Error::RuntimeError(format!("Failed to open file: {}", e)))?;
let actual_hash = match algo {
"md5" => {
let mut hasher = md5::Context::new();
std::io::copy(&mut file, &mut hasher).map_err(|e| {
mlua::Error::RuntimeError(format!("Failed to read file: {}", e))
})?;
format!("{:x}", hasher.finalize())
}
"sha256" => {
let mut hasher = Sha256::new();
std::io::copy(&mut file, &mut hasher).map_err(|e| {
mlua::Error::RuntimeError(format!("Failed to read file: {}", e))
})?;
hex::encode(hasher.finalize())
}
"sha512" => {
let mut hasher = Sha512::new();
std::io::copy(&mut file, &mut hasher).map_err(|e| {
mlua::Error::RuntimeError(format!("Failed to read file: {}", e))
})?;
hex::encode(hasher.finalize())
}
_ => {
return Err(mlua::Error::RuntimeError(format!(
"Unsupported hash algorithm: {}",
algo
)));
}
};
if actual_hash.eq_ignore_ascii_case(expected_hash) {
Ok(true)
} else {
if !quiet {
println!(
"Hash mismatch for {}: expected {}, got {}",
file_path, expected_hash, actual_hash
);
}
Ok(false)
}
})?;
lua.globals().set("verifyHash", verify_hash_fn)?;
Ok(())
}
fn add_zrm(lua: &Lua) -> Result<(), mlua::Error> {
let zrm_fn = lua.create_function(|lua, path: String| {
let ops_table: Table = match lua.globals().get("__ZoiUninstallOperations") {
Ok(t) => t,
Err(_) => {
let new_t = lua.create_table()?;
lua.globals()
.set("__ZoiUninstallOperations", new_t.clone())?;
new_t
}
};
let op = lua.create_table()?;
op.set("op", "zrm")?;
op.set("path", path)?;
ops_table.push(op)?;
Ok(())
})?;
lua.globals().set("zrm", zrm_fn)?;
Ok(())
}
fn add_cmd_util(lua: &Lua, quiet: bool) -> Result<(), mlua::Error> {
let cmd_fn = lua.create_function(move |lua, command: String| {
let build_dir: String = lua.globals().get("BUILD_DIR")?;
if !quiet {
println!("Executing: {}", command);
}
let output = if cfg!(target_os = "windows") {
std::process::Command::new("pwsh")
.arg("-Command")
.arg(&command)
.current_dir(&build_dir)
.output()
} else {
std::process::Command::new("bash")
.arg("-c")
.arg(&command)
.current_dir(&build_dir)
.output()
};
match output {
Ok(out) => {
let stdout = String::from_utf8_lossy(&out.stdout).to_string();
let stderr = String::from_utf8_lossy(&out.stderr).to_string();
let exit_code =
out.status
.code()
.unwrap_or(if out.status.success() { 0 } else { 1 });
if !out.status.success() && !quiet {
eprintln!("[cmd] {}", stderr);
}
Ok((stdout, stderr, exit_code))
}
Err(e) => {
if !quiet {
eprintln!("[cmd] Failed to execute command: {}", e);
}
Ok((String::new(), e.to_string(), 1))
}
}
})?;
lua.globals().set("cmd", cmd_fn)?;
Ok(())
}
fn add_fs_util(lua: &Lua) -> Result<(), mlua::Error> {
let fs_table = lua.create_table()?;
let exists_fn = lua.create_function(|_, path: String| Ok(Path::new(&path).exists()))?;
fs_table.set("exists", exists_fn)?;
let copy_fn = lua.create_function(|_, (src, dest): (String, String)| {
let src_path = Path::new(&src);
let dest_path = Path::new(&dest);
if src_path.is_dir() {
utils::copy_dir_all(src_path, dest_path)
.map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
} else {
fs::copy(src_path, dest_path).map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
}
Ok(true)
})?;
fs_table.set("copy", copy_fn)?;
let move_fn = lua.create_function(|_, (src, dest): (String, String)| {
fs::rename(src, dest).map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
Ok(true)
})?;
fs_table.set("move", move_fn)?;
let chmod_fn = lua.create_function(|_, (path, mode): (String, u32)| {
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
fs::set_permissions(path, fs::Permissions::from_mode(mode))
.map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
}
#[cfg(windows)]
{
let _ = (path, mode);
}
Ok(true)
})?;
fs_table.set("chmod", chmod_fn)?;
let utils_table: Table = lua.globals().get("UTILS")?;
utils_table.set("FS", fs_table)?;
Ok(())
}
fn add_find_util(lua: &Lua) -> Result<(), mlua::Error> {
let find_table = lua.create_table()?;
let find_file_fn = lua.create_function(|lua, (dir, name): (String, String)| {
let build_dir_str: String = lua.globals().get("BUILD_DIR")?;
let search_dir = Path::new(&build_dir_str).join(dir);
for entry in WalkDir::new(search_dir) {
let entry = entry.map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
if entry.file_name().to_string_lossy() == name {
let path = entry.path();
let relative_path = path.strip_prefix(Path::new(&build_dir_str)).unwrap();
return Ok(Some(relative_path.to_string_lossy().to_string()));
}
}
Ok(None)
})?;
find_table.set("file", find_file_fn)?;
let utils_table: Table = lua.globals().get("UTILS")?;
utils_table.set("FIND", find_table)?;
Ok(())
}
fn add_extract_util(lua: &Lua, quiet: bool) -> Result<(), mlua::Error> {
let extract_fn =
lua.create_function(move |lua, (source, out_name): (String, Option<String>)| {
let build_dir_str: String = lua.globals().get("BUILD_DIR")?;
let build_dir = Path::new(&build_dir_str);
let archive_file = if source.starts_with("http") {
if !quiet {
println!("Downloading: {}", source);
}
let file_name = source.split('/').next_back().unwrap_or("download.tmp");
let temp_path = build_dir.join(file_name);
let response = reqwest::blocking::get(&source)
.map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
let content = response
.bytes()
.map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
fs::write(&temp_path, content)
.map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
temp_path
} else {
PathBuf::from(source)
};
let out_dir_name = out_name.unwrap_or_else(|| "extracted".to_string());
let out_dir = build_dir.join(&out_dir_name);
if !out_dir.starts_with(build_dir) || out_dir == build_dir {
return Err(mlua::Error::RuntimeError(format!(
"Invalid output directory: {}. Extraction must be into a subdirectory of the build directory.",
out_dir_name
)));
}
fs::create_dir_all(&out_dir).map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
if !quiet {
println!(
"Extracting {} to {}",
archive_file.display(),
out_dir.display()
);
}
let file = fs::File::open(&archive_file)
.map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
let archive_path_str = archive_file.to_string_lossy();
if archive_path_str.ends_with(".zip") {
let mut archive =
ZipArchive::new(file).map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
archive
.extract(&out_dir)
.map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
} else if archive_path_str.ends_with(".tar.gz") || archive_path_str.ends_with(".tgz") {
let tar_gz = GzDecoder::new(file);
let mut archive = tar::Archive::new(tar_gz);
archive
.unpack(&out_dir)
.map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
} else if archive_path_str.ends_with(".tar.zst") {
let tar_zst =
ZstdDecoder::new(file).map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
let mut archive = tar::Archive::new(tar_zst);
archive
.unpack(&out_dir)
.map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
} else if archive_path_str.ends_with(".tar.xz") {
let tar_xz = XzDecoder::new(file);
let mut archive = tar::Archive::new(tar_xz);
archive
.unpack(&out_dir)
.map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
} else if archive_path_str.ends_with(".7z") {
sevenz_rust::decompress_file(&archive_file, &out_dir)
.map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
} else if archive_path_str.ends_with(".rar") {
if crate::utils::command_exists("unrar") {
let status = std::process::Command::new("unrar")
.arg("x")
.arg("-y")
.arg(&archive_file)
.arg(&out_dir)
.status()
.map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
if !status.success() {
return Err(mlua::Error::RuntimeError("unrar failed".to_string()));
}
} else {
return Err(mlua::Error::RuntimeError(
"unrar command not found. Please install unrar to extract .rar files."
.to_string(),
));
}
} else if archive_path_str.ends_with(".deb") {
let mut ar = ArArchive::new(file);
while let Some(entry_result) = ar.next_entry() {
let mut entry =
entry_result.map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
let name = String::from_utf8_lossy(entry.header().identifier()).to_string();
if name.starts_with("data.tar") {
let temp_data_path = build_dir.join(&name);
let mut temp_file = fs::File::create(&temp_data_path)
.map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
std::io::copy(&mut entry, &mut temp_file)
.map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
let data_file = fs::File::open(&temp_data_path)
.map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
if name.ends_with(".gz") {
let mut archive = tar::Archive::new(GzDecoder::new(data_file));
archive
.unpack(&out_dir)
.map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
} else if name.ends_with(".xz") {
let mut archive = tar::Archive::new(XzDecoder::new(data_file));
archive
.unpack(&out_dir)
.map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
} else if name.ends_with(".zst") {
let mut archive = tar::Archive::new(
ZstdDecoder::new(data_file)
.map_err(|e| mlua::Error::RuntimeError(e.to_string()))?,
);
archive
.unpack(&out_dir)
.map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
}
fs::remove_file(temp_data_path).ok();
}
}
} else {
return Err(mlua::Error::RuntimeError(format!(
"Unsupported archive format for file: {}",
archive_path_str
)));
}
Ok(())
})?;
let utils_table: Table = lua.globals().get("UTILS")?;
utils_table.set("EXTRACT", extract_fn)?;
Ok(())
}
fn add_verify_signature(lua: &Lua, quiet: bool) -> Result<(), mlua::Error> {
let verify_sig_fn = lua.create_function(
move |_, (file_path, sig_path, key_source): (String, String, String)| {
let key_bytes: Vec<u8> = if key_source.starts_with("http") {
match reqwest::blocking::get(&key_source).and_then(|r| r.bytes()) {
Ok(b) => b.to_vec(),
Err(e) => {
return Err(mlua::Error::RuntimeError(format!(
"Failed to download key: {}",
e
)));
}
}
} else if Path::new(&key_source).exists() {
match fs::read(&key_source) {
Ok(b) => b,
Err(e) => {
return Err(mlua::Error::RuntimeError(format!(
"Failed to read key file: {}",
e
)));
}
}
} else {
let pgp_dir = match crate::pkg::pgp::get_pgp_dir() {
Ok(dir) => dir,
Err(e) => {
return Err(mlua::Error::RuntimeError(format!(
"Failed to get PGP dir: {}",
e
)));
}
};
let key_path = pgp_dir.join(format!("{}.asc", key_source));
if !key_path.exists() {
return Err(mlua::Error::RuntimeError(format!(
"Key with name '{}' not found.",
key_source
)));
}
match fs::read(&key_path) {
Ok(b) => b,
Err(e) => {
return Err(mlua::Error::RuntimeError(format!(
"Failed to read key file: {}",
e
)));
}
}
};
let cert = match Cert::from_bytes(&key_bytes) {
Ok(c) => c,
Err(e) => return Err(mlua::Error::RuntimeError(format!("Invalid PGP key: {}", e))),
};
let result = crate::pkg::pgp::verify_detached_signature(
Path::new(&file_path),
Path::new(&sig_path),
&cert,
);
match result {
Ok(_) => Ok(true),
Err(e) => {
if !quiet {
eprintln!("Signature verification failed: {}", e);
}
Ok(false)
}
}
},
)?;
lua.globals().set("verifySignature", verify_sig_fn)?;
Ok(())
}
fn add_add_pgp_key(lua: &Lua, quiet: bool) -> Result<(), mlua::Error> {
let add_pgp_key_fn = lua.create_function(move |_, (source, name): (String, String)| {
let result = if source.starts_with("http") {
crate::pkg::pgp::add_key_from_url(&source, &name)
} else {
crate::pkg::pgp::add_key_from_path(&source, Some(&name))
};
if let Err(e) = result {
if !quiet {
eprintln!("Failed to add PGP key '{}': {}", name, e);
}
return Ok(false);
}
Ok(true)
})?;
lua.globals().set("addPgpKey", add_pgp_key_fn)?;
Ok(())
}
fn add_archive_util(lua: &Lua) -> Result<(), mlua::Error> {
let archive_table = lua.create_table()?;
let list_fn = lua.create_function(|_, path: String| {
let file = fs::File::open(&path).map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
let mut files = Vec::new();
if path.ends_with(".zip") {
let mut archive =
ZipArchive::new(file).map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
for i in 0..archive.len() {
let file = archive
.by_index(i)
.map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
files.push(file.name().to_string());
}
} else if path.ends_with(".tar.gz") || path.ends_with(".tgz") {
let tar_gz = GzDecoder::new(file);
let mut archive = tar::Archive::new(tar_gz);
for entry in archive
.entries()
.map_err(|e| mlua::Error::RuntimeError(e.to_string()))?
{
let entry = entry.map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
files.push(
entry
.path()
.map_err(|e| mlua::Error::RuntimeError(e.to_string()))?
.to_string_lossy()
.to_string(),
);
}
} else if path.ends_with(".tar.zst") {
let tar_zst =
ZstdDecoder::new(file).map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
let mut archive = tar::Archive::new(tar_zst);
for entry in archive
.entries()
.map_err(|e| mlua::Error::RuntimeError(e.to_string()))?
{
let entry = entry.map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
files.push(
entry
.path()
.map_err(|e| mlua::Error::RuntimeError(e.to_string()))?
.to_string_lossy()
.to_string(),
);
}
} else if path.ends_with(".tar.xz") {
let tar_xz = XzDecoder::new(file);
let mut archive = tar::Archive::new(tar_xz);
for entry in archive
.entries()
.map_err(|e| mlua::Error::RuntimeError(e.to_string()))?
{
let entry = entry.map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
files.push(
entry
.path()
.map_err(|e| mlua::Error::RuntimeError(e.to_string()))?
.to_string_lossy()
.to_string(),
);
}
} else if path.ends_with(".7z") {
let file =
fs::File::open(&path).map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
let len = file
.metadata()
.map_err(|e| mlua::Error::RuntimeError(e.to_string()))?
.len();
let reader = sevenz_rust::SevenZReader::new(file, len, sevenz_rust::Password::empty())
.map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
for entry in &reader.archive().files {
files.push(entry.name.to_string());
}
} else if path.ends_with(".rar") {
if crate::utils::command_exists("unrar") {
let output = std::process::Command::new("unrar")
.arg("lb")
.arg(&path)
.output()
.map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
if output.status.success() {
let list = String::from_utf8_lossy(&output.stdout);
for line in list.lines() {
files.push(line.to_string());
}
}
}
} else if path.ends_with(".deb") {
let mut ar = ArArchive::new(file);
while let Some(entry_result) = ar.next_entry() {
let entry = entry_result.map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
let header = entry.header();
files.push(String::from_utf8_lossy(header.identifier()).to_string());
}
} else {
return Err(mlua::Error::RuntimeError(format!(
"Unsupported archive format: {}",
path
)));
}
Ok(files)
})?;
archive_table.set("list", list_fn)?;
let utils_table: Table = lua.globals().get("UTILS")?;
utils_table.set("ARCHIVE", archive_table)?;
Ok(())
}
pub fn add_package_lifecycle_functions(lua: &Lua) -> Result<(), mlua::Error> {
let metadata_fn = lua.create_function(move |lua, pkg_def: Table| {
if let Ok(meta_table) = lua.globals().get::<Table>("__ZoiPackageMeta")
&& let Ok(pkg_global) = lua.globals().get::<Table>("PKG")
{
for pair in pkg_def.pairs::<Value, Value>() {
let (key, value) = pair?;
meta_table.set(key.clone(), value.clone())?;
pkg_global.set(key, value)?;
}
}
Ok(())
})?;
lua.globals().set("metadata", metadata_fn)?;
let system_config_fn = lua.create_function(move |lua, config: Table| {
if let Ok(sys_table) = lua.globals().get::<Table>("__ZoiSystemConfig") {
for pair in config.pairs::<String, Value>() {
let (key, value) = pair?;
sys_table.set(key, value)?;
}
}
Ok(())
})?;
lua.globals().set("system_config", system_config_fn)?;
let dependencies_fn = lua.create_function(move |lua, deps_def: Table| {
if let Ok(deps_table) = lua.globals().get::<Table>("__ZoiPackageDeps") {
for pair in deps_def.pairs::<String, Value>() {
let (key, value) = pair?;
deps_table.set(key, value)?;
}
}
Ok(())
})?;
lua.globals().set("dependencies", dependencies_fn)?;
let updates_fn = lua.create_function(move |lua, updates_list: Table| {
if let Ok(updates_table) = lua.globals().get::<Table>("__ZoiPackageUpdates") {
for pair in updates_list.pairs::<Value, Table>() {
let (_, update_info) = pair?;
updates_table.push(update_info)?;
}
}
Ok(())
})?;
lua.globals().set("updates", updates_fn)?;
let hooks_fn = lua.create_function(move |lua, hooks_def: Table| {
if let Ok(hooks_table) = lua.globals().get::<Table>("__ZoiPackageHooks") {
for pair in hooks_def.pairs::<String, Value>() {
let (key, value) = pair?;
hooks_table.set(key, value)?;
}
}
Ok(())
})?;
lua.globals().set("hooks", hooks_fn)?;
let service_fn = lua.create_function(move |lua, service_def: Table| {
if let Ok(service_table) = lua.globals().get::<Table>("__ZoiPackageService") {
for pair in service_def.pairs::<String, Value>() {
let (key, value) = pair?;
service_table.set(key, value)?;
}
}
Ok(())
})?;
lua.globals().set("service", service_fn)?;
let prepare_fn = lua.create_function(|_, _: Table| Ok(()))?;
lua.globals().set("prepare", prepare_fn)?;
let package_fn = lua.create_function(|_, _: Table| Ok(()))?;
lua.globals().set("package", package_fn)?;
let verify_fn = lua.create_function(|_, _: Table| Ok(()))?;
lua.globals().set("verify", verify_fn)?;
let test_fn = lua.create_function(|_, _: Table| Ok(true))?;
lua.globals().set("test", test_fn)?;
let uninstall_fn = lua.create_function(|_, _: Table| Ok(()))?;
lua.globals().set("uninstall", uninstall_fn)?;
Ok(())
}
pub fn setup_lua_environment(
lua: &Lua,
platform: &str,
version_override: Option<&str>,
file_path: Option<&str>,
create_pkg_dir: Option<&str>,
sub_package: Option<&str>,
quiet: bool,
) -> Result<(), mlua::Error> {
let system_table = lua.create_table()?;
let parts: Vec<&str> = platform.split('-').collect();
system_table.set("OS", *parts.first().unwrap_or(&""))?;
system_table.set("ARCH", *parts.get(1).unwrap_or(&""))?;
if let Some(distro) = utils::get_linux_distribution() {
system_table.set("DISTRO", distro)?;
}
if let Some(manager) = utils::get_native_package_manager() {
system_table.set("MANAGER", manager)?;
}
lua.globals().set("SYSTEM", system_table)?;
let zoi_table = lua.create_table()?;
if let Some(ver) = version_override {
zoi_table.set("VERSION", ver)?;
}
if let Some(dir) = create_pkg_dir {
zoi_table.set("CREATE_PKG_DIR", dir)?;
}
if let Some(sub) = sub_package {
lua.globals().set("SUBPKG", sub)?;
}
let path_table = lua.create_table()?;
if let Some(home_dir) = home::home_dir() {
path_table.set("user", home_dir.join(".zoi").to_string_lossy().to_string())?;
}
let system_bin_path = if cfg!(target_os = "windows") {
"C:\\ProgramData\\zoi\\pkgs\\bin".to_string()
} else {
"/usr/local/bin".to_string()
};
path_table.set("system", system_bin_path)?;
zoi_table.set("PATH", path_table)?;
let pkg_table = lua.create_table()?;
if let Some(home_dir) = home::home_dir() {
pkg_table.set("home", home_dir.to_string_lossy().to_string())?;
pkg_table.set(
"store",
home_dir
.join(".zoi")
.join("pkgs")
.join("store")
.to_string_lossy()
.to_string(),
)?;
}
if let Ok(current_dir) = std::env::current_dir() {
pkg_table.set("template", current_dir.to_string_lossy().to_string())?;
}
let root = if cfg!(target_os = "windows") {
"C:\\"
} else {
"/"
};
pkg_table.set("root", root)?;
if let Some(path_str) = file_path {
let abs_path = if let Ok(p) = fs::canonicalize(path_str) {
p
} else {
Path::new(path_str).to_path_buf()
};
pkg_table.set("lua", abs_path.to_string_lossy().to_string())?;
}
zoi_table.set("PKG", pkg_table)?;
lua.globals().set("ZOI", zoi_table)?;
let utils_table = lua.create_table()?;
lua.globals().set("UTILS", utils_table)?;
add_fetch_util(lua)?;
add_parse_util(lua)?;
add_git_fetch_util(lua)?;
add_file_util(lua)?;
add_zcp(lua)?;
add_zln(lua)?;
add_zchmod(lua)?;
add_zchown(lua)?;
add_zmkdir(lua)?;
add_verify_hash(lua, quiet)?;
add_zrm(lua)?;
add_cmd_util(lua, quiet)?;
add_fs_util(lua)?;
add_find_util(lua)?;
add_archive_util(lua)?;
add_extract_util(lua, quiet)?;
add_verify_signature(lua, quiet)?;
add_add_pgp_key(lua, quiet)?;
add_package_lifecycle_functions(lua)?;
if let Some(path_str) = file_path {
let path = Path::new(path_str);
add_import_util(lua, path)?;
add_include_util(lua, path)?;
}
if quiet {
let quiet_print = lua.create_function(|_, _: mlua::MultiValue| Ok(()))?;
lua.globals().set("print", quiet_print)?;
}
Ok(())
}