cmux/tests/test_remote_daemon_release_assets.sh
Lawrence Chen ccd84bd578
Fix nightly SSH remote daemon checksum mismatch (#2225)
* Fix nightly SSH remote daemon checksum mismatch

Each nightly build overwrites the shared cmuxd-remote-* assets on the
nightly release, but older nightly DMGs have manifests with checksums
from their build time. When a user's nightly is even one build behind,
the downloaded binary doesn't match their embedded manifest.

Two-layer fix:

1. CI: version nightly remote daemon asset names with the build number
   (e.g. cmuxd-remote-darwin-arm64-2362248028801) so each nightly's
   manifest points to immutable files. Unsuffixed "latest" copies are
   still uploaded for tooling compatibility.

2. Client: on checksum mismatch, fetch the live manifest from the
   release and verify against that. This handles users on older
   nightlies that predate the CI fix.

Fixes https://github.com/manaflow-ai/cmux/issues/1745

* Fix unsuffixed checksums file to use generic filenames

Regenerate cmuxd-remote-checksums.txt from the unsuffixed alias
binaries so `shasum -c` works against the generic asset names.
Also document that unsuffixed manifest intentionally keeps versioned
downloadURLs and that aliases don't carry attestation.

---------

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
2026-03-26 17:24:37 -07:00

108 lines
3.7 KiB
Bash
Executable file

#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
OUTPUT_DIR="$(mktemp -d "${TMPDIR:-/tmp}/cmux-remote-assets-test.XXXXXX")"
trap 'rm -rf "$OUTPUT_DIR"' EXIT
"$ROOT_DIR/scripts/build_remote_daemon_release_assets.sh" \
--version "0.62.0-test" \
--release-tag "v0.62.0-test" \
--repo "manaflow-ai/cmux" \
--output-dir "$OUTPUT_DIR" >/dev/null
for asset in \
cmuxd-remote-darwin-arm64 \
cmuxd-remote-darwin-amd64 \
cmuxd-remote-linux-arm64 \
cmuxd-remote-linux-amd64 \
cmuxd-remote-checksums.txt \
cmuxd-remote-manifest.json
do
if [[ ! -f "$OUTPUT_DIR/$asset" ]]; then
echo "FAIL: missing asset $asset" >&2
exit 1
fi
done
python3 - <<'PY' "$OUTPUT_DIR/cmuxd-remote-manifest.json" "$OUTPUT_DIR/cmuxd-remote-checksums.txt"
import json
import sys
from pathlib import Path
manifest_path = Path(sys.argv[1])
checksums_path = Path(sys.argv[2])
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
expected_targets = {
("darwin", "arm64"),
("darwin", "amd64"),
("linux", "arm64"),
("linux", "amd64"),
}
actual_targets = {(entry["goOS"], entry["goArch"]) for entry in manifest["entries"]}
if actual_targets != expected_targets:
raise SystemExit(f"FAIL: manifest targets {sorted(actual_targets)} != {sorted(expected_targets)}")
if manifest["appVersion"] != "0.62.0-test":
raise SystemExit(f"FAIL: unexpected appVersion {manifest['appVersion']}")
if manifest["releaseTag"] != "v0.62.0-test":
raise SystemExit(f"FAIL: unexpected releaseTag {manifest['releaseTag']}")
if not manifest["checksumsURL"].endswith("/cmuxd-remote-checksums.txt"):
raise SystemExit(f"FAIL: unexpected checksumsURL {manifest['checksumsURL']}")
checksum_lines = [line for line in checksums_path.read_text(encoding="utf-8").splitlines() if line.strip()]
if len(checksum_lines) != 4:
raise SystemExit(f"FAIL: expected 4 checksum lines, got {len(checksum_lines)}")
for entry in manifest["entries"]:
if not entry["downloadURL"].endswith("/" + entry["assetName"]):
raise SystemExit(f"FAIL: downloadURL mismatch for {entry['assetName']}")
if len(entry["sha256"]) != 64:
raise SystemExit(f"FAIL: invalid sha256 for {entry['assetName']}")
print("PASS: remote daemon release assets include all targets and manifest entries")
PY
# ------------------------------------------------------------------
# Test with --asset-suffix (nightly-style immutable asset names)
# ------------------------------------------------------------------
SUFFIX_DIR="$(mktemp -d "${TMPDIR:-/tmp}/cmux-remote-assets-suffix-test.XXXXXX")"
trap 'rm -rf "$OUTPUT_DIR" "$SUFFIX_DIR"' EXIT
"$ROOT_DIR/scripts/build_remote_daemon_release_assets.sh" \
--version "0.62.0-nightly.123456" \
--release-tag "nightly" \
--repo "manaflow-ai/cmux" \
--output-dir "$SUFFIX_DIR" \
--asset-suffix "123456" >/dev/null
for asset in \
cmuxd-remote-darwin-arm64-123456 \
cmuxd-remote-darwin-amd64-123456 \
cmuxd-remote-linux-arm64-123456 \
cmuxd-remote-linux-amd64-123456 \
cmuxd-remote-checksums-123456.txt \
cmuxd-remote-manifest-123456.json
do
if [[ ! -f "$SUFFIX_DIR/$asset" ]]; then
echo "FAIL: missing suffixed asset $asset" >&2
exit 1
fi
done
python3 - <<'PY' "$SUFFIX_DIR/cmuxd-remote-manifest-123456.json"
import json
import sys
from pathlib import Path
manifest = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8"))
for entry in manifest["entries"]:
if not entry["assetName"].endswith("-123456"):
raise SystemExit(f"FAIL: suffixed asset name missing suffix: {entry['assetName']}")
if not entry["downloadURL"].endswith("/" + entry["assetName"]):
raise SystemExit(f"FAIL: downloadURL mismatch for {entry['assetName']}")
print("PASS: --asset-suffix produces correctly suffixed assets and manifest entries")
PY