Deploy production #11
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Deploy PRODUCTION — runs scripts/deploy-prod.sh (migrations + both workers to | |
| # the TOP-LEVEL wrangler config: custom domains cloud.parachute.computer + | |
| # u.parachute.computer; NO seeding) then the READ-ONLY prod smoke | |
| # (scripts/smoke-prod.ts — creates no accounts/vaults/notes). | |
| # | |
| # Triggers: manual workflow_dispatch ONLY (deploys the selected ref, default | |
| # branch by default) — see the note above the `on:` block for why the old | |
| # v*-tag trigger was retired. | |
| # | |
| # SECRET GATE: CLOUDFLARE_API_TOKEN lives in the `production` ENVIRONMENT | |
| # secrets (approval-gated); CLOUDFLARE_ACCOUNT_ID + DEV_SECRETS are repo | |
| # secrets. See deploy-staging.yml's header for the exact Cloudflare token | |
| # permissions and the DEV_SECRETS format | |
| # (prod's smoke only reads DEV_USER_EMAIL from it, to exercise the operator | |
| # magic-link path; deploy-prod.sh itself never seeds). When any secret is | |
| # missing the workflow warns and skips, exiting green. | |
| name: Deploy production | |
| # Deliberate-dispatch only. The old `push: tags: v*` trigger predates the | |
| # production environment gate: every rc tag (one per merged PR) would now | |
| # park a pending-approval run in Actions — pure approval fatigue. Prod | |
| # deploys are an explicit `gh workflow run deploy-prod.yml` + the owner's | |
| # approval click, nothing implicit. | |
| on: | |
| workflow_dispatch: | |
| concurrency: | |
| group: deploy-prod | |
| cancel-in-progress: false | |
| jobs: | |
| deploy: | |
| name: deploy + smoke (production) | |
| runs-on: ubuntu-latest | |
| # Deploy credentials live in the `production` GitHub Environment, which is | |
| # protected by a required-reviewer rule: every run PAUSES until the owner | |
| # approves it in the Actions UI. Dispatching ≠ deploying — the human gate | |
| # is enforced by GitHub, not by convention. | |
| environment: production | |
| timeout-minutes: 25 | |
| steps: | |
| - name: Check deploy secrets | |
| id: gate | |
| env: | |
| CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} | |
| CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} | |
| DEV_SECRETS: ${{ secrets.DEV_SECRETS }} | |
| run: | | |
| missing="" | |
| if [ -z "$CLOUDFLARE_API_TOKEN" ]; then missing="$missing CLOUDFLARE_API_TOKEN"; fi | |
| if [ -z "$CLOUDFLARE_ACCOUNT_ID" ]; then missing="$missing CLOUDFLARE_ACCOUNT_ID"; fi | |
| if [ -z "$DEV_SECRETS" ]; then missing="$missing DEV_SECRETS"; fi | |
| if [ -n "$missing" ]; then | |
| echo "::notice title=Production deploy skipped::Missing deploy secret(s):$missing. Deploy + smoke skipped (green by design). See the header of .github/workflows/deploy-staging.yml for what to set." | |
| { | |
| echo "## Production deploy skipped" | |
| echo "" | |
| echo "Missing deploy secret(s): \`$missing\`" | |
| echo "" | |
| echo "Set CLOUDFLARE_API_TOKEN under Settings → Environments (production/staging) and the rest under Settings → Secrets and variables → Actions, then re-run via workflow_dispatch. Token permissions and the DEV_SECRETS format are documented in deploy-staging.yml's header." | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| echo "ready=false" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "ready=true" >> "$GITHUB_OUTPUT" | |
| fi | |
| - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 | |
| if: steps.gate.outputs.ready == 'true' | |
| - name: Clone parachute-vault (sibling file dep for @openparachute/core) | |
| # workers/vault builds against file:../../../parachute-vault/core — a | |
| # sibling checkout outside this repo. Deploys build against | |
| # parachute-vault@main, matching local practice (the deploy script's | |
| # own `bun install` refreshes the copied dep from this clone). | |
| if: steps.gate.outputs.ready == 'true' | |
| run: | | |
| git clone --depth 1 https://github.com/ParachuteComputer/parachute-vault.git "$GITHUB_WORKSPACE/../parachute-vault" | |
| echo "parachute-vault @ $(git -C "$GITHUB_WORKSPACE/../parachute-vault" rev-parse HEAD)" | |
| - name: Fetch pinned Parachute App (SPA served by the identity worker) | |
| # scripts/spa-source.env is the single source pin. Fetching the exact | |
| # commit makes tagged production deploys deterministic. | |
| if: steps.gate.outputs.ready == 'true' | |
| run: | | |
| source scripts/spa-source.env | |
| APP_REPO="$GITHUB_WORKSPACE/../parachute-app" | |
| git init "$APP_REPO" | |
| git -C "$APP_REPO" remote add origin https://github.com/ParachuteComputer/parachute-app.git | |
| git -C "$APP_REPO" fetch --depth 1 origin "$SPA_APP_REF" | |
| git -C "$APP_REPO" checkout --detach FETCH_HEAD | |
| echo "parachute-app v$SPA_APP_VERSION @ $(git -C "$APP_REPO" rev-parse HEAD)" | |
| - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 | |
| if: steps.gate.outputs.ready == 'true' | |
| with: | |
| bun-version: 1.3.13 # keep consistent with local dev | |
| - name: Materialize pinned Hub door-contract dependency | |
| if: steps.gate.outputs.ready == 'true' | |
| run: bash scripts/materialize-door-contract.sh | |
| - name: Cache bun download cache | |
| if: steps.gate.outputs.ready == 'true' | |
| uses: actions/cache@caa296126883cff596d87d8935842f9db880ef25 # v5.1.0 | |
| with: | |
| path: ~/.bun/install/cache | |
| key: ${{ runner.os }}-bun-${{ hashFiles('package.json', 'workers/*/package.json') }} | |
| restore-keys: | | |
| ${{ runner.os }}-bun- | |
| - name: Materialize workers/identity/.dev-secrets | |
| # smoke-prod.ts reads DEV_USER_EMAIL from this file (operator | |
| # magic-link check). The deploy script itself never touches it. | |
| if: steps.gate.outputs.ready == 'true' | |
| env: | |
| DEV_SECRETS: ${{ secrets.DEV_SECRETS }} | |
| run: printf '%s\n' "$DEV_SECRETS" > workers/identity/.dev-secrets | |
| - name: Deploy production (migrations + both workers, NO seed) | |
| if: steps.gate.outputs.ready == 'true' | |
| env: | |
| CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} | |
| CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} | |
| run: bash scripts/deploy-prod.sh | |
| - name: Live smoke (read-only, production) | |
| if: steps.gate.outputs.ready == 'true' | |
| run: bun scripts/smoke-prod.ts |