Files
routstrd/.github/workflows/release.yml
redshift 807bdc1c4b fix(macos): repair stale code signatures on standalone binaries
`bun build --compile` appends its standalone payload after the linker's
ad-hoc signature, so the embedded CodeDirectory no longer describes the
bytes on disk. macOS 15+ validates code pages lazily and SIGKILLs the
process on the first mismatching page (`Killed: 9`, exit 137). The
kernel logs it as:

  CODE SIGNING: rejecting invalid page ... (signed:1 validated:0 tainted:1)

Every standalone artifact we ship has this defect. Whether a given copy
dies at launch is luck: the bad page is the last one, which is cold
padding in some builds and hot startup code in others. The released
v0.4.14 darwin-arm64 binary was killed on every invocation, including
`--help` and `--version`, which also broke install.sh's version check.

Re-signing ad-hoc restores a valid signature without touching the
payload, and leaves already-valid signatures (for example a proper
Developer ID signature) untouched.

- install.sh: add ensure_valid_signature(), run after staging and before
  the version check. Only touches Mach-O files on Darwin, and only when
  codesign --verify fails.
- release.yml: repair the signature on macOS runners after the build and
  before the smoke test, so the packaged artifact is signed.
- standalone-update.ts: run ensureValidSignature() before verifyCandidate,
  which would otherwise hit the same SIGKILL during self-update.
- isMachO() reads only the four magic bytes rather than loading the whole
  binary, which is tens of megabytes.

Verified end to end: a fresh `bun build --compile` artifact reports an
invalid signature, and after repair both `codesign --verify` and the
binary itself succeed. install.sh repairs a broken binary and no-ops on
an already-valid one.
2026-10-25 20:00:43 +05:30

148 lines
4.8 KiB
YAML

name: Release
on:
pull_request:
workflow_dispatch:
push:
tags:
- "v*"
permissions:
contents: read
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: oven-sh/setup-bun@v2
with:
bun-version: 1.4.0
- run: bun install --frozen-lockfile
- name: Verify tag version
if: startsWith(github.ref, 'refs/tags/v')
run: |
package_version="$(bun -p "require('./package.json').version")"
test "${GITHUB_REF_NAME}" = "v${package_version}"
- run: bun run lint
- run: bun test
build:
needs: validate
strategy:
fail-fast: false
matrix:
include:
- runner: ubuntu-24.04
target: bun-linux-x64
platform: linux
arch: x64
- runner: ubuntu-24.04-arm
target: bun-linux-arm64
platform: linux
arch: arm64
- runner: macos-15-intel
target: bun-darwin-x64
platform: darwin
arch: x64
- runner: macos-15
target: bun-darwin-arm64
platform: darwin
arch: arm64
runs-on: ${{ matrix.runner }}
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: oven-sh/setup-bun@v2
with:
bun-version: 1.4.0
- run: bun install --frozen-lockfile
- name: Build standalone executable
run: >-
bun build --compile
--target=${{ matrix.target }}
--no-compile-autoload-dotenv
--no-compile-autoload-bunfig
src/index.ts
--outfile=build/routstrd
- name: Repair code signature (macOS)
if: runner.os == 'macOS'
run: |
# bun build --compile appends the standalone payload after the
# linker's ad-hoc signature, leaving the CodeDirectory stale. macOS
# validates code pages lazily and SIGKILLs the process on the first
# mismatching page, so re-sign before the smoke test runs the binary.
# A no-op when the artifact is already validly signed.
if codesign --verify build/routstrd >/dev/null 2>&1; then
echo "Existing signature verifies; leaving it untouched."
else
echo "Repairing stale ad-hoc signature on build/routstrd."
codesign --force --sign - --timestamp=none build/routstrd
codesign --verify --strict build/routstrd
fi
- name: Smoke test executable
run: |
package_version="$(bun -p "require('./package.json').version")"
test "$(build/routstrd --version)" = "${package_version}"
build/routstrd --help >/dev/null
- name: Package executable
run: |
package_version="$(bun -p "require('./package.json').version")"
tar -C build -czf \
"routstrd-v${package_version}-${{ matrix.platform }}-${{ matrix.arch }}.tar.gz" \
routstrd
- uses: actions/upload-artifact@v4
with:
name: routstrd-${{ matrix.platform }}-${{ matrix.arch }}
path: routstrd-*.tar.gz
if-no-files-found: error
release:
if: startsWith(github.ref, 'refs/tags/v')
needs: build
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: actions/download-artifact@v4
with:
pattern: routstrd-*
merge-multiple: true
- name: Create checksums
run: shasum -a 256 routstrd-*.tar.gz > SHA256SUMS
- name: Serve artifacts for installer smoke test
run: |
version="${GITHUB_REF_NAME#v}"
mkdir -p "serve/v${version}"
cp "routstrd-v${version}-linux-x64.tar.gz" SHA256SUMS "serve/v${version}/"
- name: Smoke test install.sh against the built artifacts
run: |
version="${GITHUB_REF_NAME#v}"
python3 -m http.server 8137 --bind 127.0.0.1 --directory serve &
server_pid=$!
trap 'kill "${server_pid}"' EXIT
sleep 1
sh install.sh \
--version "${version}" \
--platform linux \
--arch x64 \
--dir /tmp/routstrd-install-smoke \
--download-base-url http://127.0.0.1:8137
test "$(/tmp/routstrd-install-smoke/routstrd --version)" = "${version}"
- name: Publish GitHub Release
env:
GH_TOKEN: ${{ github.token }}
run: >-
gh release create "${GITHUB_REF_NAME}"
routstrd-*.tar.gz SHA256SUMS install.sh
--repo "${GITHUB_REPOSITORY}"
--verify-tag
--generate-notes
--title "${GITHUB_REF_NAME}"