diff --git a/.github/workflows/upload-release.yml b/.github/workflows/upload-release.yml new file mode 100644 index 00000000..aaeb6573 --- /dev/null +++ b/.github/workflows/upload-release.yml @@ -0,0 +1,66 @@ +name: Upload release + +on: + workflow_dispatch: + inputs: + tag: + description: 'Existing release tag (e.g. v0.18.4)' + required: true + type: string + +concurrency: + group: upload-release-${{ inputs.tag }} + cancel-in-progress: false + +jobs: + upload: + runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + contents: read + id-token: write + env: + RELEASE_TAG: ${{ inputs.tag }} + steps: + - name: Validate release tag + shell: bash + run: | + if [[ ! "$RELEASE_TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?$ ]]; then + echo "Invalid release tag: $RELEASE_TAG" >&2 + exit 1 + fi + - uses: actions/checkout@v4 + with: + persist-credentials: false + - name: Download and verify release assets + shell: bash + env: + GH_TOKEN: ${{ github.token }} + run: | + artifacts=( + mutagen-agents.tar.gz + mutagen-darwin-amd64 + mutagen-darwin-arm64 + mutagen-windows-amd64.exe + mutagen-windows-arm64.exe + ) + patterns=(--pattern SHA256SUMS) + for artifact in "${artifacts[@]}"; do + patterns+=(--pattern "$artifact") + done + gh release download "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" \ + --dir artifacts "${patterns[@]}" + cd artifacts + sha256sum "${artifacts[@]}" | diff - SHA256SUMS + - uses: google-github-actions/auth@v3 + with: + workload_identity_provider: ${{ vars.GCP_CODE_SIGNING_WORKLOAD_ID_PROVIDER }} + service_account: ${{ vars.GCP_CODE_SIGNING_SERVICE_ACCOUNT }} + - uses: google-github-actions/setup-gcloud@v3 + - name: Upload release assets + shell: bash + run: | + gcloud storage cp --if-generation-match=0 artifacts/mutagen-* \ + "gs://coder-desktop/mutagen/$RELEASE_TAG/" + gcloud storage cp --if-generation-match=0 artifacts/SHA256SUMS \ + "gs://coder-desktop/mutagen/$RELEASE_TAG/" diff --git a/RELEASING.md b/RELEASING.md index ca0600a8..d346e725 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -23,12 +23,16 @@ Desktop. All of them are produced by the release with them attached. Running the workflow manually from the Actions tab builds the same artifacts from any ref and attaches them to the workflow run instead, which is useful for a dry run. -3. Copy the artifacts to the bucket that Coder Desktop reads from: +3. Run the [Upload release workflow](.github/workflows/upload-release.yml) + manually from the Actions tab, with the existing release tag (for example, + `v0.18.4`) as the `tag` input. It downloads the five release payloads and + `SHA256SUMS`, verifies the complete checksum manifest, and uploads the files + to `gs://coder-desktop/mutagen//` without rebuilding or re-signing them. + The bucket permissions described below must be in place first. - ```bash - gh release download v0.18.4 -R coder/mutagen -p 'mutagen-*' -D mutagen-v0.18.4 - gsutil cp mutagen-v0.18.4/* gs://coder-desktop/mutagen/v0.18.4/ - ``` + Uploads refuse to overwrite existing objects. Re-running a successful + upload fails; after a partial failure, an operator must inspect and remove + the partial upload before retrying. 4. Bump the Mutagen version in Coder Desktop: `$mutagenVersion` in `scripts/Get-Mutagen.ps1` (coder/coder-desktop-windows) and @@ -60,3 +64,17 @@ Federation. It needs the following repository configuration: The service account, its Cloud KMS roles, and its workload identity binding for this repository are managed in coder/gcp under `projects/production/coder-ci`. + +## Upload setup + +The upload workflow reuses the signing workflow's Workload Identity Federation +variables and service account. In addition to its signing permissions, the +account needs `roles/storage.objectCreator` on the `coder-desktop` bucket, +with an IAM condition limiting writes to objects whose resource name starts +with `projects/_/buckets/coder-desktop/objects/mutagen/`. + +The `coder-desktop` bucket already exists in project `coder-ci`. Its bucket +resource, IAM policy, and import declarations are managed in coder/gcp under +`projects/production/coder-ci`. Add the uploader binding to that existing +policy before running the upload workflow; do not create another bucket. +No service account key or new GitHub secret is needed.