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.

111 lines · 5.9 KBRaw History
1name: D1 read replication
2
3# Checks the D1 Sessions API with read replication on a throwaway database, then
4# (when asked) turns read replication on for the production database.
5# Uses the repository secrets CLOUDFLARE_API_TOKEN (D1 and Workers Scripts: Edit)
6# and CLOUDFLARE_ACCOUNT_ID.
7on:
8 workflow_dispatch:
9 inputs:
10 enable_production:
11 description: Turn on read replication for the production database after the check passes
12 type: boolean
13 default: false
14
15permissions:
16 contents: read
17
18jobs:
19 check:
20 name: Check sessions on a test database
21 runs-on: ubuntu-latest
22 # The Cloudflare secrets are the production environment's.
23 environment: production
24 timeout-minutes: 20
25 env:
26 CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
27 CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
28 NAME: diffinery-d1-probe-${{ github.run_id }}
29 steps:
30 - uses: actions/checkout@v4
31 - uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4.3.0
32 - uses: actions/setup-node@v4
33 with:
34 node-version: 22
35 cache: pnpm
36 - run: pnpm install --frozen-lockfile
37 - name: Create a test database with read replication
38 run: |
39 set -euo pipefail
40 API="https://api.cloudflare.com/client/v4/accounts/$CLOUDFLARE_ACCOUNT_ID"
41 auth=(-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" -H "Content-Type: application/json")
42 id=$(curl -sS -X POST "$API/d1/database" "${auth[@]}" --data "{\"name\":\"$NAME\"}" | jq -r '.result.uuid // empty')
43 [ -n "$id" ] || { echo "::error::could not create the test database"; exit 1; }
44 echo "D1_ID=$id" >> "$GITHUB_ENV"
45 curl -sS -X PUT "$API/d1/database/$id" "${auth[@]}" --data '{"read_replication":{"mode":"auto"}}' | jq -e '.success' >/dev/null
46 mode=$(curl -sS "$API/d1/database/$id" "${auth[@]}" | jq -r '.result.read_replication.mode')
47 echo "read replication on the test database: $mode"
48 [ "$mode" = auto ] || { echo "::error::read replication did not turn on"; exit 1; }
49 - name: Deploy the probe Worker
50 working-directory: scripts/d1-probe
51 run: |
52 set -euo pipefail
53 cat > wrangler.json <<JSON
54 {"name":"$NAME","main":"worker.js","compatibility_date":"2026-10-01","workers_dev":true,
55 "d1_databases":[{"binding":"DB","database_name":"$NAME","database_id":"$D1_ID"}]}
56 JSON
57 W=(pnpm --dir "$GITHUB_WORKSPACE/apps/diffinery" exec wrangler)
58 "${W[@]}" d1 execute "$NAME" --remote -c "$PWD/wrangler.json" --command "CREATE TABLE probe (k TEXT PRIMARY KEY, v TEXT NOT NULL)"
59 "${W[@]}" deploy -c "$PWD/wrangler.json"
60 sub=$(curl -sS "https://api.cloudflare.com/client/v4/accounts/$CLOUDFLARE_ACCOUNT_ID/workers/subdomain" -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" | jq -r '.result.subdomain // empty')
61 [ -n "$sub" ] || { echo "::error::the account has no workers.dev subdomain"; exit 1; }
62 echo "PROBE_URL=https://$NAME.$sub.workers.dev" >> "$GITHUB_ENV"
63 - name: Read your own writes through bookmarks
64 run: |
65 set -uo pipefail
66 for i in $(seq 1 30); do
67 body=$(curl -sS "$PROBE_URL/check?rounds=25") && jq -e . >/dev/null <<<"$body" && break
68 sleep 5
69 done
70 echo "$body" | jq .
71 jq -e '.ok' >/dev/null <<<"$body" || { echo "::error::a read after a bookmark missed the write, or a query failed"; exit 1; }
72 echo "every read from a bookmark saw its write ($(jq -r '.replicaReads' <<<"$body") served by replicas)"
73 - name: Remove the probe and the test database
74 if: always()
75 run: |
76 cd scripts/d1-probe
77 [ -f wrangler.json ] && pnpm --dir "$GITHUB_WORKSPACE/apps/diffinery" exec wrangler delete -c "$PWD/wrangler.json" --force || true
78 [ -n "${D1_ID:-}" ] && curl -sS -X DELETE "https://api.cloudflare.com/client/v4/accounts/$CLOUDFLARE_ACCOUNT_ID/d1/database/$D1_ID" -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" | jq -c '{success}' || true
79
80 enable:
81 name: Turn on read replication in production
82 needs: check
83 if: ${{ inputs.enable_production }}
84 runs-on: ubuntu-latest
85 environment:
86 name: production
87 url: https://${{ vars.DIFFINERY_DOMAIN || 'diffinery.com' }}
88 env:
89 DIFFINERY_DOMAIN: ${{ vars.DIFFINERY_DOMAIN || 'diffinery.com' }}
90 CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
91 CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
92 steps:
93 - name: Enable and verify
94 run: |
95 set -euo pipefail
96 API="https://api.cloudflare.com/client/v4/accounts/$CLOUDFLARE_ACCOUNT_ID"
97 auth=(-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" -H "Content-Type: application/json")
98 id=$(curl -sS "$API/d1/database?name=diffinery&per_page=100" "${auth[@]}" | jq -r '.result[]? | select(.name=="diffinery") | .uuid' | head -n1)
99 [ -n "$id" ] || { echo "::error::production database not found"; exit 1; }
100 curl -sS -X PUT "$API/d1/database/$id" "${auth[@]}" --data '{"read_replication":{"mode":"auto"}}' | jq -e '.success' >/dev/null
101 mode=$(curl -sS "$API/d1/database/$id" "${auth[@]}" | jq -r '.result.read_replication.mode')
102 echo "read replication on diffinery: $mode"
103 [ "$mode" = auto ] || { echo "::error::read replication did not turn on"; exit 1; }
104 - name: Check the site still reads and writes
105 run: |
106 set -euo pipefail
107 for p in /healthz /login /sitemap.xml; do
108 code=$(curl -sS -o /dev/null -w '%{http_code}' "https://$DIFFINERY_DOMAIN$p"); echo "$p -> $code"; [ "$code" = 200 ]
109 done
110 bm=$(curl -sS -D - -o /dev/null https://$DIFFINERY_DOMAIN/sitemap.xml | tr -d '\r' | sed -n 's/^x-d1-bookmark: //Ip')
111 echo "x-d1-bookmark: $bm"; [ -n "$bm" ]