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.

103 lines · 5.1 KBRaw History
1name: Sync to Diffinery
2
3# Keeps diffinery.com/codician-team/diffinery in step with this repository: every
4# branch and tag, plus the Actions secrets and variables our workflows use. On the
5# first run it creates the organization and repository. For main it then waits for
6# the Actions runs on Diffinery and fails if any of them fail.
7#
8# Secrets go straight from GitHub's secret store to Diffinery's over HTTPS and are
9# never printed. Needs DIFFINERY_LIVE_TOKEN (an admin token on diffinery.com).
10on:
11 push:
12 workflow_dispatch:
13 inputs:
14 allow_secrets:
15 description: Reason for pushing past Diffinery's push protection (admins only, audited). Leave empty normally.
16 type: string
17 default: ""
18
19permissions:
20 contents: read
21
22concurrency:
23 group: sync-to-diffinery
24 cancel-in-progress: false
25
26jobs:
27 move:
28 name: Copy code, secrets and variables, then check Actions on Diffinery
29 runs-on: ubuntu-latest
30 timeout-minutes: 90
31 environment: production
32 env:
33 DIFFINERY: https://diffinery.com
34 TOKEN: ${{ secrets.DIFFINERY_LIVE_TOKEN }}
35 ORG: codician-team
36 NAME: diffinery
37 steps:
38 - name: Check the token
39 run: |
40 [ -n "$TOKEN" ] || { echo "::error::Add DIFFINERY_LIVE_TOKEN (an admin token from $DIFFINERY/settings/tokens) as a repository secret first."; exit 1; }
41 curl -fsS "$DIFFINERY/api/v3/user" -H "Authorization: Bearer $TOKEN" | jq '{login, site_admin}'
42
43 - name: Create the organization and repository
44 env:
45 DESC: ${{ github.event.repository.description }}
46 run: |
47 set -euo pipefail
48 api() { curl -sS -o /tmp/out -w '%{http_code}' -X "$1" "$DIFFINERY/api/v3$2" -H "Authorization: Bearer $TOKEN" -H "content-type: application/json" ${3:+--data "$3"}; }
49 if [ "$(api GET "/users/$ORG")" = 404 ]; then
50 code=$(api POST /user/orgs "{\"login\":\"$ORG\",\"name\":\"Codician\"}"); cat /tmp/out; echo
51 [ "$code" = 201 ] || { echo "::error::could not create the $ORG organization ($code)"; exit 1; }
52 fi
53 if [ "$(api GET "/repos/$ORG/$NAME")" = 404 ]; then
54 desc=$(jq -n --arg d "$DESC" '$d')
55 code=$(api POST "/orgs/$ORG/repos" "{\"name\":\"$NAME\",\"private\":true,\"default_branch\":\"main\",\"description\":$desc}"); cat /tmp/out; echo
56 [ "$code" = 201 ] || { echo "::error::could not create $ORG/$NAME ($code)"; exit 1; }
57 fi
58 echo "$DIFFINERY/$ORG/$NAME is ready"
59
60 - name: Copy Actions secrets and variables
61 env:
62 S_CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
63 S_CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
64 S_DIFFINERY_LIVE_TOKEN: ${{ secrets.DIFFINERY_LIVE_TOKEN }}
65 V_ACCESS_TEAM_DOMAIN: ${{ vars.ACCESS_TEAM_DOMAIN }}
66 run: |
67 set -euo pipefail
68 put() { curl -fsS -o /dev/null -X PUT "$DIFFINERY/api/v3/repos/$ORG/$NAME/actions/$1/$2" -H "Authorization: Bearer $TOKEN" -H "content-type: application/json" --data-binary @-; }
69 for name in CLOUDFLARE_API_TOKEN CLOUDFLARE_ACCOUNT_ID DIFFINERY_LIVE_TOKEN; do
70 v="S_$name"
71 if [ -n "${!v}" ]; then jq -n --arg v "${!v}" '{value: $v}' | put secrets "$name"; echo "secret $name copied"; else echo "::warning::secret $name is empty here, so it was not copied"; fi
72 done
73 for name in ACCESS_TEAM_DOMAIN; do
74 v="V_$name"
75 if [ -n "${!v}" ]; then jq -n --arg v "${!v}" '{value: $v}' | put variables "$name"; echo "variable $name = ${!v}"; fi
76 done
77
78 - uses: actions/checkout@v4
79 with:
80 fetch-depth: 0
81 lfs: true
82
83 - name: Push every branch and tag
84 env:
85 ALLOW_SECRETS: ${{ inputs.allow_secrets }}
86 run: |
87 set -euo pipefail
88 git fetch -q --tags origin '+refs/heads/*:refs/remotes/origin/*'
89 remote="https://x-token:${TOKEN}@${DIFFINERY#https://}/$ORG/$NAME.git"
90 refs=$(git for-each-ref --format='+%(refname):refs/heads/%(refname:lstrip=3)' refs/remotes/origin | grep -v ':refs/heads/HEAD$')
91 git push -q ${ALLOW_SECRETS:+-o "allow-secrets=$ALLOW_SECRETS"} "$remote" $refs '+refs/tags/*:refs/tags/*'
92 if git lfs ls-files | grep -q .; then git lfs push --all "$remote"; fi
93 echo "branches on Diffinery:"
94 curl -fsS "$DIFFINERY/api/v3/repos/$ORG/$NAME/branches" -H "Authorization: Bearer $TOKEN" | jq -r '.[] | " \(.name) \(.commit.sha)"'
95 want=$(git rev-parse refs/remotes/origin/main)
96 got=$(curl -fsS "$DIFFINERY/api/v3/repos/$ORG/$NAME/branches" -H "Authorization: Bearer $TOKEN" | jq -r '.[] | select(.name=="main") | .commit.sha')
97 [ "$want" = "$got" ] || { echo "::error::main on Diffinery is $got, expected $want"; exit 1; }
98 echo "main matches GitHub at $want"
99
100 - name: Wait for Actions on Diffinery
101 # Pushes to main start CI (and, once Diffinery deploys, the deploy) there.
102 if: github.ref == 'refs/heads/main'
103 run: scripts/diffinery-wait-runs.sh "$ORG/$NAME" "$(git rev-parse refs/remotes/origin/main)" 75