forked from mathewdunne/CodeRunner
-
Notifications
You must be signed in to change notification settings - Fork 0
210 lines (181 loc) · 7.27 KB
/
Copy pathdeploy.yml
File metadata and controls
210 lines (181 loc) · 7.27 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
name: Deploy
# Deploys an already-published release tag. Images and release artifacts are
# produced by release.yml on tag push; this workflow only checks they exist
# and rolls them out (GCE always; Cloudflare Pages when configured).
on:
workflow_dispatch:
inputs:
tag:
description: "Published release tag to deploy (for example v2.4.0)"
required: true
type: string
concurrency:
group: deploy-prod
cancel-in-progress: false
env:
WORKSPACE_IMAGE: ghcr.io/${{ github.repository_owner }}/scriptum-workspace
CONTROL_IMAGE: ghcr.io/${{ github.repository_owner }}/scriptum-control
jobs:
preflight:
runs-on: ubuntu-latest
permissions:
contents: read
packages: read
outputs:
tag: ${{ steps.check.outputs.tag }}
env:
TAG: ${{ inputs.tag }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Check release and multi-arch images exist
id: check
run: |
set -euo pipefail
if ! echo "$TAG" | grep -qE '^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$'; then
echo "Refusing to deploy '$TAG'. Expected vMAJOR.MINOR.PATCH or vMAJOR.MINOR.PATCH-prerelease."
exit 1
fi
if ! gh release view "$TAG" --repo "$GITHUB_REPOSITORY" >/dev/null; then
echo "No GitHub release for $TAG. Push the tag and let release.yml publish it first."
exit 1
fi
for image in "$WORKSPACE_IMAGE" "$CONTROL_IMAGE"; do
out="$(docker buildx imagetools inspect "$image:$TAG")"
for platform in linux/amd64 linux/arm64; do
if ! echo "$out" | grep -q "$platform"; then
echo "$image:$TAG is missing $platform. Did release.yml finish for this tag?"
exit 1
fi
done
done
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
deploy-cloudflare:
needs: [preflight, deploy]
# Only runs when CF_ACCOUNT_ID is set as a repo variable.
# Depends on deploy (GCE) so the backend is always updated before the
# frontend goes live — prevents the client from running ahead of the server.
# Leave CF_ACCOUNT_ID unset to skip this job and use single-machine mode only.
if: vars.CF_ACCOUNT_ID != ''
runs-on: ubuntu-latest
permissions:
contents: read
env:
TAG: ${{ needs.preflight.outputs.tag }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v4
with:
ref: ${{ needs.preflight.outputs.tag }}
- name: Download web-dist from release
run: gh release download "$TAG" --pattern web-dist.tar.gz
- name: Unpack web-dist
run: mkdir -p apps/web/dist && tar -xzf web-dist.tar.gz -C apps/web/dist
- uses: oven-sh/setup-bun@v2
with:
bun-version-file: package.json
- name: Deploy to Cloudflare Pages
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CF_API_TOKEN }}
accountId: ${{ vars.CF_ACCOUNT_ID }}
workingDirectory: deploy/cloudflare
packageManager: bun
command: pages deploy --commit-dirty=true --branch main --project-name=scriptum
deploy:
needs: preflight
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
env:
# Forks deploy by setting these as repo variables (Settings -> Secrets and
# variables -> Actions -> Variables). Required: GCP_PROJECT, GCP_DEPLOY_SA,
# GCP_WIF_PROVIDER. Optional: GCP_ZONE, GCP_VM_NAME.
GCP_PROJECT: ${{ vars.GCP_PROJECT }}
GCP_ZONE: ${{ vars.GCP_ZONE || 'us-central1-a' }}
VM_NAME: ${{ vars.GCP_VM_NAME || 'scriptum' }}
DEPLOY_SA: ${{ vars.GCP_DEPLOY_SA }}
WIF_PROVIDER: ${{ vars.GCP_WIF_PROVIDER }}
TAG: ${{ needs.preflight.outputs.tag }}
steps:
- name: Check required GCP variables
run: |
set -euo pipefail
for var in GCP_PROJECT DEPLOY_SA WIF_PROVIDER; do
if [ -z "${!var:-}" ]; then
echo "Missing repo variable: $var. See deploy/README.md for setup."
exit 1
fi
done
# Ship the deployed tag's compose files to the VM so they version with the
# release (they pin nothing themselves — images come from the .env tag).
# Checkout must run BEFORE auth: it cleans the workspace directory, which
# would delete the WIF credential file auth writes there.
- uses: actions/checkout@v4
with:
ref: ${{ needs.preflight.outputs.tag }}
- id: auth
name: Authenticate to GCP via Workload Identity Federation
uses: google-github-actions/auth@v2
with:
workload_identity_provider: ${{ env.WIF_PROVIDER }}
service_account: ${{ env.DEPLOY_SA }}
- name: Set up gcloud
uses: google-github-actions/setup-gcloud@v2
with:
project_id: ${{ env.GCP_PROJECT }}
- name: Copy compose files to VM
run: |
set -euo pipefail
gcloud compute scp \
--zone="$GCP_ZONE" \
--tunnel-through-iap \
--quiet \
docker-compose.yml docker-compose.prod.yml \
"$VM_NAME":/tmp/
- name: Roll service on VM
run: |
set -euo pipefail
read -r -d '' DEPLOY_SCRIPT <<EOF || true
set -euo pipefail
# Wait for first-boot provisioning to finish (compose files, .env).
for _ in \$(seq 1 60); do
test -f /var/log/scriptum-bootstrap-done && break
sleep 2
done
test -f /var/log/scriptum-bootstrap-done
sudo install -o root -g root -m 644 /tmp/docker-compose.yml /opt/scriptum/docker-compose.yml
sudo install -o root -g root -m 644 /tmp/docker-compose.prod.yml /opt/scriptum/docker-compose.prod.yml
rm -f /tmp/docker-compose.yml /tmp/docker-compose.prod.yml
# Pin the deployed tag. render-env.sh preserves SCRIPTUM_TAG across
# reboots; compose interpolates it into the control + workspace images.
sudo sed -i '/^SCRIPTUM_TAG=/d' /opt/scriptum/.env
echo "SCRIPTUM_TAG=$TAG" | sudo tee -a /opt/scriptum/.env >/dev/null
cd /opt/scriptum
sudo docker compose pull
sudo docker compose up -d --remove-orphans
# New workspace image: recycle student containers so the next access
# recreates them on the new image. Projects/homes are bind-mounted, so
# only disposable containers are removed.
sudo docker compose exec -T control scriptum rebuild-workspaces
for _ in \$(seq 1 60); do
if curl -sf http://localhost:4000/healthz >/dev/null; then
echo "healthz OK"
exit 0
fi
sleep 1
done
echo "healthz did not respond within 60s"
sudo docker compose logs --tail 80 control || true
exit 1
EOF
gcloud compute ssh "$VM_NAME" \
--zone="$GCP_ZONE" \
--tunnel-through-iap \
--quiet \
--command="$DEPLOY_SCRIPT"