Fix: Pin runtime and agent artifacts
This commit is contained in:
@@ -36,7 +36,7 @@ SHELL_WORDS='^(if|then|else|elif|fi|for|while|until|do|done|case|esac|in|functio
|
||||
# authselect is on the list for the same reason: it manages Fedora's PAM and
|
||||
# nsswitch profiles and arrives with fprintd-pam, realmd and nss-mdns, so the
|
||||
# fingerprint aliases in config/bash can rely on it without declaring it.
|
||||
BASELINE='^(sh|bash|cat|cut|sed|awk|gawk|grep|egrep|head|tail|sort|uniq|tr|wc|find|xargs|basename|dirname|mkdir|rm|cp|mv|ln|chmod|chown|stat|df|du|date|sleep|env|id|tee|touch|mktemp|readlink|realpath|seq|comm|join|paste|od|file|nl|fold|column|tput|timeout|flock|install|sha256sum|md5sum|base64|nproc|uptime|free|uname|hostname|whoami|ps|pgrep|pkill|kill|killall|lsblk|mount|umount|sudo|su|rpm|dnf|flatpak|git|python3|ss|ip|ls|rfkill|lsof|authselect|setsid|nohup|grub2-mkconfig|sysctl)$'
|
||||
BASELINE='^(sh|bash|cat|cut|sed|awk|gawk|grep|egrep|head|tail|sort|uniq|tr|wc|find|xargs|basename|dirname|mkdir|rm|cp|mv|ln|chmod|chown|stat|df|du|date|sleep|env|id|tee|touch|mktemp|readlink|realpath|seq|comm|join|paste|od|file|nl|fold|column|tput|timeout|flock|install|tar|sha256sum|md5sum|base64|nproc|uptime|free|uname|hostname|whoami|ps|pgrep|pkill|kill|killall|lsblk|mount|umount|sudo|su|rpm|dnf|flatpak|git|python3|ss|ip|ls|rfkill|lsof|authselect|setsid|nohup|grub2-mkconfig|sysctl)$'
|
||||
|
||||
# bootctl and coredumpctl ship in systemd-udev, which every Fedora install
|
||||
# carries -- it is the udev half of systemd, not an optional tool. Declaring
|
||||
@@ -45,10 +45,10 @@ BASELINE='^(sh|bash|cat|cut|sed|awk|gawk|grep|egrep|head|tail|sort|uniq|tr|wc|fi
|
||||
SESSION='^(systemctl|busctl|journalctl|loginctl|hostnamectl|localectl|systemd-inhibit|systemd-run|udevadm|bootctl|coredumpctl|gsettings|dconf|dbus-send|dbus-monitor|hyprctl|qs|quickshell|gnf|panama|wl-copy|wl-paste)$'
|
||||
|
||||
# Installed by install-packages itself rather than by a package list. Two
|
||||
# reasons, both deliberate: bun, claude and codex have no RPM or flatpak at
|
||||
# all (codex comes through npm), and node, npm and pnpm come from nvm on
|
||||
# purpose -- a dnf nodejs earlier on PATH would win every per-project
|
||||
# `nvm use`, which is the whole point of having nvm.
|
||||
# reasons, both deliberate: bun and codex use reviewed release archives,
|
||||
# Claude Code and pnpm use signed DNF repositories, and node/npm use a reviewed
|
||||
# Node archive inside nvm's version directory. A system nodejs earlier on PATH
|
||||
# would win every per-project `nvm use`, which is the whole point of having nvm.
|
||||
# Anything added here needs a matching install block and a stated reason.
|
||||
SELF_INSTALLED='^(bun|claude|codex|node|npm|pnpm)$'
|
||||
|
||||
|
||||
@@ -106,6 +106,28 @@ before_system_flathub_key="$(snapshot_file_state /var/lib/flatpak/repo/flathub.t
|
||||
before_user_flatpak="$(snapshot_file_state "$HOME/.local/share/flatpak/repo/config")"
|
||||
before_bashrc="$(snapshot_file_state "$HOME/.bashrc")"
|
||||
|
||||
# Runtime and agent installs must consume the reviewed provenance table. Keep
|
||||
# this scan at the public script boundary because a command hidden elsewhere in
|
||||
# the installer can bypass every archive-level test below.
|
||||
installer="$repo_dir/setup/scripts/install-packages"
|
||||
unsafe_installers=()
|
||||
for forbidden in \
|
||||
'curl[^|]*\|[[:space:]]*bash' \
|
||||
'nvm[[:space:]]+install[[:space:]]+--lts' \
|
||||
'npm[[:space:]]+install[[:space:]]+-g[[:space:]]+pnpm' \
|
||||
'npm[[:space:]]+install[[:space:]]+-g[[:space:]]+@openai/codex' \
|
||||
'releases/latest' \
|
||||
'api\.github\.com/.*/releases/latest'; do
|
||||
while IFS= read -r finding; do
|
||||
[[ -n "$finding" ]] && unsafe_installers+=("$finding")
|
||||
done < <(grep -nE "$forbidden" "$installer" || true)
|
||||
done
|
||||
if (( ${#unsafe_installers[@]} > 0 )); then
|
||||
printf 'package provenance contract: moving or piped installer inputs:\n' >&2
|
||||
printf ' %s\n' "${unsafe_installers[@]}" >&2
|
||||
fail 'replace each finding with a reviewed, verified installation path'
|
||||
fi
|
||||
|
||||
# This must be the only production file sourced by the contract.
|
||||
# shellcheck source=../../setup/lib/artifact-provenance
|
||||
source "$repo_dir/setup/lib/artifact-provenance"
|
||||
@@ -228,6 +250,22 @@ expect_failure rpm_signature_matches \
|
||||
"$test_tmp/wrong-signer-fixture.rpm" "$test_tmp/combined-key.asc" "$fixture_fingerprint"
|
||||
expect_success load_installer_provenance "$config"
|
||||
[[ "${INSTALLER_PROVENANCE[BUN_VERSION]:-}" == '1.4.0' ]] || fail 'valid provenance was not loaded'
|
||||
for reviewed_value in \
|
||||
'NODE_VERSION 24.20.0' \
|
||||
'NODE_X86_64_SHA256 2f2c0da162318f0de47665410c7c8c2ed3d36c8f3105de4bbc61176c70a7cbf2' \
|
||||
'NODE_AARCH64_SHA256 5f4ddab610c1ab2016b3c227cebdbf6d9495161487e4739c7b90090595f465f7' \
|
||||
'BUN_VERSION 1.4.0' \
|
||||
'BUN_X86_64_SHA256 2d03fb5fb83ac8b567aca0a281b2ce1a1a19d488f56c2968d88c3f25e92fe452' \
|
||||
'BUN_AARCH64_SHA256 4b1a332ee861983eb93bcfe6f770fff94e3e31b2c388bdaea3c8ed35e58eed0e' \
|
||||
'CODEX_VERSION 0.150.1' \
|
||||
'CODEX_X86_64_SHA256 00aba704f029f6dc0d948be407a756e0c97cc840132fd691353b2c6b0a505b17' \
|
||||
'CODEX_AARCH64_SHA256 1ecac3f87823efb98153233b076ea3d6e34a7a8cebe43c5285dc5f79e1514639' \
|
||||
'RUSTDESK_VERSION 1.4.9' \
|
||||
'RUSTDESK_X86_64_SHA256 eb1b053ac5b2f774f2271f7fbbfd2ea475899f7a55135c5e172bc54b9388f108'; do
|
||||
read -r name expected <<<"$reviewed_value"
|
||||
[[ "${INSTALLER_PROVENANCE[$name]:-}" == "$expected" ]] \
|
||||
|| fail "$name does not match the reviewed release"
|
||||
done
|
||||
for key_spec in \
|
||||
'terra44 TERRA_FINGERPRINT' \
|
||||
'claude-code CLAUDE_CODE_FINGERPRINT' \
|
||||
@@ -286,11 +324,67 @@ cp "$repo_dir"/setup/provenance/keys/*.asc "$installer_fixture/setup/provenance/
|
||||
sed '/^# --- The server path/,$d' "$repo_dir/setup/scripts/install-packages" \
|
||||
> "$installer_fixture/setup/scripts/install-packages"
|
||||
|
||||
artifact_root="$test_tmp/runtime-artifacts"
|
||||
mkdir -p "$artifact_root/build"
|
||||
for arch_spec in \
|
||||
'x86_64 x64 x64' \
|
||||
'aarch64 arm64 aarch64'; do
|
||||
read -r machine node_arch bun_arch <<<"$arch_spec"
|
||||
node_top="node-v24.20.0-linux-$node_arch"
|
||||
mkdir -p "$artifact_root/build/$node_top/bin"
|
||||
printf '#!/usr/bin/env bash\nprintf "v24.20.0\\n"\n' \
|
||||
> "$artifact_root/build/$node_top/bin/node"
|
||||
chmod +x "$artifact_root/build/$node_top/bin/node"
|
||||
tar -C "$artifact_root/build" -cJf "$artifact_root/node-$machine.tar.xz" "$node_top"
|
||||
rm -rf -- "$artifact_root/build/$node_top"
|
||||
|
||||
bun_top="bun-linux-$bun_arch"
|
||||
mkdir -p "$artifact_root/build/$bun_top"
|
||||
printf '#!/usr/bin/env bash\nprintf "1.4.0\\n"\n' \
|
||||
> "$artifact_root/build/$bun_top/bun"
|
||||
chmod +x "$artifact_root/build/$bun_top/bun"
|
||||
(cd "$artifact_root/build" && zip -q "$artifact_root/bun-$machine.zip" "$bun_top/bun")
|
||||
rm -rf -- "$artifact_root/build/$bun_top"
|
||||
|
||||
codex_name="codex-$machine-unknown-linux-musl"
|
||||
printf '#!/usr/bin/env bash\nprintf "codex-cli 0.150.1\\n"\n' \
|
||||
> "$artifact_root/build/$codex_name"
|
||||
chmod +x "$artifact_root/build/$codex_name"
|
||||
tar -C "$artifact_root/build" -czf "$artifact_root/codex-$machine.tar.gz" "$codex_name"
|
||||
rm -f -- "$artifact_root/build/$codex_name"
|
||||
done
|
||||
mkdir -p "$artifact_root/build/wrong-node/bin" "$artifact_root/build/wrong-codex"
|
||||
printf '#!/usr/bin/env bash\nprintf "v24.20.0\\n"\n' > "$artifact_root/build/wrong-node/bin/node"
|
||||
printf '#!/usr/bin/env bash\nprintf "codex-cli 0.150.1\\n"\n' > "$artifact_root/build/wrong-codex/codex"
|
||||
chmod +x "$artifact_root/build/wrong-node/bin/node" "$artifact_root/build/wrong-codex/codex"
|
||||
tar -C "$artifact_root/build" -cJf "$artifact_root/node-bad.tar.xz" wrong-node
|
||||
tar -C "$artifact_root/build" -czf "$artifact_root/codex-bad.tar.gz" wrong-codex/codex
|
||||
mkdir -p "$artifact_root/build/bun-linux-x64"
|
||||
printf '#!/usr/bin/env bash\nprintf "1.4.0\\n"\n' > "$artifact_root/build/bun-linux-x64/bun"
|
||||
printf 'unexpected\n' > "$artifact_root/build/bun-linux-x64/extra"
|
||||
chmod +x "$artifact_root/build/bun-linux-x64/bun"
|
||||
(cd "$artifact_root/build" && zip -q "$artifact_root/bun-bad.zip" \
|
||||
bun-linux-x64/bun bun-linux-x64/extra)
|
||||
printf 'reviewed rustdesk fixture\n' > "$artifact_root/rustdesk.rpm"
|
||||
rm -rf -- "$artifact_root/build"
|
||||
|
||||
make_stub_commands() {
|
||||
local case_root="$1"
|
||||
mkdir -p "$case_root/bin" "$case_root/home" "$case_root/tmp" "$case_root/etc/yum.repos.d" \
|
||||
mkdir -p "$case_root/bin" "$case_root/home" "$case_root/tmp" \
|
||||
"$case_root/etc/profile.d" "$case_root/etc/yum.repos.d" \
|
||||
"$case_root/etc/pki/rpm-gpg" "$case_root/flatpak-repo"
|
||||
|
||||
cat > "$case_root/etc/profile.d/nvm.sh" <<'STUB'
|
||||
nvm() {
|
||||
printf 'nvm:%s\n' "$*" >> "$COMMAND_LOG"
|
||||
}
|
||||
STUB
|
||||
|
||||
cat > "$case_root/bin/uname" <<'STUB'
|
||||
#!/usr/bin/env bash
|
||||
printf '%s\n' "${STUB_ARCH:-x86_64}"
|
||||
STUB
|
||||
|
||||
cat > "$case_root/bin/rpm" <<'STUB'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
@@ -298,10 +392,15 @@ if [[ "$*" == '-E %fedora' ]]; then
|
||||
printf 'rpm:release\n' >> "$COMMAND_LOG"
|
||||
printf '%s\n' "${STUB_FEDORA_RELEASE:-44}"
|
||||
elif [[ "${1:-}" == -q ]]; then
|
||||
printf 'rpm:query:%s\n' "${2:-}" >> "$COMMAND_LOG"
|
||||
case "${2:-}" in
|
||||
package="${!#}"
|
||||
printf 'rpm:query:%s\n' "$package" >> "$COMMAND_LOG"
|
||||
case "$package" in
|
||||
terra-release) [[ "${STUB_TERRA_INSTALLED:-0}" == 1 ]] ;;
|
||||
claude-desktop-extra) [[ "${STUB_CLAUDE_DESKTOP_INSTALLED:-0}" == 1 ]] ;;
|
||||
rustdesk)
|
||||
[[ -n "${STUB_RUSTDESK_VERSION:-}" ]] || exit 1
|
||||
[[ "$*" != *--queryformat* ]] || printf '%s' "$STUB_RUSTDESK_VERSION"
|
||||
;;
|
||||
*) exit 1 ;;
|
||||
esac
|
||||
else
|
||||
@@ -328,12 +427,60 @@ output_name="$(basename "$output")"
|
||||
output_name="${output_name#.}"
|
||||
output_name="${output_name%.part.*}"
|
||||
printf 'curl:%s:max=%s:output=%s\n' "$url" "$max_filesize" "$output_name" >> "$COMMAND_LOG"
|
||||
if [[ "${STUB_DOWNLOAD_INTERRUPT:-}" == 1 ]]; then
|
||||
printf 'partial' > "$output"
|
||||
exit 42
|
||||
fi
|
||||
case "$url" in
|
||||
*rpmfusion-free*) cp "$SIGNED_RPM" "$output" ;;
|
||||
*rpmfusion-nonfree*) cp "$SIGNED_RPM" "$output" ;;
|
||||
*flathub.flatpakrepo) cp "$FLATHUB_DESCRIPTOR" "$output" ;;
|
||||
*node-v24.20.0-linux-x64.tar.xz)
|
||||
cp "$ARTIFACT_ROOT/${STUB_BAD_LAYOUT:+node-bad.tar.xz}" "$output" 2>/dev/null \
|
||||
|| cp "$ARTIFACT_ROOT/node-x86_64.tar.xz" "$output"
|
||||
;;
|
||||
*node-v24.20.0-linux-arm64.tar.xz) cp "$ARTIFACT_ROOT/node-aarch64.tar.xz" "$output" ;;
|
||||
*bun-linux-x64.zip)
|
||||
cp "$ARTIFACT_ROOT/${STUB_BAD_LAYOUT:+bun-bad.zip}" "$output" 2>/dev/null \
|
||||
|| cp "$ARTIFACT_ROOT/bun-x86_64.zip" "$output"
|
||||
;;
|
||||
*bun-linux-aarch64.zip) cp "$ARTIFACT_ROOT/bun-aarch64.zip" "$output" ;;
|
||||
*codex-package-x86_64-unknown-linux-musl.tar.gz)
|
||||
cp "$ARTIFACT_ROOT/${STUB_BAD_LAYOUT:+codex-bad.tar.gz}" "$output" 2>/dev/null \
|
||||
|| cp "$ARTIFACT_ROOT/codex-x86_64.tar.gz" "$output"
|
||||
;;
|
||||
*codex-package-aarch64-unknown-linux-musl.tar.gz) cp "$ARTIFACT_ROOT/codex-aarch64.tar.gz" "$output" ;;
|
||||
*rustdesk-1.4.9-0.x86_64.rpm) cp "$ARTIFACT_ROOT/rustdesk.rpm" "$output" ;;
|
||||
*) exit 66 ;;
|
||||
esac
|
||||
STUB
|
||||
|
||||
cat > "$case_root/bin/sha256sum" <<'STUB'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
file="${!#}"
|
||||
if [[ "${STUB_DIGEST_MISMATCH:-}" == 1 ]]; then
|
||||
printf '%064d %s\n' 0 "$file"
|
||||
exit 0
|
||||
fi
|
||||
for spec in \
|
||||
'node-x86_64.tar.xz 2f2c0da162318f0de47665410c7c8c2ed3d36c8f3105de4bbc61176c70a7cbf2' \
|
||||
'node-aarch64.tar.xz 5f4ddab610c1ab2016b3c227cebdbf6d9495161487e4739c7b90090595f465f7' \
|
||||
'node-bad.tar.xz 2f2c0da162318f0de47665410c7c8c2ed3d36c8f3105de4bbc61176c70a7cbf2' \
|
||||
'bun-x86_64.zip 2d03fb5fb83ac8b567aca0a281b2ce1a1a19d488f56c2968d88c3f25e92fe452' \
|
||||
'bun-aarch64.zip 4b1a332ee861983eb93bcfe6f770fff94e3e31b2c388bdaea3c8ed35e58eed0e' \
|
||||
'bun-bad.zip 2d03fb5fb83ac8b567aca0a281b2ce1a1a19d488f56c2968d88c3f25e92fe452' \
|
||||
'codex-x86_64.tar.gz 00aba704f029f6dc0d948be407a756e0c97cc840132fd691353b2c6b0a505b17' \
|
||||
'codex-aarch64.tar.gz 1ecac3f87823efb98153233b076ea3d6e34a7a8cebe43c5285dc5f79e1514639' \
|
||||
'codex-bad.tar.gz 00aba704f029f6dc0d948be407a756e0c97cc840132fd691353b2c6b0a505b17' \
|
||||
'rustdesk.rpm eb1b053ac5b2f774f2271f7fbbfd2ea475899f7a55135c5e172bc54b9388f108'; do
|
||||
read -r fixture digest <<<"$spec"
|
||||
if cmp -s "$file" "$ARTIFACT_ROOT/$fixture"; then
|
||||
printf '%s %s\n' "$digest" "$file"
|
||||
exit 0
|
||||
fi
|
||||
done
|
||||
/usr/bin/sha256sum "$@"
|
||||
STUB
|
||||
|
||||
cat > "$case_root/bin/gpg" <<'STUB'
|
||||
@@ -418,6 +565,7 @@ for argument in "$@"; do
|
||||
case "$(basename "$argument")" in
|
||||
rpmfusion-free-release.rpm) logged+=(RPMFUSION_FREE) ;;
|
||||
rpmfusion-nonfree-release.rpm) logged+=(RPMFUSION_NONFREE) ;;
|
||||
rustdesk.rpm) logged+=(RUSTDESK_LOCAL) ;;
|
||||
flathub-key.asc) logged+=(FLATHUB_KEY) ;;
|
||||
*) logged+=("$argument") ;;
|
||||
esac
|
||||
@@ -588,6 +736,73 @@ run_installer_function() {
|
||||
: > "$case_root/commands.log"
|
||||
printf '0\n' > "$case_root/install-counter"
|
||||
printf 'preserved\n' > "$case_root/flatpak-state"
|
||||
: > "$case_root/softly-failed"
|
||||
case "${STUB_SEED_OLD:-}" in
|
||||
Node)
|
||||
mkdir -p "$case_root/home/.nvm/versions/node/v23.0.0/bin"
|
||||
printf '#!/usr/bin/env bash\nprintf "v23.0.0\\n"\n' \
|
||||
> "$case_root/home/.nvm/versions/node/v23.0.0/bin/node"
|
||||
chmod +x "$case_root/home/.nvm/versions/node/v23.0.0/bin/node"
|
||||
;;
|
||||
Bun)
|
||||
mkdir -p "$case_root/home/.bun/versions/1.3.0/bin" "$case_root/home/.bun/bin"
|
||||
printf '#!/usr/bin/env bash\nprintf "1.3.0\\n"\n' \
|
||||
> "$case_root/home/.bun/versions/1.3.0/bin/bun"
|
||||
chmod +x "$case_root/home/.bun/versions/1.3.0/bin/bun"
|
||||
ln -s "$case_root/home/.bun/versions/1.3.0/bin/bun" "$case_root/home/.bun/bin/bun"
|
||||
;;
|
||||
Codex)
|
||||
mkdir -p "$case_root/home/.local/lib/panama/codex/0.149.0" "$case_root/home/.local/bin"
|
||||
printf '#!/usr/bin/env bash\nprintf "codex-cli 0.149.0\\n"\n' \
|
||||
> "$case_root/home/.local/lib/panama/codex/0.149.0/codex"
|
||||
chmod +x "$case_root/home/.local/lib/panama/codex/0.149.0/codex"
|
||||
ln -s "$case_root/home/.local/lib/panama/codex/0.149.0/codex" \
|
||||
"$case_root/home/.local/bin/codex"
|
||||
;;
|
||||
esac
|
||||
case "${STUB_SEED_EXACT:-}" in
|
||||
Node)
|
||||
mkdir -p "$case_root/home/.nvm/versions/node/v24.20.0/bin"
|
||||
printf '#!/usr/bin/env bash\nprintf "v24.20.0\\n"\n' \
|
||||
> "$case_root/home/.nvm/versions/node/v24.20.0/bin/node"
|
||||
chmod +x "$case_root/home/.nvm/versions/node/v24.20.0/bin/node"
|
||||
;;
|
||||
Bun)
|
||||
mkdir -p "$case_root/home/.bun/versions/1.4.0/bin" "$case_root/home/.bun/bin"
|
||||
printf '#!/usr/bin/env bash\nprintf "1.4.0\\n"\n' \
|
||||
> "$case_root/home/.bun/versions/1.4.0/bin/bun"
|
||||
chmod +x "$case_root/home/.bun/versions/1.4.0/bin/bun"
|
||||
ln -s "$case_root/home/.bun/versions/1.4.0/bin/bun" "$case_root/home/.bun/bin/bun"
|
||||
;;
|
||||
Codex)
|
||||
mkdir -p "$case_root/home/.local/lib/panama/codex/0.150.1" "$case_root/home/.local/bin"
|
||||
printf '#!/usr/bin/env bash\nprintf "codex-cli 0.150.1\\n"\n' \
|
||||
> "$case_root/home/.local/lib/panama/codex/0.150.1/codex"
|
||||
chmod +x "$case_root/home/.local/lib/panama/codex/0.150.1/codex"
|
||||
ln -s "$case_root/home/.local/lib/panama/codex/0.150.1/codex" \
|
||||
"$case_root/home/.local/bin/codex"
|
||||
;;
|
||||
esac
|
||||
case "${STUB_SEED_COLLISION:-}" in
|
||||
Node)
|
||||
mkdir -p "$case_root/home/.nvm/versions/node/v24.20.0/bin"
|
||||
printf '#!/usr/bin/env bash\nprintf "v0.0.0\\n"\n' \
|
||||
> "$case_root/home/.nvm/versions/node/v24.20.0/bin/node"
|
||||
chmod +x "$case_root/home/.nvm/versions/node/v24.20.0/bin/node"
|
||||
;;
|
||||
Bun)
|
||||
mkdir -p "$case_root/home/.bun/versions/1.4.0/bin"
|
||||
printf '#!/usr/bin/env bash\nprintf "0.0.0\\n"\n' \
|
||||
> "$case_root/home/.bun/versions/1.4.0/bin/bun"
|
||||
chmod +x "$case_root/home/.bun/versions/1.4.0/bin/bun"
|
||||
;;
|
||||
Codex)
|
||||
mkdir -p "$case_root/home/.local/lib/panama/codex/0.150.1"
|
||||
printf '#!/usr/bin/env bash\nprintf "codex-cli 10.150.10\\n"\n' \
|
||||
> "$case_root/home/.local/lib/panama/codex/0.150.1/codex"
|
||||
chmod +x "$case_root/home/.local/lib/panama/codex/0.150.1/codex"
|
||||
;;
|
||||
esac
|
||||
write_flathub_descriptor "$case_root/flathub.flatpakrepo" \
|
||||
"${STUB_FLATHUB_KEY_FILE:-$installer_fixture/setup/provenance/keys/flathub.asc}" \
|
||||
"${STUB_FLATHUB_VERIFY_LINE:-}" "${STUB_FLATHUB_URL:-https://dl.flathub.org/repo/}"
|
||||
@@ -685,6 +900,8 @@ run_installer_function() {
|
||||
esac
|
||||
|
||||
COMMAND_LOG="$case_root/commands.log" \
|
||||
SOFT_LOG="$case_root/softly-failed" \
|
||||
ARTIFACT_ROOT="$artifact_root" \
|
||||
FIXTURE_ROOT="$installer_fixture" \
|
||||
REVIEWED_KEYS="$repo_dir/setup/provenance/keys" \
|
||||
SIGNED_RPM="$test_tmp/signed-fixture.rpm" \
|
||||
@@ -695,10 +912,11 @@ run_installer_function() {
|
||||
STUB_INSTALL_COUNTER="$case_root/install-counter" \
|
||||
LC_ALL="${STUB_CALLER_LOCALE:-C}" \
|
||||
HOME="$case_root/home" \
|
||||
NVM_DIR="$case_root/home/.nvm" \
|
||||
TMPDIR="$case_root/tmp" \
|
||||
PANAMA_PATH="$installer_fixture" \
|
||||
PATH="$case_root/bin:/usr/bin:/bin" \
|
||||
bash -c 'source "$PANAMA_PATH/setup/scripts/install-packages"; PANAMA_SYSTEM_ETC="$STUB_ETC"; PANAMA_SYSTEM_FLATPAK_REPO="$STUB_FLATPAK_REPO"; declare -F "$1" >/dev/null; "$1"' \
|
||||
bash -c 'source "$PANAMA_PATH/setup/scripts/install-packages"; PANAMA_SYSTEM_ETC="$STUB_ETC"; PANAMA_SYSTEM_FLATPAK_REPO="$STUB_FLATPAK_REPO"; declare -F "$1" >/dev/null; status=0; "$1" || status=$?; (( ${#softly_failed[@]} == 0 )) || printf "%s\n" "${softly_failed[@]}" > "$SOFT_LOG"; exit "$status"' \
|
||||
bash "$function_name" > "$case_root/output" 2>&1
|
||||
}
|
||||
|
||||
@@ -712,6 +930,222 @@ assert_log() {
|
||||
}
|
||||
}
|
||||
|
||||
assert_soft_failure() {
|
||||
local name="$1" component="$2"
|
||||
[[ "$(<"$test_tmp/cases/$name/softly-failed")" == "$component" ]] \
|
||||
|| fail "$name did not record exactly one $component soft failure"
|
||||
}
|
||||
|
||||
assert_no_download() {
|
||||
local name="$1"
|
||||
[[ "$(<"$test_tmp/cases/$name/commands.log")" != *'curl:'* ]] \
|
||||
|| fail "$name reached curl"
|
||||
}
|
||||
|
||||
assert_no_runtime_staging() {
|
||||
local name="$1"
|
||||
[[ -z "$(find "$test_tmp/cases/$name/home" "$test_tmp/cases/$name/tmp" \
|
||||
\( -name '*.part.*' -o -name '*.stage.*' -o -name '*.link.*' \) -print -quit)" ]] \
|
||||
|| fail "$name left private runtime staging behind"
|
||||
}
|
||||
|
||||
assert_old_runtime_preserved() {
|
||||
local name="$1" component="$2" home="$test_tmp/cases/$name/home"
|
||||
case "$component" in
|
||||
Node)
|
||||
[[ "$($home/.nvm/versions/node/v23.0.0/bin/node --version)" == v23.0.0 ]] \
|
||||
|| fail "$name changed the known-good Node"
|
||||
;;
|
||||
Bun)
|
||||
[[ "$(readlink "$home/.bun/bin/bun")" == \
|
||||
"$home/.bun/versions/1.3.0/bin/bun" ]] \
|
||||
|| fail "$name changed the active Bun link"
|
||||
[[ "$($home/.bun/bin/bun --version)" == 1.3.0 ]] \
|
||||
|| fail "$name changed the known-good Bun"
|
||||
;;
|
||||
Codex)
|
||||
[[ "$(readlink "$home/.local/bin/codex")" == \
|
||||
"$home/.local/lib/panama/codex/0.149.0/codex" ]] \
|
||||
|| fail "$name changed the active Codex link"
|
||||
[[ "$($home/.local/bin/codex --version)" == 'codex-cli 0.149.0' ]] \
|
||||
|| fail "$name changed the known-good Codex"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Each supported architecture selects its own reviewed URL, digest and archive
|
||||
# layout. Successful activation leaves no private download or extraction tree.
|
||||
for runtime_case in \
|
||||
'node-x86_64 x86_64 install_node Node https://nodejs.org/dist/v24.20.0/node-v24.20.0-linux-x64.tar.xz 67108864' \
|
||||
'node-aarch64 aarch64 install_node Node https://nodejs.org/dist/v24.20.0/node-v24.20.0-linux-arm64.tar.xz 67108864' \
|
||||
'bun-x86_64 x86_64 install_bun Bun https://github.com/oven-sh/bun/releases/download/bun-v1.4.0/bun-linux-x64.zip 67108864' \
|
||||
'bun-aarch64 aarch64 install_bun Bun https://github.com/oven-sh/bun/releases/download/bun-v1.4.0/bun-linux-aarch64.zip 67108864' \
|
||||
'codex-x86_64 x86_64 install_codex Codex https://github.com/openai/codex/releases/download/rust-v0.150.1/codex-package-x86_64-unknown-linux-musl.tar.gz 134217728' \
|
||||
'codex-aarch64 aarch64 install_codex Codex https://github.com/openai/codex/releases/download/rust-v0.150.1/codex-package-aarch64-unknown-linux-musl.tar.gz 134217728'; do
|
||||
read -r name arch function_name component url max_bytes <<<"$runtime_case"
|
||||
reset_installer_fixture
|
||||
if ! STUB_ARCH="$arch" run_installer_function "$name" "$function_name"; then
|
||||
tail -n 120 "$test_tmp/cases/$name/output" >&2
|
||||
fail "expected successful $name activation"
|
||||
fi
|
||||
grep -qFx "curl:$url:max=$max_bytes:output=artifact" \
|
||||
"$test_tmp/cases/$name/commands.log" \
|
||||
|| { sed -n '1,80p' "$test_tmp/cases/$name/output" >&2; sed -n '1,80p' "$test_tmp/cases/$name/commands.log" >&2; fail "$name did not select its reviewed artifact"; }
|
||||
[[ ! -s "$test_tmp/cases/$name/softly-failed" ]] \
|
||||
|| fail "$name recorded a soft failure after successful activation"
|
||||
assert_no_runtime_staging "$name"
|
||||
done
|
||||
|
||||
[[ "$($test_tmp/cases/node-x86_64/home/.nvm/versions/node/v24.20.0/bin/node --version)" \
|
||||
== v24.20.0 ]] || fail 'x86_64 Node activation has the wrong version'
|
||||
grep -qFx 'nvm:alias default 24.20.0' "$test_tmp/cases/node-x86_64/commands.log" \
|
||||
|| fail 'Node did not set the exact nvm default alias'
|
||||
[[ "$($test_tmp/cases/bun-x86_64/home/.bun/bin/bun --version)" == 1.4.0 ]] \
|
||||
|| fail 'x86_64 Bun activation has the wrong version'
|
||||
[[ "$(readlink "$test_tmp/cases/bun-x86_64/home/.bun/bin/bun")" == \
|
||||
"$test_tmp/cases/bun-x86_64/home/.bun/versions/1.4.0/bin/bun" ]] \
|
||||
|| fail 'Bun did not atomically activate the reviewed version path'
|
||||
[[ "$($test_tmp/cases/codex-x86_64/home/.local/bin/codex --version)" == \
|
||||
'codex-cli 0.150.1' ]] || fail 'x86_64 Codex activation has the wrong version'
|
||||
[[ "$(readlink "$test_tmp/cases/codex-x86_64/home/.local/bin/codex")" == \
|
||||
"$test_tmp/cases/codex-x86_64/home/.local/lib/panama/codex/0.150.1/codex" ]] \
|
||||
|| fail 'Codex did not atomically activate the reviewed version path'
|
||||
|
||||
# Unsupported CPUs stop before curl. RustDesk's reviewed RPM is x86_64-only,
|
||||
# so aarch64 is also an intentional, recorded soft failure without a download.
|
||||
for unsupported_case in \
|
||||
'node-unsupported install_node Node riscv64' \
|
||||
'bun-unsupported install_bun Bun riscv64' \
|
||||
'codex-unsupported install_codex Codex riscv64' \
|
||||
'rustdesk-unsupported install_rustdesk RustDesk riscv64' \
|
||||
'rustdesk-aarch64 install_rustdesk RustDesk aarch64'; do
|
||||
read -r name function_name component arch <<<"$unsupported_case"
|
||||
reset_installer_fixture
|
||||
STUB_ARCH="$arch" expect_failure run_installer_function "$name" "$function_name"
|
||||
assert_no_download "$name"
|
||||
assert_soft_failure "$name" "$component"
|
||||
done
|
||||
|
||||
# A bad digest or interrupted transfer cannot replace the previously active
|
||||
# tool and cannot leave reusable bytes behind.
|
||||
for failure_mode in digest interrupted; do
|
||||
for component_spec in \
|
||||
'Node install_node' \
|
||||
'Bun install_bun' \
|
||||
'Codex install_codex'; do
|
||||
read -r component function_name <<<"$component_spec"
|
||||
name="${component,,}-$failure_mode"
|
||||
reset_installer_fixture
|
||||
if [[ "$failure_mode" == digest ]]; then
|
||||
STUB_SEED_OLD="$component" STUB_DIGEST_MISMATCH=1 \
|
||||
expect_failure run_installer_function "$name" "$function_name"
|
||||
else
|
||||
STUB_SEED_OLD="$component" STUB_DOWNLOAD_INTERRUPT=1 \
|
||||
expect_failure run_installer_function "$name" "$function_name"
|
||||
fi
|
||||
assert_soft_failure "$name" "$component"
|
||||
assert_old_runtime_preserved "$name" "$component"
|
||||
assert_no_runtime_staging "$name"
|
||||
done
|
||||
|
||||
name="rustdesk-$failure_mode"
|
||||
reset_installer_fixture
|
||||
if [[ "$failure_mode" == digest ]]; then
|
||||
STUB_RUSTDESK_VERSION=1.4.8 STUB_DIGEST_MISMATCH=1 \
|
||||
expect_failure run_installer_function "$name" install_rustdesk
|
||||
else
|
||||
STUB_RUSTDESK_VERSION=1.4.8 STUB_DOWNLOAD_INTERRUPT=1 \
|
||||
expect_failure run_installer_function "$name" install_rustdesk
|
||||
fi
|
||||
assert_soft_failure "$name" RustDesk
|
||||
[[ "$(<"$test_tmp/cases/$name/commands.log")" != *'sudo:'* ]] \
|
||||
|| fail "$name reached DNF with an unverified RPM"
|
||||
assert_no_runtime_staging "$name"
|
||||
done
|
||||
|
||||
# Successful updates keep the old version directory and switch only the active
|
||||
# symlink after the replacement binary has passed its version check.
|
||||
for component_spec in 'Bun install_bun .bun/bin/bun .bun/versions/1.4.0/bin/bun' \
|
||||
'Codex install_codex .local/bin/codex .local/lib/panama/codex/0.150.1/codex'; do
|
||||
read -r component function_name active_relative target_relative <<<"$component_spec"
|
||||
name="${component,,}-atomic-update"
|
||||
reset_installer_fixture
|
||||
STUB_SEED_OLD="$component" expect_success run_installer_function "$name" "$function_name"
|
||||
[[ "$(readlink "$test_tmp/cases/$name/home/$active_relative")" == \
|
||||
"$test_tmp/cases/$name/home/$target_relative" ]] \
|
||||
|| fail "$name did not atomically replace the active symlink"
|
||||
if [[ "$component" == Bun ]]; then
|
||||
[[ "$($test_tmp/cases/$name/home/.bun/versions/1.3.0/bin/bun --version)" == 1.3.0 ]] \
|
||||
|| fail "$name removed the prior version directory"
|
||||
else
|
||||
[[ "$($test_tmp/cases/$name/home/.local/lib/panama/codex/0.149.0/codex --version)" \
|
||||
== 'codex-cli 0.149.0' ]] || fail "$name removed the prior version directory"
|
||||
fi
|
||||
done
|
||||
|
||||
# A reviewed digest does not excuse a malformed archive. Reject the wrong top
|
||||
# level or any extra member before a version path or active link appears.
|
||||
for layout_case in \
|
||||
'node-layout install_node Node .nvm/versions/node/v24.20.0' \
|
||||
'bun-layout install_bun Bun .bun/versions/1.4.0' \
|
||||
'codex-layout install_codex Codex .local/lib/panama/codex/0.150.1'; do
|
||||
read -r name function_name component relative_target <<<"$layout_case"
|
||||
reset_installer_fixture
|
||||
STUB_BAD_LAYOUT=1 expect_failure run_installer_function "$name" "$function_name"
|
||||
assert_soft_failure "$name" "$component"
|
||||
[[ ! -e "$test_tmp/cases/$name/home/$relative_target" ]] \
|
||||
|| fail "$name activated an archive with an unexpected layout"
|
||||
assert_no_runtime_staging "$name"
|
||||
done
|
||||
|
||||
# A valid collision is a no-download no-op. An invalid collision is preserved
|
||||
# and reported instead of being deleted and recreated.
|
||||
for collision_mode in exact collision; do
|
||||
for component_spec in \
|
||||
'Node install_node' \
|
||||
'Bun install_bun' \
|
||||
'Codex install_codex'; do
|
||||
read -r component function_name <<<"$component_spec"
|
||||
name="${component,,}-$collision_mode"
|
||||
reset_installer_fixture
|
||||
if [[ "$collision_mode" == exact ]]; then
|
||||
STUB_SEED_EXACT="$component" expect_success \
|
||||
run_installer_function "$name" "$function_name"
|
||||
[[ ! -s "$test_tmp/cases/$name/softly-failed" ]] \
|
||||
|| fail "$name reported a failure for the exact installed version"
|
||||
else
|
||||
STUB_SEED_COLLISION="$component" expect_failure \
|
||||
run_installer_function "$name" "$function_name"
|
||||
assert_soft_failure "$name" "$component"
|
||||
fi
|
||||
assert_no_download "$name"
|
||||
done
|
||||
done
|
||||
|
||||
reset_installer_fixture
|
||||
STUB_ARCH=x86_64 STUB_RUSTDESK_VERSION=1.4.8 \
|
||||
expect_success run_installer_function rustdesk-x86_64 install_rustdesk
|
||||
assert_log rustdesk-x86_64 "$(cat <<'EXPECTED'
|
||||
rpm:query:rustdesk
|
||||
curl:https://github.com/rustdesk/rustdesk/releases/download/1.4.9/rustdesk-1.4.9-0.x86_64.rpm:max=134217728:output=rustdesk.rpm
|
||||
sudo:dnf install -y --setopt=localpkg_gpgcheck=1 RUSTDESK_LOCAL
|
||||
EXPECTED
|
||||
)"
|
||||
assert_no_runtime_staging rustdesk-x86_64
|
||||
|
||||
reset_installer_fixture
|
||||
STUB_ARCH=x86_64 STUB_RUSTDESK_VERSION=1.4.9 \
|
||||
expect_success run_installer_function rustdesk-exact install_rustdesk
|
||||
assert_log rustdesk-exact 'rpm:query:rustdesk'
|
||||
|
||||
reset_installer_fixture
|
||||
expect_success run_installer_function pnpm install_pnpm
|
||||
assert_log pnpm 'sudo:dnf install -y pnpm'
|
||||
|
||||
reset_installer_fixture
|
||||
STUB_DNF_FAIL_MATCH=pnpm expect_failure run_installer_function pnpm-failure install_pnpm
|
||||
assert_soft_failure pnpm-failure pnpm
|
||||
|
||||
reset_installer_fixture
|
||||
expect_success run_installer_function rpmfusion install_rpmfusion_repositories
|
||||
assert_log rpmfusion "$(cat <<'EXPECTED'
|
||||
@@ -999,6 +1433,9 @@ for function_name in install_rpmfusion_repositories install_terra_repository \
|
||||
name="wrong-fedora-${function_name}"
|
||||
STUB_FEDORA_RELEASE=45 expect_failure run_installer_function "$name" "$function_name"
|
||||
assert_log "$name" 'rpm:release'
|
||||
if [[ "$function_name" == install_claude_code ]]; then
|
||||
assert_soft_failure "$name" 'Claude Code'
|
||||
fi
|
||||
done
|
||||
|
||||
reset_installer_fixture
|
||||
|
||||
Reference in New Issue
Block a user