Merge pull request #187 from justrach/release/0.4.8 #20
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Release | |
| on: | |
| push: | |
| tags: | |
| - 'v*' | |
| permissions: | |
| contents: write | |
| jobs: | |
| # ── Linux: cross-compile both arches on one runner ───────────────────────── | |
| build-linux: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 45 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| target: [x86_64-linux, aarch64-linux] | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: mlugg/setup-zig@v2 | |
| with: | |
| version: 0.17.0-dev.813+2153f8143 | |
| - name: Build | |
| run: zig build -Dtarget=${{ matrix.target }} -Doptimize=ReleaseFast -p out | |
| - name: Package | |
| run: | | |
| VERSION="${GITHUB_REF_NAME}" | |
| mkdir -p dist | |
| tar -czf "dist/kuri-${VERSION}-${{ matrix.target }}.tar.gz" -C out/bin . | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: kuri-${{ matrix.target }} | |
| path: dist/*.tar.gz | |
| # ── macOS: both arches on one Apple Silicon runner ───────────────────────── | |
| # Both arches build here on purpose. The previous x86 job pinned `macos-13`, | |
| # a label GitHub has retired: it never got a runner, sat queued until the 24h | |
| # job limit killed it, and took every release down with it (the publish jobs | |
| # `needs:` it, so they were skipped). Zig cross-compiles x86_64-macos from | |
| # arm64 fine, and codesign is architecture-agnostic, so one live runner does | |
| # both. Each matrix leg installs to its own prefix so the tarballs can't pick | |
| # up the other arch's binaries. | |
| build-macos: | |
| runs-on: macos-latest | |
| timeout-minutes: 60 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| target: [aarch64-macos, x86_64-macos] | |
| outputs: | |
| signed: ${{ steps.sign.outputs.signed }} | |
| env: | |
| APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} | |
| APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} | |
| APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} | |
| APPLE_ID: ${{ secrets.APPLE_ID }} | |
| APPLE_APP_PASSWORD: ${{ secrets.APPLE_APP_PASSWORD }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: mlugg/setup-zig@v2 | |
| with: | |
| version: 0.17.0-dev.813+2153f8143 | |
| - name: Build | |
| run: zig build -Dtarget=${{ matrix.target }} -Doptimize=ReleaseFast -p out | |
| # Records whether signing actually happened, so the release notes and the | |
| # channel manifest can state the truth instead of asserting it. | |
| - name: Sign & notarize | |
| id: sign | |
| run: | | |
| set -euo pipefail | |
| if [ -z "${APPLE_CERTIFICATE}" ]; then | |
| echo "signed=false" >> "$GITHUB_OUTPUT" | |
| echo "::warning::No APPLE_CERTIFICATE secret — shipping UNSIGNED, un-notarized macOS binaries." | |
| exit 0 | |
| fi | |
| KEYCHAIN="build-$(uuidgen).keychain" | |
| KEYCHAIN_PASS="$(uuidgen)" | |
| security create-keychain -p "$KEYCHAIN_PASS" "$KEYCHAIN" | |
| security set-keychain-settings -lut 21600 "$KEYCHAIN" | |
| security unlock-keychain -p "$KEYCHAIN_PASS" "$KEYCHAIN" | |
| echo "$APPLE_CERTIFICATE" | base64 --decode > /tmp/cert.p12 | |
| security import /tmp/cert.p12 -k "$KEYCHAIN" -P "$APPLE_CERTIFICATE_PASSWORD" \ | |
| -T /usr/bin/codesign -T /usr/bin/productsign | |
| security list-keychain -d user -s "$KEYCHAIN" | |
| security set-key-partition-list -S apple-tool:,apple: -s -k "$KEYCHAIN_PASS" "$KEYCHAIN" | |
| for BIN in out/bin/*; do | |
| codesign --sign "Developer ID Application: $APPLE_TEAM_ID" \ | |
| --options runtime --timestamp --force "$BIN" | |
| done | |
| zip -j "/tmp/kuri-${{ matrix.target }}.zip" out/bin/* | |
| # --timeout so a stalled submission fails in minutes, not at the job cap. | |
| xcrun notarytool submit "/tmp/kuri-${{ matrix.target }}.zip" \ | |
| --apple-id "$APPLE_ID" \ | |
| --password "$APPLE_APP_PASSWORD" \ | |
| --team-id "$APPLE_TEAM_ID" \ | |
| --wait --timeout 30m | |
| # Bare Mach-O executables cannot be stapled (only bundles/pkgs/dmgs); | |
| # Gatekeeper verifies these online. Best-effort, never fatal. | |
| for BIN in out/bin/*; do xcrun stapler staple "$BIN" || true; done | |
| security delete-keychain "$KEYCHAIN" | |
| echo "signed=true" >> "$GITHUB_OUTPUT" | |
| - name: Package | |
| run: | | |
| VERSION="${GITHUB_REF_NAME}" | |
| mkdir -p dist | |
| tar -czf "dist/kuri-${VERSION}-${{ matrix.target }}.tar.gz" -C out/bin . | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: kuri-${{ matrix.target }} | |
| path: dist/*.tar.gz | |
| # ── Publish self-managed release channel ──────────────────────────────────── | |
| publish-channel: | |
| needs: [build-linux, build-macos] | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 20 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| ref: release-channel | |
| path: channel | |
| - uses: actions/download-artifact@v4 | |
| with: | |
| merge-multiple: true | |
| path: dist | |
| - name: Publish stable channel | |
| env: | |
| MACOS_SIGNED: ${{ needs.build-macos.outputs.signed }} | |
| run: | | |
| set -euo pipefail | |
| VERSION="${GITHUB_REF_NAME}" | |
| CHANNEL_DIR="channel/stable/${VERSION}" | |
| mkdir -p "$CHANNEL_DIR" | |
| cp dist/*.tar.gz "$CHANNEL_DIR/" | |
| python3 - <<'PY' | |
| from pathlib import Path | |
| import datetime | |
| import hashlib | |
| import json | |
| import os | |
| version = os.environ["GITHUB_REF_NAME"] | |
| commit = os.environ["GITHUB_SHA"] | |
| notarized = os.environ.get("MACOS_SIGNED") == "true" | |
| root = Path("channel") | |
| version_dir = root / "stable" / version | |
| base = "https://raw.githubusercontent.com/justrach/kuri/release-channel/stable" | |
| assets = {} | |
| for path in sorted(version_dir.glob("kuri-*.tar.gz")): | |
| target = path.name.removeprefix(f"kuri-{version}-").removesuffix(".tar.gz") | |
| assets[target] = { | |
| "url": f"{base}/{version}/{path.name}", | |
| "sha256": hashlib.sha256(path.read_bytes()).hexdigest(), | |
| "size": path.stat().st_size, | |
| "notarized": target.endswith("macos") and notarized, | |
| } | |
| manifest = { | |
| "channel": "stable", | |
| "version": version, | |
| "commit": commit, | |
| "published_at": datetime.datetime.now(datetime.timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z"), | |
| "install_url": f"{base}/install.sh", | |
| "assets": assets, | |
| } | |
| (root / "stable" / "latest.json").write_text(json.dumps(manifest, indent=2) + "\n") | |
| (version_dir / "manifest.json").write_text(json.dumps(manifest, indent=2) + "\n") | |
| with (version_dir / "sha256sums.txt").open("w") as out: | |
| for target, meta in assets.items(): | |
| out.write(f"{meta['sha256']} kuri-{version}-{target}.tar.gz\n") | |
| PY | |
| cat > channel/README.md <<'EOF' | |
| # Kuri Release Channel | |
| Self-managed release channel for `justrach/kuri`. GitHub Releases mirror the tagged assets, but the installer and stable manifest are still served from this branch. | |
| ## Stable | |
| ```sh | |
| curl -fsSL https://raw.githubusercontent.com/justrach/kuri/release-channel/stable/install.sh | sh | |
| ``` | |
| Manifest: | |
| - https://raw.githubusercontent.com/justrach/kuri/release-channel/stable/latest.json | |
| EOF | |
| cd channel | |
| git config user.name "github-actions[bot]" | |
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | |
| git add README.md stable | |
| git commit -m "release: publish stable ${VERSION}" || exit 0 | |
| git push origin HEAD:release-channel | |
| publish-github-release: | |
| needs: [build-linux, build-macos] | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 20 | |
| permissions: | |
| contents: write | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/download-artifact@v4 | |
| with: | |
| merge-multiple: true | |
| path: dist | |
| - name: Build release notes and checksums | |
| run: | | |
| set -euo pipefail | |
| VERSION="${GITHUB_REF_NAME}" | |
| python3 - <<'PY' | |
| from pathlib import Path | |
| import hashlib | |
| import os | |
| import re | |
| version = os.environ["GITHUB_REF_NAME"].removeprefix("v") | |
| changelog = Path("CHANGELOG.md").read_text() | |
| pattern = re.compile(rf"^## \[{re.escape(version)}\] — .*$", re.MULTILINE) | |
| match = pattern.search(changelog) | |
| if not match: | |
| raise SystemExit(f"missing changelog section for {version}") | |
| start = match.start() | |
| next_match = re.search(r"^## \[", changelog[match.end():], re.MULTILINE) | |
| end = len(changelog) if not next_match else match.end() + next_match.start() | |
| section = changelog[start:end].strip() | |
| body = section.split("\n", 1)[1].strip() | |
| if os.environ.get("MACOS_SIGNED") == "true": | |
| footer = "macOS assets in this release are signed and notarized." | |
| else: | |
| footer = ( | |
| "> **Note** — macOS assets in this release are **not** signed or notarized. " | |
| "Gatekeeper will quarantine them; clear it with " | |
| "`xattr -d com.apple.quarantine <binary>`." | |
| ) | |
| notes = "## " + os.environ["GITHUB_REF_NAME"] + "\n\n" + body + "\n\n" + footer + "\n" | |
| Path("dist/release-notes.md").write_text(notes) | |
| with Path(os.environ["SHA_FILE"]).open("w") as out: | |
| for path in sorted(Path("dist").glob("kuri-*.tar.gz")): | |
| out.write(f"{hashlib.sha256(path.read_bytes()).hexdigest()} {path.name}\n") | |
| PY | |
| env: | |
| SHA_FILE: dist/kuri-${{ github.ref_name }}-sha256sums.txt | |
| MACOS_SIGNED: ${{ needs.build-macos.outputs.signed }} | |
| - name: Publish GitHub Release | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| set -euo pipefail | |
| VERSION="${GITHUB_REF_NAME}" | |
| TITLE="${VERSION}" | |
| NOTES="dist/release-notes.md" | |
| ASSETS=(dist/*.tar.gz "dist/kuri-${VERSION}-sha256sums.txt") | |
| if gh release view "$VERSION" >/dev/null 2>&1; then | |
| gh release upload "$VERSION" "${ASSETS[@]}" --clobber | |
| gh release edit "$VERSION" --title "$TITLE" --notes-file "$NOTES" --latest | |
| else | |
| gh release create "$VERSION" "${ASSETS[@]}" --title "$TITLE" --notes-file "$NOTES" --latest | |
| fi |