diff --git a/.github/workflows/hsm-collector-registry-publish.yml b/.github/workflows/hsm-collector-registry-publish.yml new file mode 100644 index 000000000..05e73801b --- /dev/null +++ b/.github/workflows/hsm-collector-registry-publish.yml @@ -0,0 +1,203 @@ +name: hsm-collector registry publish + +# Automates publishing a new hsm-collector version into this repo's vcpkg registry, so there is no +# manual "recipe" to remember. Push a `collector-v` tag (or run this manually) and it: +# 1. computes the source tarball SHA512 for that tag, +# 2. updates ports/hsm-collector/ (portfile REF + SHA512, vcpkg.json version), +# 3. refreshes the version database (versions/), +# 4. builds+installs the updated port to prove it's valid, then +# 5. opens a PR to master. +# +# Scope / limits: publishes SOURCE versions only — a port-only fix (no source change) needs a manual +# port-version bump. Two publishes opened before either merges will conflict in versions/ (rebase the +# second). Build validation runs on Linux; the Windows/MSVC consumer path is the hsm-collector-registry +# check, which a human runs before merge (a GITHUB_TOKEN-opened PR doesn't auto-trigger it). +# Re-runs force-push the version's branch, so any manual commit pushed onto an open auto-PR is lost. + +on: + push: + tags: + - 'collector-v*' + workflow_dispatch: + inputs: + version: + description: 'Version to publish, e.g. 0.6.3 (a matching collector-v tag must already exist).' + required: true + dry_run: + description: 'Only compute + validate + show the diff; do not open a PR.' + type: boolean + default: true + +permissions: + contents: write + pull-requests: write + +# Serialize publishes so two tag pushes can't race on the shared version database. +concurrency: + group: hsm-collector-registry-publish + cancel-in-progress: false + +jobs: + publish: + runs-on: ubuntu-latest + env: + VCPKG_DISABLE_METRICS: '1' + steps: + - uses: actions/checkout@v4 + with: + ref: master + fetch-depth: 1 + + - name: Resolve + validate version + id: v + # Pass the dispatch input through env (never interpolate ${{ }} into the shell body — that + # would let an input like $(...) run on the runner). GITHUB_REF_NAME is already a safe env var. + env: + EVENT: ${{ github.event_name }} + INPUT_VERSION: ${{ github.event.inputs.version }} + run: | + set -euo pipefail + if [ "$EVENT" = "push" ]; then + tag="$GITHUB_REF_NAME" + else + tag="collector-v${INPUT_VERSION}" + fi + ver="${tag#collector-v}" + if ! printf '%s' "$ver" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then + echo "::error::bad version '$ver' — expected X.Y.Z (tag collector-vX.Y.Z)"; exit 1 + fi + echo "tag=$tag" >> "$GITHUB_OUTPUT" + echo "ver=$ver" >> "$GITHUB_OUTPUT" + echo "Publishing hsm-collector $ver (source tag $tag)" + + - name: Compute source tarball SHA512 + id: sha + env: + TAG: ${{ steps.v.outputs.tag }} + REPO: ${{ github.repository }} + run: | + set -euo pipefail + url="https://github.com/${REPO}/archive/${TAG}.tar.gz" + # Download to a file (with -f so a missing tag → non-zero exit under set -e) rather than piping + # curl into sha512sum, where a 404 would otherwise be hashed as an empty stream. + curl -fsSL --retry 3 --retry-connrefused --max-time 300 "$url" -o source.tgz + sha=$(sha512sum source.tgz | awk '{print $1}') + empty="cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e" + if [ "${#sha}" -ne 128 ] || [ "$sha" = "$empty" ]; then + echo "::error::bad SHA512 for $url (does the tag exist?)"; exit 1 + fi + rm -f source.tgz + echo "sha=$sha" >> "$GITHUB_OUTPUT" + + - name: Prepare registry update + id: prep + env: + TAG: ${{ steps.v.outputs.tag }} + VER: ${{ steps.v.outputs.ver }} + SHA: ${{ steps.sha.outputs.sha }} + run: | + set -euo pipefail + br="registry/hsm-collector-${VER}" + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git checkout -b "$br" + + sed -i -E "s|REF collector-v[0-9]+\.[0-9]+\.[0-9]+|REF ${TAG}|" ports/hsm-collector/portfile.cmake + sed -i -E "s|SHA512 [0-9a-f]{128}|SHA512 ${SHA}|" ports/hsm-collector/portfile.cmake + sed -i -E "s|(\"version\"[[:space:]]*:[[:space:]]*\")[0-9]+\.[0-9]+\.[0-9]+(\")|\1${VER}\2|" ports/hsm-collector/vcpkg.json + + # Commit the port first so the recorded git-tree is the NEW port content. + git add ports/hsm-collector + git commit -q -m "vcpkg registry: publish hsm-collector ${VER}" + tree=$(git rev-parse "HEAD:ports/hsm-collector") + + VER="$VER" TREE="$tree" python3 - <<'PY' + import json, os, sys + ver, tree = os.environ['VER'], os.environ['TREE'] + f = 'versions/h-/hsm-collector.json' + d = json.load(open(f)) + # vcpkg version->git-tree mappings are immutable. If this version is already published with a + # DIFFERENT tree (e.g. its source tag was recreated with new content), refuse — bump instead. + existing = next((e for e in d['versions'] if e.get('version') == ver), None) + if existing and existing.get('git-tree') != tree: + print(f"::error::hsm-collector {ver} is already published with git-tree " + f"{existing['git-tree']}; refusing to rewrite it to {tree}. Bump the version.") + sys.exit(1) + b = json.load(open('versions/baseline.json')) + cur = b['default']['hsm-collector'].get('baseline', '0.0.0') + semver = lambda v: tuple(int(x) for x in v.split('.')) + # Only ADVANCE the baseline. A strictly newer version resets port-version; re-publishing the + # SAME baseline version leaves port-version alone (a manual port-only bump must not be + # clobbered); an OLDER version (backport) still gets its versions/ entry (below) but never + # rolls the default that consumers resolve backward. + if semver(ver) > semver(cur): + b['default']['hsm-collector']['baseline'] = ver + b['default']['hsm-collector']['port-version'] = 0 + with open('versions/baseline.json', 'w') as fh: + json.dump(b, fh, indent=2); fh.write('\n') + elif semver(ver) < semver(cur): + print(f"::warning::published {ver}, but the baseline stays at {cur} (newer) — not rolling back.") + else: # ver == cur: baseline already correct; leave baseline.json (and its port-version) alone. + if b['default']['hsm-collector'].get('port-version', 0): + print(f"::warning::re-published {ver} while baseline port-version is " + f"{b['default']['hsm-collector']['port-version']}; the versions/ entry records port-version 0. " + f"Port-only changes need a manual port-version bump (see the header note).") + # Idempotent (pre-merge re-runs): drop any existing entry for this version, then prepend it + # (newest-first — the sanity-check step's `grep ... | head -1` relies on this ordering). + d['versions'] = [e for e in d['versions'] if e.get('version') != ver] + d['versions'].insert(0, {"version": ver, "git-tree": tree}) + with open(f, 'w') as fh: + json.dump(d, fh, indent=2); fh.write('\n') + PY + + # Fold versions/ into the same commit (ports/ tree — and thus the recorded git-tree — is + # unchanged by touching versions/). + git add versions + git commit -q --amend --no-edit + echo "branch=$br" >> "$GITHUB_OUTPUT" + + - name: Sanity-check version DB git-tree + env: + VER: ${{ steps.v.outputs.ver }} + run: | + set -euo pipefail + # Read the git-tree recorded for THIS version (order-independent — not `head -1`). + recorded=$(python3 -c "import json,os; d=json.load(open('versions/h-/hsm-collector.json')); print(next(e['git-tree'] for e in d['versions'] if e['version']==os.environ['VER']))") + actual=$(git rev-parse "HEAD:ports/hsm-collector") + echo "recorded=$recorded actual=$actual" + [ "$recorded" = "$actual" ] + + - name: Set up vcpkg + uses: lukka/run-vcpkg@v11 + with: + vcpkgGitCommitId: 6f29f12e82a8293156836ad81cc9bf5af41fe836 + + - name: Validate the updated port builds + installs + # Proves the refreshed SHA512 + REF actually resolve and the collector builds from them, so the + # PR is known-good even though (see below) the separate registry check won't auto-run on it. + env: + VCPKG_BINARY_SOURCES: clear + run: | + "$VCPKG_ROOT/vcpkg" install "hsm-collector[http]" --overlay-ports=ports + + - name: Show the computed change + run: git show --stat HEAD + + - name: Open PR + if: github.event_name == 'push' || github.event.inputs.dry_run == 'false' + env: + GH_TOKEN: ${{ github.token }} + BR: ${{ steps.prep.outputs.branch }} + VER: ${{ steps.v.outputs.ver }} + run: | + set -euo pipefail + git push -f -u origin "$BR" + # Check for an OPEN PR explicitly (gh pr view also resolves merged/closed ones) rather than + # masking every gh failure with `|| echo`. + if [ -n "$(gh pr list --head "$BR" --state open --json number --jq '.[].number')" ]; then + echo "A PR for $BR is already open; pushed the refreshed branch to it." + else + gh pr create --base master --head "$BR" \ + --title "vcpkg registry: hsm-collector ${VER}" \ + --body "Automated by \`hsm-collector-registry-publish\`: the port now fetches \`collector-v${VER}\` (SHA512 refreshed) and the version DB is updated. The updated port was **built + installed on Linux in the publish run** (validates the SHA512/REF resolve and the collector compiles with \`[http]\`). The consumer-facing **hsm-collector-registry** check is Windows/MSVC and does NOT auto-run on a \`GITHUB_TOKEN\` PR — **run it manually** (Actions -> hsm-collector-registry -> Run workflow on this branch) before merging to cover the Windows path." + fi