From dd8ec3f1a6ccace3e1e06af753405b4ec83801d4 Mon Sep 17 00:00:00 2001 From: Mao Nakamoto <41178744+maonakamoto@users.noreply.github.com> Date: Mon, 3 Aug 2026 10:02:03 +0200 Subject: [PATCH] ci: replace Vercel deploy with self-host CD, split CI into its own workflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Prod is bitbaum behind Caddy, not Vercel — the Vercel deploy workflow was shipping to somewhere nobody looks while the real production build only changed by hand. The repo's only verification lived inside that deploy workflow, so it is extracted to ci.yml first; the shared CD pipeline blocks on a red CI, and without this file there would be no CI to be green. Co-Authored-By: Claude Opus 5 --- .github/workflows/ci.yml | 51 ++++++++++++++++++++---------------- .github/workflows/deploy.yml | 22 ++++++++++++++++ .husky/pre-push | 22 +++++++++++++--- 3 files changed, 68 insertions(+), 27 deletions(-) create mode 100644 .github/workflows/deploy.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 141a677c..9f07668f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,36 +1,41 @@ name: CI -# Botsmann is deployed self-hosted on the Hetzner box "bitbaum" behind Caddy. -# Deployment runs from the local `.husky/pre-push` hook (build -> rsync -> systemd -# restart) when main is pushed. This workflow only validates build/lint/tests. +# The verification that used to live inside the Vercel deploy workflow. It +# belongs in its own file: prod is self-hosted on bitbaum, so the deploy +# workflow is now a shim over the shared pipeline — and that pipeline refuses +# to ship a commit whose CI is not green. Without this file there would be no +# CI to be green, and every deploy would sail through unverified. on: push: branches: [main] pull_request: - branches: [main] + +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true jobs: - build-and-test: + verify: + name: Format, lint, test, build runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 with: node-version: '22' - cache: 'npm' - - - name: Install dependencies - run: npm ci - - # Single SSOT gate: format:check + lint + test + build. - # Runs verbatim what developers run locally via `npm run verify`. - # No --if-present: a missing/renamed gate script fails loudly. - - name: Verify (format + lint + test + build) - run: npm run verify - env: - NEXT_PUBLIC_DEPLOY_TIME: ${{ github.event.head_commit.timestamp }} + cache: npm + + - run: npm ci + + - name: Format check + run: npm run format:check --if-present + + - name: Lint + run: npm run lint --if-present + + - name: Test + run: npm run test --if-present + + - name: Build + run: npm run build diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 00000000..d1e57883 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,22 @@ +name: Deploy + +# Push to main → deploy botsmann.orangecat.ch to bitbaum. All the logic lives in +# one place: maonakamoto/fleetcrown/.github/workflows/selfhost-deploy.yml (docs: +# docs/infrastructure/self-host-cd.md there). It waits for this commit's own CI +# to go green before anything reaches the box. +# +# Replaces a Vercel deploy workflow: this app has been served from bitbaum +# behind Caddy for months, so those deploys were shipping to somewhere nobody +# looks while the real production build only ever changed by hand. + +on: + push: + branches: [main] + +jobs: + deploy: + uses: maonakamoto/fleetcrown/.github/workflows/selfhost-deploy.yml@main + with: + app: botsmann + node-version: '22' + secrets: inherit diff --git a/.husky/pre-push b/.husky/pre-push index 27673f50..44970cc2 100755 --- a/.husky/pre-push +++ b/.husky/pre-push @@ -1,8 +1,22 @@ #!/usr/bin/env sh # >>> fleetcrown push-deploy >>> -# Pushing main deploys to the Hetzner box (background; see /tmp/push-deploy-botsmann.log) -if git symbolic-ref --short HEAD 2>/dev/null | grep -qx main; then - ( sleep 5 && env -u CI -u VERCEL bash /home/g/dev/fleetcrown/scripts/hetzner/deploy.sh botsmann ) >> /tmp/push-deploy-botsmann.log 2>&1 & disown 2>/dev/null || true - echo "[push-deploy] botsmann: deploy started in background → /tmp/push-deploy-botsmann.log" +# Pushing the repo's default branch deploys to the Hetzner box +# (background; see /tmp/push-deploy-botsmann.log). Repos differ: some use +# 'main', some still use 'master' — match either so push-to-deploy fires +# regardless of the repo's branch convention. +# CI GATE: the background deploy first waits for GitHub CI on the pushed +# commit and is BLOCKED on a red — prod only receives what CI verified +# (ci-gate.sh; repos without CI pass after a short grace window). +if git symbolic-ref --short HEAD 2>/dev/null | grep -qxE 'main|master'; then + ( sleep 5 + _sha=$(git rev-parse HEAD) + _nwo=$(git remote get-url origin 2>/dev/null | sed -E 's#(git@github.com:|https://github.com/)##; s#\.git$##') + if [ -n "$_nwo" ] && ! bash "/home/g/dev/fleetcrown/scripts/hetzner/ci-gate.sh" "$_nwo" "$_sha"; then + echo "[push-deploy] botsmann: BLOCKED by CI gate for ${_sha} — fix CI, then re-push or deploy manually" + else + env -u CI bash /home/g/dev/fleetcrown/scripts/hetzner/deploy.sh botsmann + fi + ) >> /tmp/push-deploy-botsmann.log 2>&1 & disown 2>/dev/null || true + echo "[push-deploy] botsmann: CI-gated deploy started in background → /tmp/push-deploy-botsmann.log" fi # <<< fleetcrown push-deploy <<<