name: Publish Package Repositories "on": workflow_dispatch: inputs: channel: description: "Repository channel expected from the trusted release manifest" type: choice options: - stable - experimental required: true default: stable run_id: description: "Exact successful Release run ID (required for experimental)" required: false default: "" permissions: contents: read actions: read jobs: publish: runs-on: ubuntu-latest timeout-minutes: 25 steps: - name: Checkout publication policy uses: actions/checkout@v4 with: fetch-depth: 1 submodules: false - name: Validate and resolve trusted Release run id: resolve-run shell: bash env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} REQUESTED_CHANNEL: ${{ inputs.channel }} REQUESTED_RUN_ID: ${{ inputs.run_id }} run: | set -euo pipefail if [ "$REQUESTED_CHANNEL" != stable ] && [ "$REQUESTED_CHANNEL" != experimental ]; then echo "::error::Missing or unsupported repository channel: $REQUESTED_CHANNEL" exit 1 fi if [ "$REQUESTED_CHANNEL" = experimental ] && [ -z "$REQUESTED_RUN_ID" ]; then echo "::error::Experimental publication requires an exact Release run ID" exit 1 fi RUN_ID="$REQUESTED_RUN_ID" if [ -z "$RUN_ID" ]; then RUN_ID=$(gh api "repos/${{ github.repository }}/actions/workflows/release.yml/runs?per_page=100" \ --jq '.workflow_runs[] | select(.conclusion == "success" and .event == "push") | .id' | head -1) if [ -z "$RUN_ID" ]; then echo "::error::No successful tag-triggered Release workflow run found" exit 1 fi fi if ! [[ "$RUN_ID" =~ ^[1-9][0-9]*$ ]]; then echo "::error::run_id must be a positive numeric Actions run ID" exit 1 fi RUN_INFO=$(gh api "repos/${{ github.repository }}/actions/runs/${RUN_ID}" \ --jq '{conclusion: .conclusion, status: .status, event: .event, path: .path, run_attempt: .run_attempt}') CONCLUSION=$(jq -r '.conclusion' <<<"$RUN_INFO") STATUS=$(jq -r '.status' <<<"$RUN_INFO") EVENT=$(jq -r '.event' <<<"$RUN_INFO") WORKFLOW_PATH=$(jq -r '.path' <<<"$RUN_INFO") RUN_ATTEMPT=$(jq -r '.run_attempt' <<<"$RUN_INFO") if [ "$STATUS" != completed ] || [ "$CONCLUSION" != success ]; then echo "::error::Release run $RUN_ID is not a completed success" exit 1 fi if [ "$EVENT" = pull_request ] || [ "$EVENT" = pull_request_target ]; then echo "::error::Refusing repository artifacts from a pull-request run" exit 1 fi if [ "$WORKFLOW_PATH" != ".github/workflows/release.yml" ]; then echo "::error::Run $RUN_ID belongs to $WORKFLOW_PATH, not release.yml" exit 1 fi if ! [[ "$RUN_ATTEMPT" =~ ^[1-9][0-9]*$ ]]; then echo "::error::Release run has invalid attempt metadata" exit 1 fi echo "run_id=$RUN_ID" >> "$GITHUB_OUTPUT" echo "run_attempt=$RUN_ATTEMPT" >> "$GITHUB_OUTPUT" - name: Download trusted release manifest uses: actions/download-artifact@v4 with: run-id: ${{ steps.resolve-run.outputs.run_id }} name: release-manifest path: trusted-manifest github-token: ${{ secrets.GITHUB_TOKEN }} - name: Download APT repository artifact uses: actions/download-artifact@v4 with: run-id: ${{ steps.resolve-run.outputs.run_id }} name: apt-repo path: repository-input/aptrepo github-token: ${{ secrets.GITHUB_TOKEN }} - name: Download YUM repository artifact uses: actions/download-artifact@v4 with: run-id: ${{ steps.resolve-run.outputs.run_id }} name: yum-repo path: repository-input/yumrepo github-token: ${{ secrets.GITHUB_TOKEN }} - name: Download Arch repository artifact uses: actions/download-artifact@v4 with: run-id: ${{ steps.resolve-run.outputs.run_id }} name: arch-repo path: repository-input/archrepo github-token: ${{ secrets.GITHUB_TOKEN }} - name: Publish validated channel to R2 env: STABLE_CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN || secrets.R2_SECRET_ACCESS_KEY }} STABLE_CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} EXPERIMENTAL_CLOUDFLARE_API_TOKEN: ${{ secrets.EXPERIMENTAL_CLOUDFLARE_API_TOKEN || secrets.CLOUDFLARE_API_TOKEN || secrets.R2_SECRET_ACCESS_KEY }} EXPERIMENTAL_CLOUDFLARE_ACCOUNT_ID: ${{ secrets.EXPERIMENTAL_CLOUDFLARE_ACCOUNT_ID || secrets.CLOUDFLARE_ACCOUNT_ID }} STABLE_GPG_PUBLIC_KEY_B64: ${{ secrets.GPG_PUBLIC_KEY_B64 || '' }} EXPERIMENTAL_GPG_PUBLIC_KEY_B64: ${{ secrets.EXPERIMENTAL_GPG_PUBLIC_KEY_B64 || secrets.GPG_PUBLIC_KEY_B64 || '' }} REQUESTED_CHANNEL: ${{ inputs.channel }} shell: bash run: | set -euo pipefail if [ "$REQUESTED_CHANNEL" = stable ]; then CLOUDFLARE_API_TOKEN="$STABLE_CLOUDFLARE_API_TOKEN" CLOUDFLARE_ACCOUNT_ID="$STABLE_CLOUDFLARE_ACCOUNT_ID" GPG_PUBLIC_KEY_B64="$STABLE_GPG_PUBLIC_KEY_B64" else CLOUDFLARE_API_TOKEN="$EXPERIMENTAL_CLOUDFLARE_API_TOKEN" CLOUDFLARE_ACCOUNT_ID="$EXPERIMENTAL_CLOUDFLARE_ACCOUNT_ID" GPG_PUBLIC_KEY_B64="$EXPERIMENTAL_GPG_PUBLIC_KEY_B64" fi export CLOUDFLARE_API_TOKEN CLOUDFLARE_ACCOUNT_ID unset STABLE_CLOUDFLARE_API_TOKEN STABLE_CLOUDFLARE_ACCOUNT_ID unset EXPERIMENTAL_CLOUDFLARE_API_TOKEN EXPERIMENTAL_CLOUDFLARE_ACCOUNT_ID unset STABLE_GPG_PUBLIC_KEY_B64 EXPERIMENTAL_GPG_PUBLIC_KEY_B64 if [ -z "$CLOUDFLARE_API_TOKEN" ] || [ -z "$CLOUDFLARE_ACCOUNT_ID" ]; then echo "Missing channel-specific Cloudflare R2 authority" >&2 exit 1 fi WRANGLER_ROOT="${RUNNER_TEMP:-/tmp}/wrangler-cli" WRANGLER_BIN="$WRANGLER_ROOT/node_modules/.bin/wrangler" if [ ! -x "$WRANGLER_BIN" ]; then npm install --prefix "$WRANGLER_ROOT" wrangler@4.69.0 >/dev/null fi "$WRANGLER_BIN" --version >&2 public_key_args=() if [ -n "$GPG_PUBLIC_KEY_B64" ]; then echo "$GPG_PUBLIC_KEY_B64" | base64 -d > channel-gpg.key public_key_args=(--public-key channel-gpg.key) fi python3 scripts/ci/publish_repository.py \ --repo-root repository-input \ --manifest trusted-manifest/latest.json \ --expected-channel "$REQUESTED_CHANNEL" \ --selected-run-id "${{ steps.resolve-run.outputs.run_id }}" \ --selected-run-attempt "${{ steps.resolve-run.outputs.run_attempt }}" \ --bucket yams-repository \ --wrangler "$WRANGLER_BIN" \ "${public_key_args[@]}" echo "Published $REQUESTED_CHANNEL without pruning any channel objects." >&2