226 lines
8.7 KiB
Bash
Executable File
226 lines
8.7 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
SCRIPT="$ROOT_DIR/scripts/release/updater-manifest.mjs"
|
|
TMP_DIR="$(mktemp -d)"
|
|
trap 'rm -rf "$TMP_DIR"' EXIT
|
|
|
|
TEST_PUBLIC_KEY="$(node - <<'NODE'
|
|
const value = `untrusted comment: minisign public key E7620F1842B4E81F
|
|
RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3
|
|
`;
|
|
process.stdout.write(Buffer.from(value).toString("base64"));
|
|
NODE
|
|
)"
|
|
TEST_SIGNATURE="$(node - <<'NODE'
|
|
const value = `untrusted comment: signature from minisign secret key
|
|
RUQf6LRCGA9i559r3g7V1qNyJDApGip8MfqcadIgT9CuhV3EMhHoN1mGTkUidF/z7SrlQgXdy8ofjb7bNJJylDOocrCo8KLzZwo=
|
|
trusted comment: timestamp:1556193335\tfile:test
|
|
y/rUw2y8/hOUYjZU71eHp/Wo1KZ40fGy2VJEDl34XMJM+TX48Ss/17u3IvIfbVR1FkZZSNCisQbuQY+bHwhEBg==`;
|
|
process.stdout.write(Buffer.from(value).toString("base64"));
|
|
NODE
|
|
)"
|
|
WRONG_PUBLIC_KEY="$(node - <<'NODE'
|
|
const value = `untrusted comment: wrong updater public key
|
|
RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO4
|
|
`;
|
|
process.stdout.write(Buffer.from(value).toString("base64"));
|
|
NODE
|
|
)"
|
|
|
|
fail() {
|
|
echo "FAIL: $*" >&2
|
|
exit 1
|
|
}
|
|
|
|
pass() {
|
|
echo "PASS: $*"
|
|
}
|
|
|
|
write_artifact() {
|
|
local assets_dir="$1"
|
|
local filename="$2"
|
|
printf 'test' > "$assets_dir/$filename"
|
|
printf '%s\n' "$TEST_SIGNATURE" > "$assets_dir/$filename.sig"
|
|
}
|
|
|
|
write_complete_assets() {
|
|
local assets_dir="$1"
|
|
mkdir -p "$assets_dir"
|
|
write_artifact "$assets_dir" "kandev-desktop-macos-arm64-Kandev.app.tar.gz"
|
|
write_artifact "$assets_dir" "kandev-desktop-macos-x64-Kandev.app.tar.gz"
|
|
write_artifact "$assets_dir" "kandev-desktop-linux-arm64-Kandev.AppImage.tar.gz"
|
|
write_artifact "$assets_dir" "kandev-desktop-linux-x64-Kandev.AppImage.tar.gz"
|
|
write_artifact "$assets_dir" "kandev-desktop-windows-x64-Kandev-setup.nsis.zip"
|
|
}
|
|
|
|
generate_manifest() {
|
|
local assets_dir="$1"
|
|
shift
|
|
node "$SCRIPT" generate \
|
|
--assets-dir "$assets_dir" \
|
|
--output "$assets_dir/latest.json" \
|
|
--version "1.2.3" \
|
|
--tag "v1.2.3" \
|
|
--repository "kdlbs/kandev" \
|
|
--notes-file "$assets_dir/notes.md" \
|
|
--pub-date "2026-07-15T12:00:00Z" \
|
|
--public-key "${UPDATER_TEST_PUBLIC_KEY:-$TEST_PUBLIC_KEY}" \
|
|
"$@"
|
|
}
|
|
|
|
complete_assets="$TMP_DIR/complete"
|
|
write_complete_assets "$complete_assets"
|
|
printf 'Release notes\n' > "$complete_assets/notes.md"
|
|
for file in "$complete_assets"/kandev-desktop-*; do
|
|
"$ROOT_DIR/scripts/release/write-sha256.sh" "$file" "$file.sha256"
|
|
done
|
|
for platform in macos-arm64 macos-x64 linux-arm64 linux-x64 windows-x64; do
|
|
"$ROOT_DIR/scripts/release/verify-desktop-assets.sh" "$complete_assets" "$platform" >/dev/null
|
|
done
|
|
pass "desktop asset verification retains updater bundles and signatures"
|
|
|
|
generate_manifest "$complete_assets"
|
|
node "$SCRIPT" verify \
|
|
--assets-dir "$complete_assets" \
|
|
--manifest "$complete_assets/latest.json" \
|
|
--version "1.2.3" \
|
|
--tag "v1.2.3" \
|
|
--repository "kdlbs/kandev" \
|
|
--public-key "$TEST_PUBLIC_KEY"
|
|
node - "$complete_assets/latest.json" <<'NODE'
|
|
const fs = require("node:fs");
|
|
const manifest = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
|
|
const expected = [
|
|
"darwin-aarch64",
|
|
"darwin-x86_64",
|
|
"linux-aarch64",
|
|
"linux-x86_64",
|
|
"windows-x86_64",
|
|
];
|
|
if (JSON.stringify(Object.keys(manifest.platforms).sort()) !== JSON.stringify(expected)) {
|
|
throw new Error(`unexpected updater targets: ${Object.keys(manifest.platforms).join(", ")}`);
|
|
}
|
|
if (manifest.notes !== "Release notes\n" || manifest.pub_date !== "2026-07-15T12:00:00Z") {
|
|
throw new Error("manifest metadata was not preserved");
|
|
}
|
|
NODE
|
|
pass "updater manifest covers every desktop target"
|
|
|
|
missing_signature="$TMP_DIR/missing-signature"
|
|
cp -R "$complete_assets" "$missing_signature"
|
|
rm "$missing_signature/kandev-desktop-linux-arm64-Kandev.AppImage.tar.gz.sig" \
|
|
"$missing_signature/latest.json"
|
|
if "$ROOT_DIR/scripts/release/verify-desktop-assets.sh" "$missing_signature" linux-arm64 \
|
|
>"$TMP_DIR/out" 2>"$TMP_DIR/err"; then
|
|
fail "desktop asset verification should reject a missing updater signature"
|
|
fi
|
|
grep -q "Missing updater signature" "$TMP_DIR/err" || fail "asset verifier did not explain the missing updater signature"
|
|
if generate_manifest "$missing_signature" >"$TMP_DIR/out" 2>"$TMP_DIR/err"; then
|
|
fail "generator should reject a missing signature"
|
|
fi
|
|
grep -q "Missing updater signature" "$TMP_DIR/err" || fail "missing signature error was not actionable"
|
|
pass "updater manifest rejects missing signatures"
|
|
|
|
invalid_signature="$TMP_DIR/invalid-signature"
|
|
cp -R "$complete_assets" "$invalid_signature"
|
|
printf 'not-a-tauri-signature\n' > "$invalid_signature/kandev-desktop-linux-x64-Kandev.AppImage.tar.gz.sig"
|
|
rm "$invalid_signature/latest.json"
|
|
if generate_manifest "$invalid_signature" >"$TMP_DIR/out" 2>"$TMP_DIR/err"; then
|
|
fail "generator should reject an invalid signature"
|
|
fi
|
|
grep -q "Invalid updater signature" "$TMP_DIR/err" || fail "invalid signature error was not actionable"
|
|
pass "updater manifest rejects invalid signatures"
|
|
|
|
wrong_key="$TMP_DIR/wrong-key"
|
|
cp -R "$complete_assets" "$wrong_key"
|
|
rm "$wrong_key/latest.json"
|
|
if UPDATER_TEST_PUBLIC_KEY="$WRONG_PUBLIC_KEY" generate_manifest "$wrong_key" \
|
|
>"$TMP_DIR/out" 2>"$TMP_DIR/err"; then
|
|
fail "generator should reject signatures made by a different updater key"
|
|
fi
|
|
grep -Eq "different key|does not match embedded public key|invalid updater public key" \
|
|
"$TMP_DIR/err" || fail "wrong updater key error was not actionable"
|
|
pass "updater manifest rejects signatures from a different key"
|
|
|
|
wrong_url="$TMP_DIR/wrong-url"
|
|
cp -R "$complete_assets" "$wrong_url"
|
|
WRONG_URL_MANIFEST="$wrong_url/latest.json" node <<'NODE'
|
|
const fs = require("node:fs");
|
|
const path = process.env.WRONG_URL_MANIFEST;
|
|
const manifest = JSON.parse(fs.readFileSync(path, "utf8"));
|
|
manifest.platforms["darwin-aarch64"].url = "https://example.test/not-the-release-asset.tar.gz";
|
|
fs.writeFileSync(path, `${JSON.stringify(manifest, null, 2)}\n`);
|
|
NODE
|
|
if node "$SCRIPT" verify \
|
|
--assets-dir "$wrong_url" \
|
|
--manifest "$wrong_url/latest.json" \
|
|
--version "1.2.3" \
|
|
--tag "v1.2.3" \
|
|
--repository "kdlbs/kandev" \
|
|
--public-key "$TEST_PUBLIC_KEY" >"$TMP_DIR/out" 2>"$TMP_DIR/err"; then
|
|
fail "verifier should reject a wrong release URL"
|
|
fi
|
|
grep -q "Unexpected updater URL" "$TMP_DIR/err" || fail "wrong URL error was not actionable"
|
|
pass "updater manifest rejects wrong URLs"
|
|
|
|
if node "$SCRIPT" generate \
|
|
--assets-dir "$complete_assets" \
|
|
--output "$TMP_DIR/prerelease.json" \
|
|
--version "1.2.3-beta.1" \
|
|
--tag "v1.2.3-beta.1" \
|
|
--repository "kdlbs/kandev" \
|
|
--notes-file "$complete_assets/notes.md" \
|
|
--pub-date "2026-07-15T12:00:00Z" >"$TMP_DIR/out" 2>"$TMP_DIR/err"; then
|
|
fail "generator should reject prerelease metadata"
|
|
fi
|
|
grep -q "stable SemVer" "$TMP_DIR/err" || fail "prerelease error was not actionable"
|
|
pass "updater manifest rejects prerelease versions"
|
|
|
|
if node "$SCRIPT" generate \
|
|
--assets-dir "$complete_assets" \
|
|
--output "$TMP_DIR/leading-zero-version.json" \
|
|
--version "01.2.3" \
|
|
--tag "v01.2.3" \
|
|
--repository "kdlbs/kandev" \
|
|
--notes-file "$complete_assets/notes.md" \
|
|
--pub-date "2026-07-15T12:00:00Z" >"$TMP_DIR/out" 2>"$TMP_DIR/err"; then
|
|
fail "generator should reject leading-zero SemVer metadata"
|
|
fi
|
|
grep -q "stable SemVer" "$TMP_DIR/err" || fail "leading-zero SemVer error was not actionable"
|
|
pass "updater manifest rejects leading-zero versions"
|
|
|
|
if node "$SCRIPT" generate \
|
|
--assets-dir "$complete_assets" \
|
|
--output "$TMP_DIR/invalid-date.json" \
|
|
--version "1.2.3" \
|
|
--tag "v1.2.3" \
|
|
--repository "kdlbs/kandev" \
|
|
--notes-file "$complete_assets/notes.md" \
|
|
--pub-date "2025-02-31T12:00:00Z" >"$TMP_DIR/out" 2>"$TMP_DIR/err"; then
|
|
fail "generator should reject invalid calendar dates"
|
|
fi
|
|
grep -q "pub_date must be RFC 3339" "$TMP_DIR/err" || fail "invalid date error was not actionable"
|
|
pass "updater manifest rejects invalid calendar dates"
|
|
|
|
unsigned_assets="$TMP_DIR/unsigned"
|
|
mkdir -p "$unsigned_assets"
|
|
printf 'Release notes\n' > "$unsigned_assets/notes.md"
|
|
printf 'unsigned installer\n' > "$unsigned_assets/kandev-desktop-linux-x64-Kandev.AppImage"
|
|
generate_manifest "$unsigned_assets" --allow-unsigned
|
|
if [ -e "$unsigned_assets/latest.json" ]; then
|
|
fail "unsigned fallback should not emit latest.json"
|
|
fi
|
|
pass "unsigned installers are excluded from the updater feed"
|
|
|
|
partial_assets="$TMP_DIR/partial"
|
|
mkdir -p "$partial_assets"
|
|
printf 'Release notes\n' > "$partial_assets/notes.md"
|
|
write_artifact "$partial_assets" "kandev-desktop-linux-x64-Kandev.AppImage.tar.gz"
|
|
if generate_manifest "$partial_assets" --allow-unsigned >"$TMP_DIR/out" 2>"$TMP_DIR/err"; then
|
|
fail "partial signed updater sets should fail"
|
|
fi
|
|
grep -q "Incomplete updater artifact set" "$TMP_DIR/err" || fail "partial set error was not actionable"
|
|
pass "partial signed updater feeds fail closed"
|