From ceb930a648a88e954d2e4644e14045b5d48ed69d Mon Sep 17 00:00:00 2001 From: Luqman Bello Date: Mon, 20 Jul 2026 16:48:45 +0200 Subject: [PATCH 1/2] chore: Dispatch promotions repo instead of GitLab --- .github/actions/release/action.yml | 15 ++++++-------- .github/workflows/deployment.yml | 14 +++++++++++-- scripts/prepare_production_deployment.sh | 26 ++++++++++++++++-------- 3 files changed, 35 insertions(+), 20 deletions(-) diff --git a/.github/actions/release/action.yml b/.github/actions/release/action.yml index 662802f11..7208dab5f 100644 --- a/.github/actions/release/action.yml +++ b/.github/actions/release/action.yml @@ -9,14 +9,11 @@ inputs: bucket-name: description: S3 bucket name required: true - prod-deployment-hook-token: - description: Token for the webhook that triggers the deployment to production + promotions-token: + description: GitHub App token used to dispatch the production promotion workflow required: true - prod-deployment-hook-url: - description: URL of the webhook that triggers the deployment to production - required: true - react-app-rpc-token: - description: Infura RPC API token + promotions-repo: + description: Repository hosting the gated production promotion workflows (owner/name) required: true react-app-tenderly-org-name: description: Tenderly simulation organization name @@ -59,8 +56,8 @@ runs: git tag -f last-release shell: bash env: - PROD_DEPLOYMENT_HOOK_TOKEN: ${{ inputs.prod-deployment-hook-token }} - PROD_DEPLOYMENT_HOOK_URL: ${{ inputs.prod-deployment-hook-url }} + GH_TOKEN: ${{ inputs.promotions-token }} + PROMOTIONS_REPO: ${{ inputs.promotions-repo }} BUCKET_NAME: ${{ inputs.bucket-name }} GITHUB_TOKEN: ${{ inputs.github-token }} REACT_APP_TENDERLY_ORG_NAME: ${{ inputs.react-app-tenderly-org-name }} diff --git a/.github/workflows/deployment.yml b/.github/workflows/deployment.yml index e0e064d0c..c67c5a4d1 100644 --- a/.github/workflows/deployment.yml +++ b/.github/workflows/deployment.yml @@ -140,12 +140,22 @@ jobs: role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME_STAGING }} aws-region: ${{ secrets.AWS_DEFAULT_REGION }} + - name: Generate promotions repo token + id: promotions_token + continue-on-error: true + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 + with: + app-id: ${{ secrets.PROMOTIONS_APP_ID }} + private-key: ${{ secrets.PROMOTIONS_APP_PRIVATE_KEY }} + repositories: ${{ secrets.PROMOTIONS_REPO }} + permission-actions: write + - name: 'Deploy production' uses: ./.github/actions/release with: github-token: ${{ secrets.GITHUB_TOKEN }} - prod-deployment-hook-token: ${{ secrets.PROD_DEPLOYMENT_HOOK_TOKEN }} - prod-deployment-hook-url: ${{ secrets.PROD_DEPLOYMENT_HOOK_URL }} + promotions-token: ${{ steps.promotions_token.outputs.token }} + promotions-repo: ${{ github.repository_owner }}/${{ secrets.PROMOTIONS_REPO }} bucket-name: ${{ secrets.STAGING_BUCKET_NAME }}/releases react-app-tenderly-org-name: ${{ secrets.REACT_APP_TENDERLY_ORG_NAME }} react-app-tenderly-project-name: ${{ secrets.REACT_APP_TENDERLY_PROJECT_NAME }} diff --git a/scripts/prepare_production_deployment.sh b/scripts/prepare_production_deployment.sh index b41651476..57ab1564c 100755 --- a/scripts/prepare_production_deployment.sh +++ b/scripts/prepare_production_deployment.sh @@ -3,17 +3,25 @@ set -ev # Only: -# - Security env variables are available. -if [ -n "$PROD_DEPLOYMENT_HOOK_TOKEN" ] && [ -n "$PROD_DEPLOYMENT_HOOK_URL" ] +# - GH_TOKEN and PROMOTIONS_REPO are available. +if [ -n "$GH_TOKEN" ] && [ -n "$PROMOTIONS_REPO" ] then APP_NAME="$(basename $(pwd))" PACKAGE_VERSION=$(sed -nr 's/^\s*\"version": "([0-9]{1,}\.[0-9]{1,}.*)",$/\1/p' package.json) - curl --silent --output /dev/null --write-out "%{http_code}" -X POST \ - -F token="$PROD_DEPLOYMENT_HOOK_TOKEN" \ - -F ref=master \ - -F "variables[TRIGGER_RELEASE_APP_NAME]=$APP_NAME" \ - -F "variables[TRIGGER_RELEASE_COMMIT_TAG]=$PACKAGE_VERSION" \ - $PROD_DEPLOYMENT_HOOK_URL + # --ref is required: without it gh resolves the default branch via GraphQL, + # which the app token (actions:write, metadata:read only) is not allowed to do. + # A failed dispatch must not fail this script: it runs mid nx-chain, before + # `git tag -f last-release`, and a hard exit would leave the release half-done. + # The error annotation stays visible on the run; the promotion can be + # dispatched manually from safe-production-promotions. + if ! gh workflow run react-apps-production.yml \ + --repo "$PROMOTIONS_REPO" \ + --ref main \ + -f "app=$APP_NAME" \ + -f "tag=$PACKAGE_VERSION" + then + echo "::error::Failed to dispatch production deployment for $APP_NAME $PACKAGE_VERSION" + fi else - echo "[ERROR] Production deployment could not be prepared" + echo "::warning::Production deployment could not be prepared: GH_TOKEN or PROMOTIONS_REPO missing" fi From dc4d2f503e6958473d218952eac4e13f485d7d22 Mon Sep 17 00:00:00 2001 From: Luqman Bello Date: Mon, 20 Jul 2026 17:22:18 +0200 Subject: [PATCH 2/2] fix: Skip tx-builder dispatch and note token lifetime --- .github/workflows/deployment.yml | 4 ++++ scripts/prepare_production_deployment.sh | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/.github/workflows/deployment.yml b/.github/workflows/deployment.yml index c67c5a4d1..6b68d3e4d 100644 --- a/.github/workflows/deployment.yml +++ b/.github/workflows/deployment.yml @@ -140,6 +140,10 @@ jobs: role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME_STAGING }} aws-region: ${{ secrets.AWS_DEFAULT_REGION }} + # Minted here because composite-action steps cannot read secrets directly. + # Installation tokens live 60 minutes and the dispatch is the last nx target; + # historical release runs complete in 3-9 minutes, so expiry is not a + # practical concern. - name: Generate promotions repo token id: promotions_token continue-on-error: true diff --git a/scripts/prepare_production_deployment.sh b/scripts/prepare_production_deployment.sh index 57ab1564c..eec2a5e01 100755 --- a/scripts/prepare_production_deployment.sh +++ b/scripts/prepare_production_deployment.sh @@ -8,6 +8,14 @@ if [ -n "$GH_TOKEN" ] && [ -n "$PROMOTIONS_REPO" ] then APP_NAME="$(basename $(pwd))" PACKAGE_VERSION=$(sed -nr 's/^\s*\"version": "([0-9]{1,}\.[0-9]{1,}.*)",$/\1/p' package.json) + # tx-builder promotes via safe-tx-builder-tf (built from safe-wallet-monorepo + # releases) since 2026-03; dispatching it here would only trip the promotion + # workflow's app guard. + if [ "$APP_NAME" = "tx-builder" ] + then + echo "Skipping tx-builder: it deploys via safe-tx-builder-tf" + exit 0 + fi # --ref is required: without it gh resolves the default branch via GraphQL, # which the app token (actions:write, metadata:read only) is not allowed to do. # A failed dispatch must not fail this script: it runs mid nx-chain, before