KeiSeiKit-1.0/.github/workflows/release.yml
Parfii-bot c27b626af7 fix(v0.19.1): SHA-pin release.yml GitHub Actions + require bun.lock
Partial supply-chain hardening (rate-limited before completing).

release.yml (H5 — CVE-2025-30066 class defense):
  - actions/checkout@34e114876b... (v4.3.1)
  - dtolnay/rust-toolchain@3c5f7ea28... (rust 1.94.1)
  - Swatinem/rust-cache@c19371144... (v2.9.1)
  - actions/upload-artifact@ea165f8d6... (v4.6.2)
  - actions/download-artifact@<pinned>
  - oven-sh/setup-bun@0c5077e51... (v2.2.0)
  - softprops/action-gh-release@<pinned>

release.yml (H4 — reproducible build):
  - Removed '|| bun install' fallback from build-mcp-binary job.
  - bun.lock now REQUIRED — missing lockfile fails the build.

NOT YET DONE (deferred to follow-up agent):
  - ci.yml same SHA-pinning (separate commit)
  - .github/dependabot.yml (weekly SHA update PRs)
  - _ts_packages/packages/mcp-server/bun.lock (placeholder commit)
  - BUILD.md 'Lockfile' subsection
  - CHANGELOG Security section under [Unreleased]

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 17:09:16 +08:00

308 lines
12 KiB
YAML

name: Release
on:
push:
tags:
- 'v*'
permissions:
contents: write
jobs:
build-release:
name: Build ${{ matrix.target }}
runs-on: ${{ matrix.os }}
continue-on-error: ${{ matrix.experimental }}
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
experimental: false
- os: ubuntu-latest
target: aarch64-unknown-linux-gnu
experimental: true
- os: macos-latest
target: x86_64-apple-darwin
experimental: false
- os: macos-latest
target: aarch64-apple-darwin
experimental: false
steps:
# v0.19.1 supply-chain hardening (H5): all actions pinned by full
# commit SHA; a floating tag like @v4 can be re-pointed by a
# compromised maintainer (CVE-2025-30066 class). Version comment next
# to each SHA is for human readability only — the SHA is load-bearing.
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
fetch-depth: 0
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@3c5f7ea28cd621ae0bf5283f0e981fb97b8a7af9 # rust 1.94.1 (dtolnay/rust-toolchain master)
with:
targets: ${{ matrix.target }}
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
workspaces: _primitives/_rust
- name: Install aarch64 cross-linker (Linux only)
if: matrix.target == 'aarch64-unknown-linux-gnu'
run: |
sudo apt-get update
sudo apt-get install -y gcc-aarch64-linux-gnu
mkdir -p .cargo
printf '[target.aarch64-unknown-linux-gnu]\nlinker = "aarch64-linux-gnu-gcc"\n' \
> _primitives/_rust/.cargo/config.toml
- name: Build workspace (release)
working-directory: _primitives/_rust
run: cargo build --workspace --release --target ${{ matrix.target }}
- name: Package binaries
id: package
working-directory: _primitives/_rust/target/${{ matrix.target }}/release
shell: bash
run: |
set -euo pipefail
# Collect every Cargo-built executable (Linux + macOS: no ext, mode +x).
# Portable across GNU + BSD find: iterate, test executability in shell.
BINS=()
for f in *; do
[ -f "$f" ] || continue
case "$f" in
*.d|*.rlib|*.rmeta|*.so|*.dylib|*.dSYM) continue ;;
esac
if [ -x "$f" ]; then
BINS+=("$f")
fi
done
if [ "${#BINS[@]}" -eq 0 ]; then
echo "::error::no release binaries produced for ${{ matrix.target }}"
exit 1
fi
echo "Binaries found: ${BINS[*]}"
ARCHIVE="keisei-${{ matrix.target }}.tar.gz"
tar czf "$GITHUB_WORKSPACE/$ARCHIVE" "${BINS[@]}"
cd "$GITHUB_WORKSPACE"
if command -v sha256sum >/dev/null 2>&1; then
sha256sum "$ARCHIVE" > "$ARCHIVE.sha256"
else
shasum -a 256 "$ARCHIVE" > "$ARCHIVE.sha256"
fi
echo "archive=$ARCHIVE" >> "$GITHUB_OUTPUT"
- name: Upload artifact
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: binaries-${{ matrix.target }}
path: |
keisei-${{ matrix.target }}.tar.gz
keisei-${{ matrix.target }}.tar.gz.sha256
if-no-files-found: error
# v0.18 Phase 1 (exobrain): compile @keisei/mcp-server to a single static
# binary for 5 platforms via `bun build --compile`. Runs in parallel with
# build-release; the release job below `needs:` both. Linux arm64 is kept
# `continue-on-error` because the ubuntu arm runner pool is newer and
# occasionally flaky — a missing linux-arm64 asset must NOT block release.
build-mcp-binary:
name: Build mcp-server ${{ matrix.target.platform }}-${{ matrix.target.arch }}
runs-on: ${{ matrix.target.runner }}
continue-on-error: ${{ matrix.target.arch == 'arm64' && matrix.target.platform == 'linux' }}
strategy:
fail-fast: false
matrix:
target:
- { platform: linux, arch: x64, runner: ubuntu-latest, bun_target: bun-linux-x64, ext: '' }
- { platform: linux, arch: arm64, runner: ubuntu-24.04-arm, bun_target: bun-linux-arm64, ext: '' }
- { platform: darwin, arch: x64, runner: macos-13, bun_target: bun-darwin-x64, ext: '' }
- { platform: darwin, arch: arm64, runner: macos-latest, bun_target: bun-darwin-arm64, ext: '' }
- { platform: windows, arch: x64, runner: windows-latest, bun_target: bun-windows-x64, ext: '.exe' }
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- name: Install bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
with:
bun-version: latest
# v0.19.1 supply-chain hardening (H4): lockfile is REQUIRED — the
# `|| bun install` fallback was removed so a missing bun.lock fails
# the build instead of resolving deps fresh against the live npm
# registry (tainted-binary window). bun.lock MUST be committed
# before any release tag. See BUILD.md §Lockfile.
- name: Install mcp-server deps
shell: bash
working-directory: _ts_packages/packages/mcp-server
run: bun install --frozen-lockfile
- name: Compile single-binary
shell: bash
env:
BIN_NAME: kei-mcp-server-${{ matrix.target.platform }}-${{ matrix.target.arch }}${{ matrix.target.ext }}
run: |
set -euo pipefail
mkdir -p dist
bun build \
--compile \
--target=${{ matrix.target.bun_target }} \
_ts_packages/packages/mcp-server/src/index.ts \
--outfile "dist/${BIN_NAME}"
ls -la "dist/${BIN_NAME}"
- name: Compute sha256
shell: bash
env:
BIN_NAME: kei-mcp-server-${{ matrix.target.platform }}-${{ matrix.target.arch }}${{ matrix.target.ext }}
run: |
set -euo pipefail
cd dist
if command -v sha256sum >/dev/null 2>&1; then
sha256sum "${BIN_NAME}" > "${BIN_NAME}.sha256"
else
shasum -a 256 "${BIN_NAME}" > "${BIN_NAME}.sha256"
fi
cat "${BIN_NAME}.sha256"
- name: Upload artifact
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: kei-mcp-server-${{ matrix.target.platform }}-${{ matrix.target.arch }}
path: |
dist/kei-mcp-server-${{ matrix.target.platform }}-${{ matrix.target.arch }}${{ matrix.target.ext }}
dist/kei-mcp-server-${{ matrix.target.platform }}-${{ matrix.target.arch }}${{ matrix.target.ext }}.sha256
if-no-files-found: error
release:
name: Publish GitHub Release
needs: [build-release, build-mcp-binary]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
fetch-depth: 0
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@3c5f7ea28cd621ae0bf5283f0e981fb97b8a7af9 # rust 1.94.1 (dtolnay/rust-toolchain master)
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
workspaces: _primitives/_rust
- name: Build kei-changelog
working-directory: _primitives/_rust
run: cargo build --release -p kei-changelog
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
path: dist/
- name: Flatten artifacts
run: |
set -euo pipefail
mkdir -p release-assets
# Rust tarballs + sha256 sums from build-release matrix.
# MCP-server bare binaries (+ .exe on windows) + sha256 sums from
# build-mcp-binary matrix. Bare binaries need a stable name to stay
# USB-drive-droppable, so no archive — we ship them raw alongside
# the tarballs.
find dist -type f \( \
-name '*.tar.gz' \
-o -name '*.sha256' \
-o -name 'kei-mcp-server-*' \
\) -exec mv {} release-assets/ \;
ls -la release-assets
- name: Generate release notes (kei-changelog)
id: notes
run: |
set -euo pipefail
TAG="${GITHUB_REF_NAME}"
PREV="$(git tag --sort=-creatordate | grep -v "^${TAG}$" | head -n1 || true)"
echo "Current tag: ${TAG}"
echo "Previous tag: ${PREV:-<none>}"
if [ -n "${PREV}" ]; then
NOTES="$(./_primitives/_rust/target/release/kei-changelog \
--from "${PREV}" --to "${TAG}" --version "${TAG}")"
else
NOTES="$(./_primitives/_rust/target/release/kei-changelog \
--to "${TAG}" --version "${TAG}")"
fi
if [ -z "${NOTES}" ]; then
NOTES="Release ${TAG}. No conventional-commit entries found in range."
fi
{
echo 'notes<<KEISEI_NOTES_EOF'
echo "${NOTES}"
echo 'KEISEI_NOTES_EOF'
} >> "$GITHUB_OUTPUT"
- name: Publish GitHub Release
# HIGH priority pin: this action has `contents: write` — a compromised
# tag would let an attacker publish arbitrary releases under this repo.
uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2.6.2
with:
name: ${{ github.ref_name }}
tag_name: ${{ github.ref_name }}
body: ${{ steps.notes.outputs.notes }}
files: |
release-assets/*.tar.gz
release-assets/*.sha256
release-assets/kei-mcp-server-*
fail_on_unmatched_files: false
npm-publish:
name: Publish npm packages (optional)
needs: release
runs-on: ubuntu-latest
# Graceful skip: if NPM_TOKEN secret is not configured, the first step
# reports "skipped" and exits 0 — Rust-binary release above still succeeds.
steps:
- name: Check NPM_TOKEN presence
id: have_token
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
run: |
if [ -n "${NPM_TOKEN:-}" ]; then
echo "present=1" >> "$GITHUB_OUTPUT"
else
echo "present=0" >> "$GITHUB_OUTPUT"
echo "::notice::NPM_TOKEN not set — skipping npm publish gracefully"
fi
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
if: steps.have_token.outputs.present == '1'
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
if: steps.have_token.outputs.present == '1'
with:
node-version: '20'
registry-url: 'https://registry.npmjs.org'
- name: Install deps
if: steps.have_token.outputs.present == '1'
working-directory: _ts_packages
run: npm ci
- name: Build workspaces
if: steps.have_token.outputs.present == '1'
working-directory: _ts_packages
run: npm run build --workspaces --if-present
- name: Publish each package
if: steps.have_token.outputs.present == '1'
working-directory: _ts_packages
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: |
set -euo pipefail
for pkg in packages/*/; do
if [ -f "$pkg/package.json" ]; then
echo "::group::publish $pkg"
( cd "$pkg" && npm publish --access public ) \
|| echo "::warning::publish failed for $pkg (continuing)"
echo "::endgroup::"
fi
done