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
66 changes: 45 additions & 21 deletions .bran-export.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"schema_version": 1,
"source_repository": "alphazede/bran-dev",
"source_commit": "4e28fd5f0d45d47b05f97bc35abbd6e2c30b9c61",
"source_commit": "32f37928f77b21b1d1a55a22862c91f61fce7ed0",
"version": "0.1.0",
"public_repository": "alphazede/bran",
"files": [
Expand All @@ -23,6 +23,12 @@
"bytes": 696,
"sha256": "75002eee19da62eeb11dbc9c86eb67d6fde08635d3277e41298e887305f92128"
},
{
"path": ".github/workflows/release.yml",
"mode": "100644",
"bytes": 8337,
"sha256": "ac6713baf1765339026fd05e1161e07eb1b943c21a6553daf77ad7081a73a1de"
},
{
"path": ".gitignore",
"mode": "100644",
Expand All @@ -32,14 +38,14 @@
{
"path": "Cargo.lock",
"mode": "100644",
"bytes": 6486,
"sha256": "7305ad8cdf4a72f852943c840893720a9781c1445ed955d5fb9b019fb8d680a1"
"bytes": 8934,
"sha256": "84d67f2d5d5ec36a738e56400dab679d139cf7bb15b1012b3f9d9a48606a1602"
},
{
"path": "Cargo.toml",
"mode": "100644",
"bytes": 96,
"sha256": "3322881f7489b12e80a2b7a727cec3d2173f9615f03dc6fbb5a28490037f6da3"
"bytes": 105,
"sha256": "b8ff81cee89b842d6970d0ecb1670c77dd98f28ddfacacc5564d430570768372"
},
{
"path": "LICENSE",
Expand All @@ -62,8 +68,8 @@
{
"path": "README.md",
"mode": "100644",
"bytes": 11524,
"sha256": "0baf93700cc37a3e0cdd501725e135f99d915c6b628889baf69b1d4d39bc878d"
"bytes": 9773,
"sha256": "e1e78f08b7f5bfe79f6a922d86df0d14de8a10d6e0771b2e602afda6ce502c3d"
},
{
"path": "assets/brand/bran-repository-raven.png",
Expand Down Expand Up @@ -254,8 +260,8 @@
{
"path": "crates/bran-core/src/lib.rs",
"mode": "100644",
"bytes": 24814,
"sha256": "dd2d8c4c2f66b171aa149423b805dd5c92de6d88b8ba4f6e5cd3f3587fafccd2"
"bytes": 25540,
"sha256": "7823d50d4b59746c09635472364a415123afadfac1845b3345250e8486f5118a"
},
{
"path": "crates/bran-core/src/metadata/mod.rs",
Expand Down Expand Up @@ -560,8 +566,8 @@
{
"path": "fixtures/release/valid-exact-release-manifest.json",
"mode": "100644",
"bytes": 2900,
"sha256": "5e47f7482fbcdd38ddf7d8e32f25e2bb83cefef8c3b1e948472ac4317be694da"
"bytes": 3067,
"sha256": "1f5ac7c18b2519b2132171250e4f163392bc966df5f18cc17c5d2c464c5ec669"
},
{
"path": "fixtures/repair/rollback-v1.json",
Expand Down Expand Up @@ -596,8 +602,8 @@
{
"path": "schemas/bran-release-manifest.schema.json",
"mode": "100644",
"bytes": 7321,
"sha256": "cba9b364722a0d68abb25eb54dbd6ef8b79333b377c639357d66165702bbf0d6"
"bytes": 7579,
"sha256": "e6425b8e29e09f399c72eb69e440002d3ba73f161b7551a883df08b36a493674"
},
{
"path": "schemas/bran-repository-policy.schema.json",
Expand Down Expand Up @@ -674,8 +680,8 @@
{
"path": "tools/ci/build-release.sh",
"mode": "100755",
"bytes": 4290,
"sha256": "97b70095b0a16e8eb186fb24d2cfbd3bce14063b331c1b746e4582f6c74a1577"
"bytes": 2854,
"sha256": "f84343135189fb33fcbcb41e55792a87f63731bc9e2a710fc0e0adf4636e0204"
},
{
"path": "tools/ci/check.sh",
Expand All @@ -698,20 +704,20 @@
{
"path": "tools/ci/release-check.sh",
"mode": "100755",
"bytes": 1093,
"sha256": "29360f0ac6fe9701eb42c3357428f135d8f62028aeb2fa2a7bccd9290de8aac5"
"bytes": 1471,
"sha256": "bf04ac9e19e67ba4e4b84255cb25d44b4f8980f8ec5e7f9839f8e01f19cd9bb9"
},
{
"path": "tools/ci/release_contract_check.py",
"mode": "100644",
"bytes": 11801,
"sha256": "36f0aa9098e350410d057cc1fe387d3e053816ab56c5658d6cdebbad5da1ccb2"
"bytes": 12318,
"sha256": "d37bb1c35618214d4d58b52d5e317c68bdd1864b0d9d097c748f62247aed5106"
},
{
"path": "tools/ci/release_seal.py",
"mode": "100755",
"bytes": 22511,
"sha256": "32063c65aaff99ddc39ed47fb07ace8b30a022d9f3e177ce35469978ab5fb302"
"bytes": 24663,
"sha256": "7d6fa282b8ca614a135dbf90e1504558b40e3195e8352d8f2abde334393eaee8"
},
{
"path": "tools/ci/test-budget.json",
Expand All @@ -724,6 +730,24 @@
"mode": "100644",
"bytes": 33505,
"sha256": "ff60cd69b4a5e7994004095441491fe24b3635159676ea4133955006b9187f1c"
},
{
"path": "xtask/Cargo.toml",
"mode": "100644",
"bytes": 264,
"sha256": "a4786c44a9e41ef709353d292d586628775a6007115bfa33d64b68aa57b5fd75"
},
{
"path": "xtask/src/archive.rs",
"mode": "100644",
"bytes": 11299,
"sha256": "b62ebac302bd3d718a29d6c22d2e7440780372ead11b313e8d7bf1a0177025b4"
},
{
"path": "xtask/src/main.rs",
"mode": "100644",
"bytes": 5272,
"sha256": "142d55253c8c8912015a0c70a35675adc7dc899ffe5a998809a695b7e70cf169"
}
]
}
216 changes: 216 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
name: release

on:
push:
tags:
- "bran-v*"

jobs:
build:
name: build ${{ matrix.target }}
runs-on: ${{ matrix.runner }}
# Least privilege: building needs to read the tag and nothing else. Only the
# sign job may write releases or mint an OIDC token.
permissions:
contents: read
strategy:
fail-fast: false
matrix:
include:
- target: x86_64-unknown-linux-gnu
runner: ubuntu-latest
- target: aarch64-unknown-linux-gnu
runner: ubuntu-24.04-arm
- target: x86_64-apple-darwin
runner: macos-13
- target: aarch64-apple-darwin
runner: macos-14
- target: x86_64-pc-windows-msvc
runner: windows-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable
with:
targets: ${{ matrix.target }}
# The xtask packages archives with only cargo; no python interpreter or
# /bin/sh shim is needed on any runner.
- name: Build release archive
run: cargo run --locked -p xtask -- package --target "${{ matrix.target }}" --tag "${{ github.ref_name }}" --dist dist
- uses: actions/upload-artifact@v4
with:
name: artifact-${{ matrix.target }}
path: dist/

sign:
name: Sign checksums and emit manifest
needs: build
runs-on: ubuntu-latest
permissions:
contents: write
id-token: write
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
pattern: artifact-*
path: dist
merge-multiple: true
- name: Produce SHA256SUMS
run: |
python3 - "${{ github.ref_name }}" <<'PY'
import hashlib
import pathlib
import sys

tag = sys.argv[1]
targets = [
"x86_64-unknown-linux-gnu",
"aarch64-unknown-linux-gnu",
"x86_64-apple-darwin",
"aarch64-apple-darwin",
"x86_64-pc-windows-msvc",
]
names = [
f"{tag}-{target}.zip" if target == "x86_64-pc-windows-msvc" else f"{tag}-{target}.tar.gz"
for target in targets
]
dist = pathlib.Path("dist")
lines = []
for name in sorted(names):
digest = hashlib.sha256((dist / name).read_bytes()).hexdigest()
lines.append(f"{digest} {name}")
(dist / "SHA256SUMS").write_text("\n".join(lines) + "\n", encoding="utf-8")
PY
- uses: sigstore/cosign-installer@398d4b0eeef1380460a10c8013a76f728fb906ac # v3
- name: Sign SHA256SUMS with cosign keyless
run: cosign sign-blob --yes --bundle dist/SHA256SUMS.sigstore dist/SHA256SUMS
- name: Emit bran-release-manifest.json
env:
TAG: ${{ github.ref_name }}
SOURCE_COMMIT: ${{ github.sha }}
CERTIFICATE_IDENTITY: https://github.com/alphazede/bran/.github/workflows/release.yml@refs/tags/${{ github.ref_name }}
run: |
python3 - <<'PY'
import hashlib
import json
import os
import pathlib
from datetime import datetime, timezone

tag = os.environ["TAG"]
source_commit = os.environ["SOURCE_COMMIT"]
certificate_identity = os.environ["CERTIFICATE_IDENTITY"]
issuer = "https://token.actions.githubusercontent.com"
dist = pathlib.Path("dist")

def digest(path):
return hashlib.sha256(path.read_bytes()).hexdigest()

def media_type(name):
return "application/zip" if name.endswith(".zip") else "application/gzip"

targets = [
"x86_64-unknown-linux-gnu",
"aarch64-unknown-linux-gnu",
"x86_64-apple-darwin",
"aarch64-apple-darwin",
"x86_64-pc-windows-msvc",
]
names = [
f"{tag}-{target}.zip" if target == "x86_64-pc-windows-msvc" else f"{tag}-{target}.tar.gz"
for target in targets
]
assets = [
{
"name": name,
"url": f"https://github.com/alphazede/bran/releases/download/{tag}/{name}",
"sha256": digest(dist / name),
"media_type": media_type(name),
}
for name in names
]
sums_digest = digest(dist / "SHA256SUMS")
sig_digest = digest(dist / "SHA256SUMS.sigstore")
assets += [
{"name": "SHA256SUMS",
"url": f"https://github.com/alphazede/bran/releases/download/{tag}/SHA256SUMS",
"sha256": sums_digest, "media_type": "text/plain"},
{"name": "SHA256SUMS.sigstore",
"url": f"https://github.com/alphazede/bran/releases/download/{tag}/SHA256SUMS.sigstore",
"sha256": sig_digest, "media_type": "application/vnd.dev.sigstore.bundle.v0.3+json"},
]
bundle = json.loads((dist / "SHA256SUMS.sigstore").read_text(encoding="utf-8"))
signed_at = datetime.fromtimestamp(
bundle["logEntry"]["integratedTime"], timezone.utc
).strftime("%Y-%m-%dT%H:%M:%SZ")
lockfile_digest = digest(pathlib.Path("Cargo.lock"))
manifest = {
"schema_version": "1.0.0",
"tag": tag,
"repository": "alphazede/bran",
"source_commit": source_commit,
"lockfile_sha256": lockfile_digest,
"immutable": True,
"manifest_asset": "bran-release-manifest.json",
"assets": assets,
"checksums": {"asset": "SHA256SUMS", "algorithm": "sha256", "sha256": sums_digest},
"signature": {
"asset": "SHA256SUMS.sigstore",
"format": "sigstore-bundle",
"certificate_identity": certificate_identity,
"certificate_oidc_issuer": issuer,
"signed_at": signed_at,
},
"provenance": {
"format": "https://slsa.dev/provenance/v1",
"predicate_type": "https://slsa.dev/provenance/v1",
"source_repository": "alphazede/bran",
"source_commit": source_commit,
"lockfile_sha256": lockfile_digest,
"build_type": "https://alphazede.dev/bran/build/v1",
},
}
(dist / "bran-release-manifest.json").write_text(
json.dumps(manifest, indent=2) + "\n", encoding="utf-8"
)
PY
# Validate the generated manifest against the shipped release contract
# before anything is published. A manifest that violates its own contract
# must never reach a release.
- name: Verify the generated manifest satisfies the release contract
run: |
python3 - <<'PY'
import json
import pathlib
import sys

sys.path.insert(0, "tools/ci")
import release_contract_check as contract

manifest = json.loads(
pathlib.Path("dist/bran-release-manifest.json").read_text(encoding="utf-8")
)
errors = contract.validate_manifest(manifest)
if errors:
print("FAIL generated manifest violates the release contract")
for error in errors:
print(f" {error}")
raise SystemExit(1)
print("PASS generated manifest satisfies the release contract")
PY
- uses: actions/upload-artifact@v4
with:
name: release-files
path: dist/
# Publish the release. Without this the assets exist only as workflow
# artifacts and every releases/download URL in the manifest would 404.
- name: Publish the release
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ github.ref_name }}
run: |
gh release create "$TAG" \
--repo "${{ github.repository }}" \
--title "$TAG" \
--notes "Release $TAG. Verify with cosign: see the Releases section of the README." \
dist/*
Loading