#!/bin/sh # Installs the harness: # # curl -fsSL https:///install.sh | sh # # Downloads the build for this machine, checks its release signature when this # machine's ssh-keygen can, checks it against its published SHA-256, and installs # it as $HARNESS_INSTALL_DIR/harness. Builds: Linux x86_64, and macOS 15.4 or # later on Apple Silicon or Intel. A pin of a release before 0.27.0 is refused on # an Intel Mac (no build) and on macOS before 26 (the older builds need it). # # HARNESS_VERSION the release to install, e.g. v0.7.1 (default: the latest) # HARNESS_INSTALL_DIR where the binary goes (default: ~/.local/bin) # HARNESS_DOWNLOAD_BASE where releases live (default: the site's /dl) # HARNESS_REQUIRE_SIGNATURE 1: install only a release whose signature was checked: # stop when no ssh-keygen here can verify signatures, # and refuse a pin of a release made before signing # # Under the base, `latest` names the newest release and each release is # /harness-.tar.gz plus its .sha256 and .sig (README.md). # The .sig signs the target, the version and the archive's SHA-256 (the message # built in main()); it is checked against the keys embedded below. A release # before 0.26.0 has none, and only a pin of such a release skips the check # (refused under HARNESS_REQUIRE_SIGNATURE=1). # # Everything runs inside main(), called on the last line, so a download cut # short runs nothing. # The site's own /dl, which vercel.json redirects to the release storage. SITE_DL="https://megacode-harness.vercel.app/dl" set -eu say() { printf '%s\n' "$*"; } err() { printf 'install: %s\n' "$*" >&2; exit 1; } warn() { printf 'install: %s\n' "$*" >&2; } # Standard input, line by line, with no tool beyond the shell's own. lines() { while IFS= read -r line; do printf '%s\n' "$line"; done; } # The release keys, in allowed_signers form: a copy of the harness repository's # crates/cli/release-keys.allowed_signers, which scripts/release.sh checks equal # before it tags (it reads the lines between the two RELEASE_KEYS markers). release_keys() { lines <<'RELEASE_KEYS' harness-release namespaces="harness-release" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILJDRf5npa3pw2Nk37A/c5bdNvklXwDnPXX3WNZQJ+P/ harness-release harness-release namespaces="harness-release" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPjFhPK/18InAD1mdJxWGJj+8JyQwQ2bcRZ2EBaqvqXm harness-release-backup RELEASE_KEYS } # A throwaway key, whose private half is not kept in the repository or the # site, and its signature of the line `harness-release-selftest`: can_verify() # checks this to learn whether this machine's ssh-keygen can verify a signature # at all. It trusts nothing: the release is checked against release_keys() only. probe_keys() { lines <<'PROBE_KEYS' harness-selftest namespaces="harness-release" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMaQTeTR/fZx34KHdFUJEVuZlgZlNi8Qt1JHkVDHPCwC harness-selftest PROBE_KEYS } probe_sig() { lines <<'PROBE_SIG' -----BEGIN SSH SIGNATURE----- U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgxpBN5NH99nHfgod0VQkRW5mWBm U2LxC3UkeRUMc8LAIAAAAPaGFybmVzcy1yZWxlYXNlAAAAAAAAAAZzaGE1MTIAAABTAAAA C3NzaC1lZDI1NTE5AAAAQKhdyvhKY35sTDdv75qAzQAy6FS4PcE8bQ3YZbCKDZUhHWeuaf QYUBY3aqeHNickQxHBqUpfNRZUXEWCiTPA0AI= -----END SSH SIGNATURE----- PROBE_SIG } # Can this machine's ssh-keygen verify a signature? OpenSSH before 8.0 or 8.1 has # no `-Y verify` (and `ssh-keygen -V` is no version flag), so the answer is # tested, not read from a version. Its output is not wanted, only its status. # Needs $tmp. can_verify() { command -v ssh-keygen >/dev/null 2>&1 || return 1 probe_keys >"$tmp/probe-keys" && probe_sig >"$tmp/probe-sig" || return 1 printf 'harness-release-selftest\n' | ssh-keygen -Y verify -f "$tmp/probe-keys" -I harness-selftest -n harness-release -s "$tmp/probe-sig" >/dev/null 2>&1 } # A checksum field is 64 lowercase hex characters, spelled out so no locale's # idea of a range matters. valid_hash() { case "$1" in *[!0123456789abcdef]*) return 1 ;; esac [ "${#1}" -eq 64 ] } # Was this release made before signing began (0.26.0)? The normalised version has # exactly three numeric parts, a major of 0 and a minor of 0 to 25, so v0.26.0, # v0.100.0, v1.0.0 and v0.25.0.1 are not. released_before_signing() { case "$1" in v0.[0-9].[0-9]* | v0.1[0-9].[0-9]* | v0.2[0-5].[0-9]*) ;; *) return 1 ;; esac patch=${1#v0.*.} case "$patch" in *[!0123456789]*) return 1 ;; esac } # Was this release made before 0.27.0, the first with an Intel Mac build and a # macOS 15.4 floor? The normalised version is v0.[.] with a minor of # 0 to 26, so v0.26.0 and v0.19.3 are, and v0.27.0, v0.100.0 and v1.0.0 are not. released_before_0_27() { case "$1" in v0.[0-9] | v0.[0-9].* | v0.1[0-9] | v0.1[0-9].* | v0.2[0-6] | v0.2[0-6].*) ;; *) return 1 ;; esac } # The macOS version, e.g. 15.4.1 (a function of its own: target() runs in a # subshell, so main() reads it again for a pin). macos_version() { sw_vers -productVersion; } # The release target for this machine, or an error naming what is missing. target() { os=$(uname -s) arch=$(uname -m) case "$os" in Linux) case "$arch" in x86_64 | amd64) say x86_64-unknown-linux-musl ;; *) err "no build for Linux on $arch yet (x86_64 only)" ;; esac ;; Darwin) # A shell under Rosetta reports x86_64 on an Apple Silicon Mac. if [ "$arch" = x86_64 ] && [ "$(sysctl -n sysctl.proc_translated 2>/dev/null || true)" = 1 ]; then arch=arm64 fi case "$arch" in arm64 | aarch64) t=aarch64-apple-darwin ;; x86_64) t=x86_64-apple-darwin ;; *) err "no build for $arch Macs (Apple Silicon and Intel only)" ;; esac # The oldest macOS the binary is built for: 15.4, compared as major then # minor. `sw_vers` prints 15.4, 15.4.1, 26.0.1 or 27.0, and a string with no # dot has minor 0. macos=$(macos_version) major=${macos%%.*} minor=0 case "$macos" in *.*) minor=${macos#*.}; minor=${minor%%.*} ;; esac if [ "$major" -lt 15 ] || { [ "$major" -eq 15 ] && [ "$minor" -lt 4 ]; }; then err "needs macOS 15.4 or later (this Mac runs $macos)" fi say "$t" ;; *) err "no build for $os (Linux and macOS only)" ;; esac } fetch() { # --proto-redir: a redirect from the site to storage never drops to plain http. curl -fsSL --proto-redir =https -o "$2" "$1" || err "download failed: $1" } main() { base=${HARNESS_DOWNLOAD_BASE:-$SITE_DL} dir=${HARNESS_INSTALL_DIR:-$HOME/.local/bin} for tool in curl tar mktemp; do command -v "$tool" >/dev/null 2>&1 || err "needs $tool" done if command -v sha256sum >/dev/null 2>&1; then sum="sha256sum" elif command -v shasum >/dev/null 2>&1; then sum="shasum -a 256" else err "needs sha256sum or shasum to check the download" fi t=$(target) || exit 1 asset="harness-$t.tar.gz" tmp=$(mktemp -d) || err "cannot make a temporary folder" # Only the files this script wrote, then the folder itself. trap 'rm -f "$tmp/latest" "$tmp/$asset" "$tmp/$asset.sha256" "$tmp/$asset.sig" "$tmp/release-keys" "$tmp/probe-keys" "$tmp/probe-sig" "$tmp/harness" "$dir/.harness.new"; rmdir "$tmp" 2>/dev/null || true' EXIT trap 'exit 130' INT TERM version=${HARNESS_VERSION:-} pinned=no if [ -z "$version" ]; then fetch "$base/latest" "$tmp/latest" version=$(tr -d ' \r\n' <"$tmp/latest") else pinned=yes fi case "$version" in [0-9]*) version="v$version" ;; esac case "$version" in *[!v0-9.]* | v | v*v*) err "not a version: '$version'" ;; v[0-9]*) ;; *) err "not a version: '$version'" ;; esac # A pin the Mac cannot run is refused before anything is fetched. It keys on # the chosen target, so a Rosetta shell on Apple Silicon gets the macOS # message. Releases before 0.27.0 have no Intel build and need macOS 26. if [ "$pinned" = yes ] && released_before_0_27 "$version"; then case "$t" in x86_64-apple-darwin) err "no Intel Mac build before 0.27.0" ;; *-apple-darwin) macos=$(macos_version) if [ "${macos%%.*}" -lt 26 ]; then err "releases before 0.27.0 need macOS 26 or later (this Mac runs $macos)" fi ;; esac fi say "Installing harness $version ($t)" # Only an explicit pin of a release made before signing skips the signature. # A version named by `latest`, which is unsigned, always needs one: otherwise # an attacker who could change `latest` would name an old release to reach # the unsigned path. check=yes if [ "$pinned" = yes ] && released_before_signing "$version"; then if [ "${HARNESS_REQUIRE_SIGNATURE:-}" = 1 ]; then err "harness $version was released before signing, so it has no signature to check; nothing installed" fi check=no say "harness $version was released before signing; checking its SHA-256 only" fi # The checksum and the signature come before the archive: a release that does # not check is never downloaded. fetch "$base/$version/$asset.sha256" "$tmp/$asset.sha256" want=$(cut -d' ' -f1 <"$tmp/$asset.sha256") valid_hash "$want" || err "checksum mismatch for $asset; nothing installed" if [ "$check" = yes ]; then if can_verify; then fetch "$base/$version/$asset.sig" "$tmp/$asset.sig" release_keys >"$tmp/release-keys" # The message names the format, the version, the target and the checksum # field that was just validated, never the file's bytes. printf 'harness-release-v1\n%s\n%s\n%s\n' "${version#v}" "$t" "$want" | ssh-keygen -Y verify -f "$tmp/release-keys" -I harness-release -n harness-release -s "$tmp/$asset.sig" >/dev/null 2>&1 || err "the signature of $asset does not check; nothing installed" elif [ "${HARNESS_REQUIRE_SIGNATURE:-}" = 1 ]; then err "cannot check the release signature: no ssh-keygen here can verify signatures; nothing installed" else warn "cannot check the release signature: no ssh-keygen here can verify signatures (OpenSSH's -Y verify); installing on the SHA-256 alone" fi fi fetch "$base/$version/$asset" "$tmp/$asset" got=$($sum "$tmp/$asset" | cut -d' ' -f1) [ -n "$want" ] && [ "$want" = "$got" ] || err "checksum mismatch for $asset; nothing installed" tar -xzf "$tmp/$asset" -C "$tmp" harness || err "cannot unpack $asset" mkdir -p "$dir" 2>/dev/null || err "cannot create $dir" # Written beside the target, then renamed over it: a harness already # running keeps the file it started from. { cp "$tmp/harness" "$dir/.harness.new" && chmod 755 "$dir/.harness.new" && mv -f "$dir/.harness.new" "$dir/harness"; } 2>/dev/null || err "cannot write to $dir" installed=$("$dir/harness" --version) || err "installed $dir/harness, but it does not run" say "Installed $installed to $dir/harness" case ":$PATH:" in *":$dir:"*) ;; *) say "" say "$dir is not on your PATH. Add this line to your shell's startup file" say "(~/.zshrc, ~/.bashrc), then open a new terminal:" say " export PATH=\"$dir:\$PATH\"" ;; esac if [ "$(uname -s)" = Linux ] && ! command -v bwrap >/dev/null 2>&1; then say "" say "The harness sandboxes commands with bubblewrap, which is not installed." say "Install the bubblewrap package, e.g.: sudo apt install bubblewrap" fi say "" say "Next, see how to start: ${SITE_DL%/dl}/#keys" say "Then start a session: harness run" } main "$@"