Skip to content

Nightly Release

Nightly Release #436

Workflow file for this run

name: Nightly Release
on:
schedule:
- cron: "0 0 * * *" # midnight GMT
workflow_dispatch:
permissions:
contents: read # checkout repository
concurrency:
group: ${{ github.workflow }}
cancel-in-progress: false
env:
CARGO_TERM_COLOR: always
jobs:
# 0) Move/force the "nightly" tag to main and delete the prior GH release.
prepare-nightly-release:
name: Prepare nightly tag + delete prior release
runs-on: ubuntu-latest
permissions:
contents: write # push nightly tag and delete prior release
env:
GH_TOKEN: ${{ github.token }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
persist-credentials: false
- name: Set nightly tag to latest main
shell: bash
run: |
set -euo pipefail
git fetch origin main
MAIN_SHA="$(git rev-parse origin/main)"
# Move the nightly tag via the API (git push is unavailable with persist-credentials: false)
gh api "repos/${GITHUB_REPOSITORY}/git/refs/tags/nightly" \
--method PATCH \
--field sha="${MAIN_SHA}" \
--field force=true 2>/dev/null \
|| gh api "repos/${GITHUB_REPOSITORY}/git/refs" \
--method POST \
--field ref="refs/tags/nightly" \
--field sha="${MAIN_SHA}"
- name: Delete existing nightly release (if any)
shell: bash
run: gh release delete nightly --yes || true
# 1) Build & push per-arch nightly images, sign each by digest, export digests for manifest job.
build-oci-images:
name: Build OCI nightly image (${{ matrix.arch }})
environment: staging
needs: prepare-nightly-release
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-24.04
arch: amd64
platform: linux/amd64
- os: ubuntu-24.04-arm
arch: arm64
platform: linux/arm64
runs-on: ${{ matrix.os }}
permissions:
contents: read # checkout repository
packages: write # push container images to GHCR
id-token: write # keyless cosign signing
outputs:
digest_amd64: ${{ steps.meta.outputs.digest_amd64 }}
digest_arm64: ${{ steps.meta.outputs.digest_arm64 }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
submodules: true
persist-credentials: false
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
- name: Install cosign
uses: sigstore/cosign-installer@cad07c2e89fa2edd6e2d7bab4c1aa38e53f76003 # v4.1.1
- name: Log in to GHCR
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build & push (by platform) with BuildKit cache
id: build
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0
with:
context: .
push: true
platforms: ${{ matrix.platform }}
tags: |
ghcr.io/${{ github.repository_owner }}/hyper-mcp:nightly-${{ matrix.arch }}
cache-from: type=gha,scope=hyper-mcp-nightly-${{ matrix.arch }}
cache-to: type=gha,mode=max,scope=hyper-mcp-nightly-${{ matrix.arch }}
- name: Resolve and sign arch image by digest
id: meta
shell: bash
run: |
set -euo pipefail
IMAGE="ghcr.io/${REPOSITORY_OWNER}/hyper-mcp"
DIGEST="${STEPS_BUILD_OUTPUTS_DIGEST}"
REF="${IMAGE}@${DIGEST}"
echo "Signing arch nightly image by digest: ${REF}"
cosign sign --yes "${REF}"
if [[ "${ARCH}" == "amd64" ]]; then
echo "digest_amd64=${DIGEST}" >> "$GITHUB_OUTPUT"
elif [[ "${ARCH}" == "arm64" ]]; then
echo "digest_arm64=${DIGEST}" >> "$GITHUB_OUTPUT"
else
echo "Unknown arch: ${ARCH}" >&2
exit 1
fi
echo "Built ${IMAGE}:nightly-${ARCH} -> ${DIGEST}"
env:
STEPS_BUILD_OUTPUTS_DIGEST: ${{ steps.build.outputs.digest }}
REPOSITORY_OWNER: ${{ github.repository_owner }}
ARCH: ${{ matrix.arch }}
# 2) Create multi-arch nightly manifest (tag=nightly), sign manifest by digest, export digest for release notes.
create-multiarch-manifests:
name: Create & sign multi-arch nightly manifest
needs: build-oci-images
runs-on: ubuntu-latest
permissions:
contents: read # checkout repository
packages: write # push manifest to GHCR
id-token: write # keyless cosign signing
outputs:
nightly_digest: ${{ steps.digest.outputs.nightly_digest }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
persist-credentials: false
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
- name: Install cosign
uses: sigstore/cosign-installer@cad07c2e89fa2edd6e2d7bab4c1aa38e53f76003 # v4.1.1
- name: Log in to GHCR
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Create, push, and sign nightly manifest by digest
id: digest
shell: bash
run: |
set -euo pipefail
IMAGE="ghcr.io/${REPOSITORY_OWNER}/hyper-mcp"
AMD64_DIGEST="${NEEDS_BUILD_OCI_IMAGES_OUTPUTS_DIGEST_AMD64}"
ARM64_DIGEST="${NEEDS_BUILD_OCI_IMAGES_OUTPUTS_DIGEST_ARM64}"
if [[ -z "${AMD64_DIGEST}" || -z "${ARM64_DIGEST}" ]]; then
echo "Missing per-arch digests from build job." >&2
exit 1
fi
echo "Creating multi-arch manifest: ${IMAGE}:nightly"
docker buildx imagetools create \
-t "${IMAGE}:nightly" \
"${IMAGE}@${AMD64_DIGEST}" \
"${IMAGE}@${ARM64_DIGEST}"
NIGHTLY_DIGEST="$(
docker buildx imagetools inspect "${IMAGE}:nightly" --format '{{json .Manifest}}' \
| python3 -c 'import json,sys; print(json.load(sys.stdin)["digest"])'
)"
if [[ ! "$NIGHTLY_DIGEST" =~ ^sha256:[0-9a-f]{64}$ ]]; then
echo "ERROR: Invalid digest: '$NIGHTLY_DIGEST'" >&2
exit 1
fi
echo "nightly_digest=${NIGHTLY_DIGEST}" >> "$GITHUB_OUTPUT"
echo "Resolved ${IMAGE}:nightly -> ${NIGHTLY_DIGEST}"
echo "Signing nightly multi-arch manifest by digest: ${IMAGE}@${NIGHTLY_DIGEST}"
cosign sign --yes "${IMAGE}@${NIGHTLY_DIGEST}"
env:
NEEDS_BUILD_OCI_IMAGES_OUTPUTS_DIGEST_AMD64: ${{ needs.build-oci-images.outputs.digest_amd64 }}
NEEDS_BUILD_OCI_IMAGES_OUTPUTS_DIGEST_ARM64: ${{ needs.build-oci-images.outputs.digest_arm64 }}
REPOSITORY_OWNER: ${{ github.repository_owner }}
# 3) Build binaries (matrix), upload as artifacts.
build-nightly-binaries:
name: Build nightly binaries (${{ matrix.target }})
environment: staging
needs: prepare-nightly-release
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-24.04
target: x86_64-unknown-linux-gnu
ext: ""
- os: ubuntu-24.04-arm
target: aarch64-unknown-linux-gnu
ext: ""
- os: macos-latest
target: aarch64-apple-darwin
ext: ""
- os: windows-latest
target: x86_64-pc-windows-msvc
ext: ".exe"
runs-on: ${{ matrix.os }}
permissions:
contents: read # checkout repository
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
persist-credentials: false
- name: Install Rust toolchain
run: rustup show
- name: Install cargo-auditable
run: cargo install cargo-auditable
- name: Install compilation target
shell: bash
run: rustup target add "${TARGET}"
env:
TARGET: ${{ matrix.target }}
- name: Build
shell: bash
run: cargo auditable build --target "${TARGET}" --release --locked
env:
TARGET: ${{ matrix.target }}
- name: Package
shell: bash
run: |
set -euo pipefail
mkdir -p dist
bin="hyper-mcp${EXT}"
src="target/${TARGET}/release/${bin}"
if [[ "${RUNNER_OS}" == "Windows" ]]; then
# Use PowerShell's Compress-Archive? We'll keep it simple with tar if available;
# but Windows runners have bsdtar via Git. Use zip for compatibility.
7z a "dist/hyper-mcp-${TARGET}.zip" "${src}"
pkg="dist/hyper-mcp-${TARGET}.zip"
else
tar -czf "dist/hyper-mcp-${TARGET}.tar.gz" -C "target/${TARGET}/release" "${bin}"
pkg="dist/hyper-mcp-${TARGET}.tar.gz"
fi
# checksums
if command -v sha256sum >/dev/null 2>&1; then
sha256sum "${pkg}" > "dist/checksums-${TARGET}.txt"
elif command -v shasum >/dev/null 2>&1; then
shasum -a 256 "${pkg}" > "dist/checksums-${TARGET}.txt"
elif [[ "$RUNNER_OS" == "Windows" ]]; then
powershell.exe -NoProfile -Command \
"\$h=(Get-FileHash -Algorithm SHA256 '${pkg}').Hash.ToLower(); \"\$h ${pkg}\" | Out-File -Encoding ascii 'dist/checksums-${TARGET}.txt'"
else
echo "No SHA256 tool found" >&2
exit 1
fi
env:
TARGET: ${{ matrix.target }}
EXT: ${{ matrix.ext }}
RUNNER_OS: ${{ runner.os }}
- name: Upload artifacts
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: nightly-${{ matrix.target }}
path: |
dist/hyper-mcp-${{ matrix.target }}.tar.gz
dist/hyper-mcp-${{ matrix.target }}.zip
dist/checksums-${{ matrix.target }}.txt
if-no-files-found: error
sbom:
name: SBOM
runs-on: ubuntu-latest
permissions:
contents: read # checkout repository
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
submodules: true
persist-credentials: false
- name: Install cargo-cyclonedx
run: cargo install cargo-cyclonedx --locked
- name: Generate CycloneDX SBOM
run: cargo cyclonedx -f json --all-features --override-filename temp
- name: Clean SBOM
run: jq '
del(.metadata.component."bom-ref")
| del(.metadata.component.purl)
| del(.metadata.component.components[0]."bom-ref")
| del(.metadata.component.components[0].purl)
' temp.json > sbom.cdx.json
- name: Upload sbom artifact
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: sbom-cdx-json
path: sbom.cdx.json
if-no-files-found: error
# 4) Create the GitHub nightly release once, attach all artifacts, and include the immutable nightly digest.
publish-nightly-release:
name: Publish nightly GitHub Release
needs:
- build-nightly-binaries
- create-multiarch-manifests
- sbom
runs-on: ubuntu-latest
permissions:
contents: write # create GitHub release and upload assets
env:
GH_TOKEN: ${{ github.token }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
persist-credentials: false
- name: Download build artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
pattern: nightly-*
path: dist
- name: Flatten artifacts
shell: bash
run: |
set -euo pipefail
mkdir -p out
find dist -type f -maxdepth 3 -print -exec cp {} out/ \;
- name: Download sbom artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: sbom-cdx-json
path: .
- name: Write release notes (with immutable digest)
id: notes
shell: bash
run: |
set -euo pipefail
IMAGE="ghcr.io/${REPOSITORY_OWNER}/hyper-mcp"
NIGHTLY_DIGEST="${NEEDS_CREATE_MULTIARCH_MANIFESTS_OUTPUTS_NIGHTLY_DIGEST}"
SHA="${GITHUB_SHA}"
cat > release-body.md <<EOF
Nightly build from \`main\`.
Commit:
- \`${SHA}\`
Container image (tag):
- \`${IMAGE}:nightly\`
✅ Multi-arch digest (immutable, recommended for pinning):
- \`${IMAGE}@${NIGHTLY_DIGEST}\`
All container images are signed with Cosign. Verify the **immutable digest** with:
\`\`\`bash
cosign verify \
--certificate-identity "https://github.com/hyper-mcp-rs/hyper-mcp/.github/workflows/nightly.yml@refs/heads/main" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
${IMAGE}@${NIGHTLY_DIGEST}
\`\`\`
EOF
env:
NEEDS_CREATE_MULTIARCH_MANIFESTS_OUTPUTS_NIGHTLY_DIGEST: ${{ needs.create-multiarch-manifests.outputs.nightly_digest }}
REPOSITORY_OWNER: ${{ github.repository_owner }}
- name: Create nightly release
shell: bash
run: |
gh release create nightly \
--title "Nightly build" \
--prerelease \
--generate-notes \
--notes-file release-body.md \
out/* \
sbom.cdx.json