Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ jobs:
run: cargo build --workspace && ./scripts/test-gate-e2e.sh
- name: Links between daemons end-to-end (MUS-66)
run: ./scripts/test-links-e2e.sh
# MUS-96: install-codex.sh runs the shim only when it matches the release's SHA256SUMS.
# A fake curl serves a local fixture, so nothing is downloaded.
- name: Installer checks its download (MUS-96)
run: sh scripts/test-install-codex.sh
# MUS-58: the sweeper starts only once myko has replayed Postgres, so an
# ad-hoc room membership survives a restart. Uses the job's Postgres
# service, in a table of its own.
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ jobs:
toolchain: stable
targets: ${{ matrix.target }}
- name: Build
env:
# MUS-96: shipped binaries carry no symbol tables or debuginfo. Here rather than in
# Cargo.toml, so local release builds keep theirs.
CARGO_PROFILE_RELEASE_STRIP: symbols
run: cargo build --release --locked --target ${{ matrix.target }} -p muster-shim -p muster-daemon
- name: Name the binaries for the release
run: |
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -556,7 +556,7 @@ it to a LAN or mesh address, or `0.0.0.0:6255`, to accept other machines.
| [`muster-prime-agent`](plugins/muster-prime-agent/) | Prime Agent live-conversation extension. |
| [`muster-opencode`](plugins/muster-opencode/) | opencode native plugin. |

Muster has no release pipeline or published packages yet; install from the repository (see Install). CI (`.github/workflows/ci.yml`) runs fmt, clippy, the tests and `scripts/test-gate-e2e.sh` on every push and pull request to `main`.
Muster has no published release or packages yet; install from the repository (see Install). `.github/workflows/release.yml` builds muster-shim and muster-daemon for macOS arm64 and Linux x86_64 with a `SHA256SUMS` (MUS-14), and `scripts/install-codex.sh` checks the shim against that file before running it (MUS-96). Both come from the same release, so the check guards against a corrupt or swapped download, not against a compromised release. CI (`.github/workflows/ci.yml`) runs fmt, clippy, the tests and `scripts/test-gate-e2e.sh` on every push and pull request to `main`.

## Status

Expand Down
9 changes: 9 additions & 0 deletions docs/roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,15 @@ Merged and needing a live run: MUS-36 and MUS-37.
Linux x86_64 under the names install-codex.sh asks for, with one SHA256SUMS. workflow_dispatch
is a dry run that only uploads artifacts; a `v*` tag also makes a draft release, which stays
unpublished until Josh publishes it. Left: a tag and a published release, on Josh's word.
- [x] **MUS-96: The installer checks its download, and release binaries are stripped.** From
MUS-14's review. Done when: `scripts/install-codex.sh` fetches SHA256SUMS from the same release,
checks the shim against it with sha256sum (or `shasum -a 256` on macOS), and refuses to run it on
a mismatch, a missing entry, a missing SHA256SUMS or no tool to check with; a fixture test
shows a tampered shim refused; and the release workflow strips the binaries, keeping its smoke
test, SHA-pinned actions and no cache. `scripts/test-install-codex.sh` runs in CI with a fake
curl: 9 checks pass, and 7 of them fail against the installer from before. This guards against a
corrupt or swapped download, not a tampered release, since both files come from the same
release; signing waits for Josh's Apple developer setup.
- [ ] **MUS-91: Keep `windows-cross-check` from timing out.** It hit its 20-minute limit on #110,
#113 and #124, and a re-run passed each time. Every time it was the apt step: `mingw-w64` pulls
278 MB (the i686 and x86_64 toolchains, C and C++), which normally takes 7 s, but the Azure
Expand Down
40 changes: 38 additions & 2 deletions scripts/install-codex.sh
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,49 @@ case "$os" in
;;
esac

url="$REPO/releases/latest/download/muster-shim-$target"
asset="muster-shim-$target"
base="$REPO/releases/latest/download"
tmp=$(mktemp -d)
bin="$tmp/muster-shim"
trap 'rm -rf "$tmp"' EXIT INT TERM

# The SHA-256 of a file, with whichever tool this machine has (macOS ships shasum).
sha256_of() {
if command -v sha256sum >/dev/null 2>&1; then
sha256sum "$1" | awk '{print $1}'
elif command -v shasum >/dev/null 2>&1; then
shasum -a 256 "$1" | awk '{print $1}'
else
return 1
fi
}

echo "Downloading muster-shim ($target)..."
curl -fSL --proto '=https' --tlsv1.2 -o "$bin" "$url"
curl -fSL --proto '=https' --tlsv1.2 -o "$bin" "$base/$asset"

# Check it against the release's SHA256SUMS before running it (MUS-96). Both come from the same
# release over HTTPS, so this catches a corrupt or swapped download, not a tampered release.
# Anything short of a match stops here, before the binary runs.
if ! curl -fsSL --proto '=https' --tlsv1.2 -o "$tmp/SHA256SUMS" "$base/SHA256SUMS"; then
echo "error: couldn't download SHA256SUMS from the release; not installing." >&2
exit 1
fi
expected=$(awk -v f="$asset" '{ name = $2; sub(/^\*/, "", name) } name == f { print $1; exit }' "$tmp/SHA256SUMS")
if [ -z "$expected" ]; then
echo "error: the release's SHA256SUMS has no entry for $asset; not installing." >&2
exit 1
fi
if ! actual=$(sha256_of "$bin"); then
echo "error: neither sha256sum nor shasum is installed, so the download can't be checked; not installing." >&2
exit 1
fi
if [ "$actual" != "$expected" ]; then
echo "error: $asset doesn't match the release's SHA256SUMS; not installing." >&2
echo " expected $expected" >&2
echo " got $actual" >&2
exit 1
fi
echo "Checked $asset against SHA256SUMS."
chmod +x "$bin"
# Clear the Gatekeeper quarantine in case this binary was fetched via a browser
# (curl downloads carry none). Harmless on Linux.
Expand Down
105 changes: 105 additions & 0 deletions scripts/test-install-codex.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
#!/bin/sh
# Check that scripts/install-codex.sh runs the shim only when it matches the release's SHA256SUMS
# (MUS-96). A fake `curl` serves a local fixture instead of GitHub, and the "shim" is a script that
# records how it was run, so nothing is downloaded and Codex is never touched. Runs in CI.
set -u
REPO=$(cd "$(dirname "$0")/.." && pwd)
WORK=$(mktemp -d)
trap 'rm -rf "$WORK"' EXIT INT TERM
pass=0
fail=0
check() {
if eval "$2"; then echo "PASS $1"; pass=$((pass + 1)); else echo "FAIL $1"; fail=$((fail + 1)); fi
}

# The fake curl: `-o FILE ... URL` copies the fixture named like the URL's last part, or fails as
# `curl -f` does when the release has no such file.
mkdir -p "$WORK/fake"
cat >"$WORK/fake/curl" <<'EOF'
#!/bin/sh
out=""
url=""
while [ $# -gt 0 ]; do
case "$1" in
-o) out=$2; shift 2 ;;
--proto | --tlsv1.2) [ "$1" = --proto ] && shift; shift ;;
-*) shift ;;
*) url=$1; shift ;;
esac
done
src="$FIXTURE/${url##*/}"
[ -f "$src" ] || { echo "curl: (22) 404 for $url" >&2; exit 22; }
cp "$src" "$out"
EOF
chmod +x "$WORK/fake/curl"

# A fixture release: the "shim" under both targets' names, and a SHA256SUMS listing them.
release() {
FIXTURE="$WORK/release-$1"
rm -rf "$FIXTURE"
mkdir -p "$FIXTURE"
for target in aarch64-apple-darwin x86_64-unknown-linux-gnu; do
printf '#!/bin/sh\necho "$*" > "$MARK"\n' >"$FIXTURE/muster-shim-$target"
done
(cd "$FIXTURE" && sha256sum muster-shim-* >SHA256SUMS)
export FIXTURE
}

# Runs the installer with PATH=$1, and records its exit status, its output and what the shim saw.
install() {
MARK="$WORK/shim-ran"
rm -f "$MARK"
export MARK
PATH=$1 sh "$REPO/scripts/install-codex.sh" --daemon ws://test:6255 >"$WORK/out" 2>&1
status=$?
}
ran() { [ -f "$MARK" ]; }
said() { grep -q "$1" "$WORK/out"; }

FULL="$WORK/fake:$PATH"

release good
install "$FULL"
check "a matching shim installs" '[ $status -eq 0 ] && said "Checked muster-shim-"'
check "and is run with codex-setup and the daemon" '[ "$(cat "$MARK")" = "codex-setup --daemon ws://test:6255" ]'

release tampered
for f in "$FIXTURE"/muster-shim-*; do echo "# one byte more" >>"$f"; done
install "$FULL"
check "a tampered shim is refused" '[ $status -ne 0 ] && said "doesn.t match the release.s SHA256SUMS"'
check "and never runs" '! ran'

release no-entry
: >"$FIXTURE/SHA256SUMS.new"
grep -v muster-shim "$FIXTURE/SHA256SUMS" >"$FIXTURE/SHA256SUMS.new"
mv "$FIXTURE/SHA256SUMS.new" "$FIXTURE/SHA256SUMS"
install "$FULL"
check "a SHA256SUMS without the shim is refused" '[ $status -ne 0 ] && said "has no entry for muster-shim-" && ! ran'

release no-sums
rm "$FIXTURE/SHA256SUMS"
install "$FULL"
check "a release without SHA256SUMS is refused" '[ $status -ne 0 ] && said "couldn.t download SHA256SUMS" && ! ran'

# macOS has shasum, not sha256sum. A PATH with only the tools the installer needs, a shasum that
# answers like the real one, and no sha256sum.
mkdir -p "$WORK/min"
for tool in sh awk uname mktemp chmod rm cp cat; do ln -s "$(command -v "$tool")" "$WORK/min/$tool"; done
ln -s "$WORK/fake/curl" "$WORK/min/curl"
SHA256SUM=$(command -v sha256sum)
printf '#!/bin/sh\n[ "$1" = -a ] && [ "$2" = 256 ] || exit 2\nexec %s "$3"\n' "$SHA256SUM" >"$WORK/min/shasum"
chmod +x "$WORK/min/shasum"
release good
install "$WORK/min"
check "with only shasum, a matching shim installs" '[ $status -eq 0 ] && ran'
release tampered
for f in "$FIXTURE"/muster-shim-*; do echo "# one byte more" >>"$f"; done
install "$WORK/min"
check "with only shasum, a tampered shim is refused" '[ $status -ne 0 ] && ! ran'
rm "$WORK/min/shasum"
release good
install "$WORK/min"
check "with neither tool, it refuses rather than skip the check" '[ $status -ne 0 ] && said "neither sha256sum nor shasum" && ! ran'

echo "$pass passed, $fail failed"
[ "$fail" -eq 0 ]
Loading