chore(ci): GitHub owner maonakamoto -> catomean #586
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
| name: Deploy production app | |
| # Push to main → build + rsync to production app revampit.orangecat.ch. | |
| # One-time setup (Settings → Secrets → Actions): | |
| # HETZNER_SSH_PRIVATE_KEY — private key for ubuntu@167.233.22.31 | |
| # SELFHOST_ENV — full contents of .env.selfhost.local (multiline OK) | |
| # AUTH_TEST_USER_PASSWORD — non-admin E2E account (butaeff@gmail.com) | |
| # AUTH_TEST_ADMIN_PASSWORD — staff E2E account (georgy.butaev@revamp-it.ch) | |
| # Without deploy secrets the job logs a notice and exits cleanly. | |
| # Without E2E passwords the post-deploy inventory step is skipped. | |
| on: | |
| workflow_dispatch: {} | |
| push: | |
| branches: [main] | |
| concurrency: | |
| group: deploy-selfhost-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| deploy: | |
| name: revampit.orangecat.ch | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 45 | |
| outputs: | |
| deployed: ${{ steps.mark-deployed.outputs.deployed }} | |
| env: | |
| HETZNER_SSH_PRIVATE_KEY: ${{ secrets.HETZNER_SSH_PRIVATE_KEY }} | |
| SELFHOST_ENV: ${{ secrets.SELFHOST_ENV }} | |
| steps: | |
| - name: Check secrets configured | |
| id: secrets | |
| run: | | |
| if [ -z "$HETZNER_SSH_PRIVATE_KEY" ] || [ -z "$SELFHOST_ENV" ]; then | |
| echo "::notice::HETZNER_SSH_PRIVATE_KEY and/or SELFHOST_ENV not set — skipping self-host deploy. Push from a machine with .env.selfhost.local still deploys via the pre-push hook." | |
| echo "configured=false" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "configured=true" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Checkout code | |
| if: steps.secrets.outputs.configured == 'true' | |
| uses: actions/checkout@v7 | |
| - name: Setup Node.js | |
| if: steps.secrets.outputs.configured == 'true' | |
| uses: actions/setup-node@v7 | |
| with: | |
| node-version: '20' | |
| cache: npm | |
| - name: Install dependencies | |
| if: steps.secrets.outputs.configured == 'true' | |
| run: npm ci | |
| # Persist Next.js's incremental compiler cache across runs. The build runs | |
| # on this ephemeral runner, so without this every build is COLD (full | |
| # recompile). restore-keys falls back to the latest cache for the same | |
| # lockfile, making subsequent builds incremental (warm). | |
| - name: Cache Next.js build | |
| if: steps.secrets.outputs.configured == 'true' | |
| uses: actions/cache@v6 | |
| with: | |
| path: .next/cache | |
| key: nextjs-${{ runner.os }}-${{ hashFiles('package-lock.json') }}-${{ github.sha }} | |
| restore-keys: | | |
| nextjs-${{ runner.os }}-${{ hashFiles('package-lock.json') }}- | |
| # ONE definition of "verified" — the same bundle ci.yml runs, called | |
| # verbatim. These four steps used to be re-inlined here, which quietly made | |
| # the DEPLOY gate weaker than the MERGE gate: it omitted `lint:chrome` | |
| # (the card-shell ratchet) and ran only the i18n subset instead of the | |
| # full unit suite. A regression those cover could not block a merge but | |
| # could still ship. `verify` = lint + umlauts + chrome + typecheck + test | |
| # + build, and it already includes the i18n structure gate via `test`. | |
| - name: Verify (lint + umlauts + chrome + typecheck + test + build) | |
| if: steps.secrets.outputs.configured == 'true' | |
| run: npm run verify | |
| env: | |
| # Same build-time placeholders ci.yml uses: this step runs BEFORE | |
| # .env.selfhost.local is written, so strict env validation would | |
| # otherwise fail during compile. The deploy script rebuilds after | |
| # this with the real env and the release SHA; that second build is | |
| # warm (the .next/cache step above), so the gate costs little. | |
| AUTH_SECRET: ci-build-placeholder-secret-32chars | |
| DB_HOST: localhost | |
| DB_NAME: revampit_ci | |
| DB_USER: ci | |
| DB_PASSWORD: ci | |
| - name: Write selfhost env | |
| if: steps.secrets.outputs.configured == 'true' | |
| run: | | |
| printf '%s' "$SELFHOST_ENV" > .env.selfhost.local | |
| chmod 600 .env.selfhost.local | |
| - name: Setup SSH | |
| if: steps.secrets.outputs.configured == 'true' | |
| run: | | |
| mkdir -p ~/.ssh | |
| printf '%s\n' "$HETZNER_SSH_PRIVATE_KEY" > ~/.ssh/id_ed25519 | |
| chmod 600 ~/.ssh/id_ed25519 | |
| ssh-keyscan -H 167.233.22.31 >> ~/.ssh/known_hosts | |
| - name: Deploy | |
| if: steps.secrets.outputs.configured == 'true' | |
| run: bash scripts/selfhost-deploy-revampit.sh | |
| - name: Mark deployed | |
| id: mark-deployed | |
| if: steps.secrets.outputs.configured == 'true' | |
| run: echo "deployed=true" >> "$GITHUB_OUTPUT" | |
| post-deploy-smoke: | |
| name: Read-only prod smoke | |
| runs-on: ubuntu-latest | |
| needs: deploy | |
| if: needs.deploy.outputs.deployed == 'true' | |
| timeout-minutes: 5 | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v7 | |
| # Read-only: public pages render + DB-backed public APIs return success. | |
| # No login, no prod mutation. The heavier authenticated dual-persona | |
| # journeys (which mutate prod) are manual: npm run test:e2e:inventory:prod. | |
| - name: Run read-only smoke (public pages + APIs) | |
| run: bash scripts/post-deploy-smoke.sh |