Diffinery

codician/diffinerypublic

Open-source, agent-first GitHub and GitHub Actions alternative that runs on your own Cloudflare account: git hosting, pull requests, code review and drop-in GitHub Actions YAML on Workers, Durable Objects, R2 and Containers.

104 lines · 4.7 KBRaw History
1name: Deploy
2
3# Deploys diffinery.com from main. Uses the repository secrets
4# CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID; optional repository variable
5# ACCESS_TEAM_DOMAIN (e.g. codician.cloudflareaccess.com).
6on:
7 push:
8 branches: [main]
9 workflow_dispatch:
10
11permissions:
12 contents: read
13
14concurrency:
15 group: deploy-production
16 cancel-in-progress: false
17
18jobs:
19 deploy:
20 name: Deploy to Cloudflare
21 # Production deploys from Diffinery, on its own hosted runners. GitHub
22 # mirrors this repository there (see sync-to-diffinery.yml); running this
23 # workflow by hand on GitHub still deploys, as a fallback.
24 if: ${{ github.server_url == 'https://diffinery.com' || github.event_name == 'workflow_dispatch' }}
25 runs-on: ubuntu-latest
26 timeout-minutes: 45
27 environment:
28 name: production
29 url: https://diffinery.com
30 env:
31 CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
32 CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
33 ACCESS_TEAM_DOMAIN: ${{ vars.ACCESS_TEAM_DOMAIN }}
34 steps:
35 - uses: actions/checkout@v4
36 - uses: pnpm/action-setup@v4
37 - uses: actions/setup-node@v4
38 with:
39 node-version: 22
40 cache: pnpm
41 - run: pnpm install --frozen-lockfile
42 - name: Build runner agent and web islands
43 run: pnpm -r --if-present build
44 - name: Typecheck and test
45 run: pnpm -r --if-present typecheck && pnpm -r --if-present test
46
47 - name: Create or update Cloudflare resources
48 id: bootstrap
49 run: scripts/bootstrap.sh
50
51 - name: Deploy Worker and runner image
52 working-directory: apps/diffinery
53 run: pnpm exec wrangler deploy -c wrangler.deploy.jsonc
54
55 - name: Generate Worker secrets (first deploy only)
56 run: scripts/ensure-secrets.sh
57
58 - name: Finish Access setup on first deploy
59 if: steps.bootstrap.outputs.access_team == ''
60 run: |
61 scripts/bootstrap.sh
62 cd apps/diffinery && pnpm exec wrangler deploy -c wrangler.deploy.jsonc --containers-rollout none
63
64 - name: Smoke test
65 run: |
66 # A new custom domain can take a few minutes to get its certificate.
67 for i in $(seq 1 30); do
68 code=$(curl -s -o /dev/null -w '%{http_code}' https://diffinery.com/healthz) && [ "$code" = 200 ] && exit 0
69 sleep 10
70 done
71 echo "::error::https://diffinery.com/healthz did not return 200 (last: $code). Check that the diffinery.com DNS record is proxied (orange cloud)."; exit 1
72
73 - name: Check sign-in pages
74 run: |
75 set -u
76 page=$(curl -sS https://diffinery.com/login)
77 grep -q "Sign in with a passkey" <<<"$page" || { echo "::error::/login is not the Diffinery sign-in page"; exit 1; }
78 echo "/login serves the passkey sign-in page"
79 loc=$(curl -sS -o /dev/null -w '%{http_code} %{redirect_url}' https://diffinery.com/auth/access)
80 echo "/auth/access -> $loc"
81 [[ "$loc" == 302\ https://*.cloudflareaccess.com/* ]] || echo "::warning::/auth/access is not behind Cloudflare Access yet, so email recovery and first admin sign-in will not work"
82 for kind in js css; do
83 asset=$(grep -o "/-/kumo/[a-z0-9]*\.$kind" <<<"$page" | head -1)
84 [ -n "$asset" ] || { echo "::error::/login does not load the Kumo $kind bundle"; exit 1; }
85 code=$(curl -sS -o /dev/null -w '%{http_code}' "https://diffinery.com$asset")
86 [ "$code" = 200 ] || { echo "::error::$asset returned $code"; exit 1; }
87 echo "$asset -> 200"
88 done
89 - name: Check favicon and search metadata
90 run: |
91 set -u
92 for p in /favicon.ico /favicon.svg /favicon-48.png /apple-touch-icon.png /og-image.png /site.webmanifest /robots.txt /sitemap.xml; do
93 code=$(curl -sS -o /dev/null -w '%{http_code} %{content_type}' "https://diffinery.com$p")
94 [[ "$code" == 200\ * ]] || { echo "::error::$p returned $code"; exit 1; }
95 echo "$p -> $code"
96 done
97 home=$(curl -sS https://diffinery.com/)
98 for tag in 'rel="icon" href="/favicon.ico"' 'property="og:image"' 'rel="canonical"' 'application/ld+json'; do
99 grep -qF "$tag" <<<"$home" || { echo "::error::the home page is missing $tag"; exit 1; }
100 done
101 echo "the home page carries the favicon, canonical, Open Graph and structured data tags"
102 bm=$(curl -sS -D - -o /dev/null https://diffinery.com/sitemap.xml | tr -d '\r' | sed -n 's/^x-d1-bookmark: //Ip')
103 [ -n "$bm" ] || { echo "::error::D1 queries don't run in a session (no x-d1-bookmark header)"; exit 1; }
104 echo "D1 sessions are on (bookmark $bm)"