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.

105 lines · 5.2 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 GitHub only; the copy on Diffinery has nothing to sync to.
30 if: ${{ github.server_url == 'https://github.com' }}
31 runs-on: ubuntu-latest
32 timeout-minutes: 90
33 environment: production
34 env:
35 DIFFINERY: https://diffinery.com
36 TOKEN: ${{ secrets.DIFFINERY_LIVE_TOKEN }}
37 ORG: codician-team
38 NAME: diffinery
39 steps:
40 - name: Check the token
41 run: |
42 [ -n "$TOKEN" ] || { echo "::error::Add DIFFINERY_LIVE_TOKEN (an admin token from $DIFFINERY/settings/tokens) as a repository secret first."; exit 1; }
43 curl -fsS "$DIFFINERY/api/v3/user" -H "Authorization: Bearer $TOKEN" | jq '{login, site_admin}'
44
45 - name: Create the organization and repository
46 env:
47 DESC: ${{ github.event.repository.description }}
48 run: |
49 set -euo pipefail
50 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"}; }
51 if [ "$(api GET "/users/$ORG")" = 404 ]; then
52 code=$(api POST /user/orgs "{\"login\":\"$ORG\",\"name\":\"Codician\"}"); cat /tmp/out; echo
53 [ "$code" = 201 ] || { echo "::error::could not create the $ORG organization ($code)"; exit 1; }
54 fi
55 if [ "$(api GET "/repos/$ORG/$NAME")" = 404 ]; then
56 desc=$(jq -n --arg d "$DESC" '$d')
57 code=$(api POST "/orgs/$ORG/repos" "{\"name\":\"$NAME\",\"private\":true,\"default_branch\":\"main\",\"description\":$desc}"); cat /tmp/out; echo
58 [ "$code" = 201 ] || { echo "::error::could not create $ORG/$NAME ($code)"; exit 1; }
59 fi
60 echo "$DIFFINERY/$ORG/$NAME is ready"
61
62 - name: Copy Actions secrets and variables
63 env:
64 S_CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
65 S_CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
66 S_DIFFINERY_LIVE_TOKEN: ${{ secrets.DIFFINERY_LIVE_TOKEN }}
67 V_ACCESS_TEAM_DOMAIN: ${{ vars.ACCESS_TEAM_DOMAIN }}
68 run: |
69 set -euo pipefail
70 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 @-; }
71 for name in CLOUDFLARE_API_TOKEN CLOUDFLARE_ACCOUNT_ID DIFFINERY_LIVE_TOKEN; do
72 v="S_$name"
73 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
74 done
75 for name in ACCESS_TEAM_DOMAIN; do
76 v="V_$name"
77 if [ -n "${!v}" ]; then jq -n --arg v "${!v}" '{value: $v}' | put variables "$name"; echo "variable $name = ${!v}"; fi
78 done
79
80 - uses: actions/checkout@v4
81 with:
82 fetch-depth: 0
83 lfs: true
84
85 - name: Push every branch and tag
86 env:
87 ALLOW_SECRETS: ${{ inputs.allow_secrets }}
88 run: |
89 set -euo pipefail
90 git fetch -q --tags origin '+refs/heads/*:refs/remotes/origin/*'
91 remote="https://x-token:${TOKEN}@${DIFFINERY#https://}/$ORG/$NAME.git"
92 refs=$(git for-each-ref --format='+%(refname):refs/heads/%(refname:lstrip=3)' refs/remotes/origin | grep -v ':refs/heads/HEAD$')
93 git push -q ${ALLOW_SECRETS:+-o "allow-secrets=$ALLOW_SECRETS"} "$remote" $refs '+refs/tags/*:refs/tags/*'
94 if git lfs ls-files | grep -q .; then git lfs push --all "$remote"; fi
95 echo "branches on Diffinery:"
96 curl -fsS "$DIFFINERY/api/v3/repos/$ORG/$NAME/branches" -H "Authorization: Bearer $TOKEN" | jq -r '.[] | " \(.name) \(.commit.sha)"'
97 want=$(git rev-parse refs/remotes/origin/main)
98 got=$(curl -fsS "$DIFFINERY/api/v3/repos/$ORG/$NAME/branches" -H "Authorization: Bearer $TOKEN" | jq -r '.[] | select(.name=="main") | .commit.sha')
99 [ "$want" = "$got" ] || { echo "::error::main on Diffinery is $got, expected $want"; exit 1; }
100 echo "main matches GitHub at $want"
101
102 - name: Wait for Actions on Diffinery
103 # Pushes to main start CI (and, once Diffinery deploys, the deploy) there.
104 if: github.ref == 'refs/heads/main'
105 run: scripts/diffinery-wait-runs.sh "$ORG/$NAME" "$(git rev-parse refs/remotes/origin/main)" 75