deckhearth/.github/workflows/preview-smoke.yml
Randall Stillwell 1cc2e28423 Migrate Deck Hearth off Vercel/Neon to homelab Dokploy stack.
Replace @vercel/postgres, Blob, and Upstash with lib/sql.js, MinIO object
storage, and CT 102 Redis rate limits. Add Dockerfile for Dokploy deploy,
homelab runbooks, Neon data-copy helper, and point CI smoke/visual at the
homelab URL instead of Vercel previews.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-15 09:32:13 -05:00

134 lines
4.6 KiB
YAML

name: Preview smoke
# Runs Playwright smoke against the homelab deployment (Dokploy on CT 112,
# routed via Traefik at deckhearth.stillwell.cloud). Until per-PR preview
# deploy exists on Dokploy, smoke validates the live homelab app after merge.
on:
pull_request:
branches: [main]
types: [labeled, opened, synchronize, reopened]
# Skip the heavy Playwright smoke on doc-only PRs. Same set as
# ci.yml's paths-ignore — see `slash-ci-minutes` convoy
# (2026-06-04). Code/style/UI PRs still trigger smoke normally.
paths-ignore:
- '.convoys/**'
- '**/*.md'
- 'docs/**'
- 'AGENTS.md'
- '.cursor/**'
- 'README.md'
concurrency:
group: preview-smoke-${{ github.event.pull_request.number }}
cancel-in-progress: true
permissions:
contents: read
pull-requests: read
jobs:
gate:
name: Should run?
runs-on: [self-hosted, axiom]
outputs:
should_run: ${{ steps.check.outputs.should_run }}
steps:
- name: Decide
id: check
env:
# Route PR body + fork flag through env vars instead of inline
# ${{ }} interpolation. Direct ${{ github.event.pull_request.body }}
# in a shell command pastes arbitrary user-controlled text (parens,
# backticks, pipes, heredocs) directly into the script — both a
# syntax-error risk AND a shell-injection vector. Quoting the env
# vars below makes both safe.
PR_BODY: ${{ github.event.pull_request.body }}
PR_IS_FORK: ${{ github.event.pull_request.head.repo.fork }}
run: |
if [[ "$PR_IS_FORK" == "true" ]]; then
echo "should_run=false" >> $GITHUB_OUTPUT
echo "::notice::Smoke skipped on fork PR (bypass secret unavailable to forks)"
elif echo "$PR_BODY" | grep -qE 'pipeline:.*skip.*\bsmoke\b'; then
echo "should_run=false" >> $GITHUB_OUTPUT
echo "::notice::Smoke skipped via pipeline directive"
else
echo "should_run=true" >> $GITHUB_OUTPUT
fi
smoke:
name: Playwright smoke
needs: gate
if: needs.gate.outputs.should_run == 'true'
runs-on: [self-hosted, axiom]
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
- name: Wait for homelab app health
run: |
BASE_URL="${SMOKE_BASE_URL:-https://deckhearth.stillwell.cloud}"
for i in $(seq 1 30); do
if curl -fsS "${BASE_URL}/api/health" >/dev/null; then
echo "Homelab app healthy at ${BASE_URL}"
exit 0
fi
echo "Waiting for ${BASE_URL}/api/health (${i}/30)…"
sleep 10
done
echo "::error::Homelab app did not become healthy in time"
exit 1
env:
SMOKE_BASE_URL: ${{ vars.SMOKE_BASE_URL }}
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: npm
- name: Cache node_modules
id: cache-node-modules
uses: actions/cache@v4
with:
path: node_modules
key: node-modules-${{ runner.os }}-node20-${{ hashFiles('package-lock.json') }}
- run: npm ci
if: steps.cache-node-modules.outputs.cache-hit != 'true'
# Cache Playwright's browser binaries (~/.cache/ms-playwright) keyed
# by the resolved @playwright/test version in the lockfile. Saves
# ~15-25s/run of chromium download + system-deps install. Cache
# invalidates automatically on any Playwright version bump.
- name: Resolve Playwright version
id: pw-version
run: |
VERSION=$(node -p "require('./package-lock.json').packages['node_modules/@playwright/test'].version")
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
- name: Cache Playwright browsers
id: cache-playwright
uses: actions/cache@v4
with:
path: ~/.cache/ms-playwright
key: playwright-${{ runner.os }}-${{ steps.pw-version.outputs.version }}-chromium
- name: Install Playwright browsers
run: |
if [ "${{ steps.cache-playwright.outputs.cache-hit }}" = "true" ]; then
npx playwright install-deps chromium
else
npx playwright install --with-deps chromium
fi
- name: Run smoke tests
run: npx playwright test --project=smoke
env:
BASE_URL: ${{ vars.SMOKE_BASE_URL || 'https://deckhearth.stillwell.cloud' }}
- name: Upload Playwright report on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: playwright-report
path: playwright-report/
retention-days: 7