-
-
Notifications
You must be signed in to change notification settings - Fork 13
217 lines (205 loc) · 10.8 KB
/
Copy pathdeploy-develop.yml
File metadata and controls
217 lines (205 loc) · 10.8 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
211
212
213
214
215
216
217
# Puts whatever is on develop onto develop.posnic.io, so anybody can try it.
#
# THIS MACHINE RUNS UNREVIEWED CONTRIBUTOR CODE. That single fact decides
# everything below.
#
# It is NOT registered in the estate console. A registered instance can be
# handed a real customer's shop by the provisioner, and no amount of care
# afterwards would undo that.
#
# It gets its OWN database and its OWN secrets. It never receives the
# control-plane credentials, the S3 keys, the payment keys or the mail keys
# that the production deploys carry - deliberately, by simply not sending
# them, so nothing on that box can reach anything real.
#
# It holds demo data only, and says so on every page.
#
# It is a public sandbox that happens to run our code. Treat anything on it as
# readable by anyone, because it is.
name: Deploy develop
on:
push:
branches: [develop]
workflow_dispatch:
permissions:
contents: read
concurrency:
group: deploy-develop
cancel-in-progress: false
jobs:
deploy:
# Off until the machine exists and DEVELOP_DEPLOY_ENABLED is set to true.
# A workflow that fails on every push teaches people to ignore red marks.
if: vars.DEVELOP_DEPLOY_ENABLED == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: "22"
# Built here rather than on the box: a 2 GB machine running mongod and
# the API should not also be running a gulp build, and building in CI
# means a broken build never reaches the sandbox at all.
# Every language ships in every build now, the unreviewed ones marked
# beta in the menu (frontend/gulpfile.js/config.js). Nothing to switch
# on here: the sandbox shows exactly what an installer shows.
- name: Build the frontend
run: |
npm --prefix frontend install --no-audit --no-fund
cd frontend && npx gulp build
- name: Check discovery files
run: |
set -e
for f in \
frontend/public/robots.txt \
frontend/public/sitemap.xml \
frontend/public/llms.txt
do
test -s "$f" || { echo "missing or empty after build: $f"; exit 1; }
done
- name: Setup SSH
run: |
mkdir -p ~/.ssh
echo "${{ secrets.DEVELOP_SSH_KEY }}" > ~/.ssh/develop.pem
chmod 600 ~/.ssh/develop.pem
ssh-keyscan -H "${{ secrets.DEVELOP_HOST }}" >> ~/.ssh/known_hosts
- name: Sync the app
run: |
# .env is excluded on purpose. The sandbox generates its own secrets
# once and keeps them; overwriting them on every deploy would sign
# out every tester mid-test.
rsync -az --delete \
--exclude node_modules --exclude .env --exclude uploads \
-e "ssh -i ~/.ssh/develop.pem" \
api/ "${{ secrets.DEVELOP_USER }}@${{ secrets.DEVELOP_HOST }}:~/posnic-develop/api/"
# HASHED BUNDLES ARE PROTECTED FROM --delete.
#
# A tab that is already open still asks for script/dashboard.<old>.js
# by name. Plain --delete removes it the moment the next build lands,
# the page 404s on its own script, and the person looking at it says
# the site is down - which is exactly what happened here after four
# deploys inside thirteen minutes.
#
# deploy-frontend.yml learned this on a live estate ("mobile view add
# category shown and page broken. crashed" was an open tab meeting a
# deploy that had pruned its stylesheet). develop never did, and
# develop is where people sit with a tab open all day while builds
# land on top of them. Same protection, same retention below.
rsync -az --delete \
--filter='protect public/script/*.js' \
--filter='protect public/style/*.css' \
-e "ssh -i ~/.ssh/develop.pem" \
frontend/public/ "${{ secrets.DEVELOP_USER }}@${{ secrets.DEVELOP_HOST }}:~/posnic-develop/frontend/public/"
rsync -az \
-e "ssh -i ~/.ssh/develop.pem" \
languages/ "${{ secrets.DEVELOP_USER }}@${{ secrets.DEVELOP_HOST }}:~/posnic-develop/languages/"
# Retention: for every hashed bundle family (name.<hash8>.ext), keep
# the newest 3 and remove the rest. An open tab survives at least two
# more deploys; the instance never grows without bound. Same shape as
# deploy-frontend.yml, which is where this was worked out.
ssh -i ~/.ssh/develop.pem "${{ secrets.DEVELOP_USER }}@${{ secrets.DEVELOP_HOST }}" 'bash -s' <<'REMOTE'
set -u
for d in ~/posnic-develop/frontend/public/script ~/posnic-develop/frontend/public/style; do
[ -d "$d" ] || continue
cd "$d"
ls -1 2>/dev/null | grep -E '^[A-Za-z0-9_-]+\.[0-9a-f]{8}\.(js|css)$' \
| sed -E 's/\.[0-9a-f]{8}\.(js|css)$/.\1/' | sort -u \
| while read -r fam; do
base="${fam%.*}"; ext="${fam##*.}"
ls -1t "$base".????????."$ext" 2>/dev/null | tail -n +4 \
| while read -r old; do rm -f -- "$old" && echo "pruned $d/$old"; done
done
done
REMOTE
# THE SANDBOX'S OWN SCRIPTS, which used to be copied by hand.
#
# reset.sh and seed.js decide what the box looks like every morning,
# and a copy on the box that nobody can diff against the repository
# drifts until the two are different programs. A fix committed here
# would sit in git doing nothing until somebody remembered to scp
# it - which is exactly how the nightly reset came to be wiping
# credentials that were supposed to be permanent.
#
# tests/sandbox-scripts-deployed.test.js pins this to what reset.sh
# actually calls.
rsync -az \
-e "ssh -i ~/.ssh/develop.pem" \
scripts/sandbox/reset.sh scripts/sandbox/seed.js scripts/sandbox/keep-branches.js \
"${{ secrets.DEVELOP_USER }}@${{ secrets.DEVELOP_HOST }}:~/posnic-develop/"
ssh -i ~/.ssh/develop.pem "${{ secrets.DEVELOP_USER }}@${{ secrets.DEVELOP_HOST }}" \
'chmod +x ~/posnic-develop/reset.sh' || echo "sandbox: could not mark reset.sh executable"
# The sandbox's own nginx config lives in the repository (scripts/sandbox),
# and the banner it injects is part of what people come here to see. Applied
# on every deploy when the box allows it; otherwise noted and skipped, never
# failed - a banner is not a reason to withhold a build.
rsync -az \
-e "ssh -i ~/.ssh/develop.pem" \
scripts/sandbox/nginx-develop.conf "${{ secrets.DEVELOP_USER }}@${{ secrets.DEVELOP_HOST }}:/tmp/nginx-develop.conf"
ssh -i ~/.ssh/develop.pem "${{ secrets.DEVELOP_USER }}@${{ secrets.DEVELOP_HOST }}" 'bash -s' <<'NGINX'
if sudo -n true 2>/dev/null; then
if ! sudo cmp -s /tmp/nginx-develop.conf /etc/nginx/sites-available/develop; then
sudo cp /tmp/nginx-develop.conf /etc/nginx/sites-available/develop \
&& sudo nginx -t && sudo systemctl reload nginx && echo "nginx: config applied"
else
echo "nginx: config unchanged"
fi
else
echo "nginx: no passwordless sudo on this box; apply scripts/sandbox/nginx-develop.conf by hand"
fi
NGINX
# Stamp the asset version with this commit. Scripts and styles are
# cached four hours on purpose, and a tag that never changes means a
# phone that opened the menu this morning runs this morning's code
# until tonight. The literal in the HTML is what a checkout served
# straight from the tree uses; tests/bundle-deployment.test.js keeps
# the two in step.
sed -i "s/v=20260927-items/v=${GITHUB_SHA:0:8}/g" order/*.html menu/index.html
# The customer-facing bundles. api/app.js serves these from '../menu'
# and '../order' behind an fs.existsSync guard, so leaving them out
# was not an error - it was a silent 404 on every /menu and /order
# URL, which is exactly the surface anybody comes to develop to test.
rsync -az --delete \
-e "ssh -i ~/.ssh/develop.pem" \
menu/ "${{ secrets.DEVELOP_USER }}@${{ secrets.DEVELOP_HOST }}:~/posnic-develop/menu/"
rsync -az --delete \
-e "ssh -i ~/.ssh/develop.pem" \
order/ "${{ secrets.DEVELOP_USER }}@${{ secrets.DEVELOP_HOST }}:~/posnic-develop/order/"
- name: Install and restart
run: |
ssh -i ~/.ssh/develop.pem \
"${{ secrets.DEVELOP_USER }}@${{ secrets.DEVELOP_HOST }}" \
'bash -lc "cd ~/posnic-develop/api && npm install --omit=dev --no-audit --no-fund && pm2 reload posnic-develop --update-env || pm2 start server.js --name posnic-develop"'
- name: Prove it is actually serving
run: |
# A deploy that reports success without checking is how a box sits
# broken for a week while the badge stays green.
#
# Asked of the APP, on the box, over SSH - not through nginx and not
# through Cloudflare. The deploy changed the application; nginx, DNS
# and TLS are separate concerns owned elsewhere, and a job that goes
# red because of one of those is reporting on somebody else's work.
#
# This checked http://<ip> until TLS was set up, at which point nginx
# started answering 301 and every deploy failed on a healthy box -
# the check was measuring the redirect, not the app.
for i in 1 2 3 4 5 6; do
CODE=$(ssh -i ~/.ssh/develop.pem -o StrictHostKeyChecking=no \
"${{ secrets.DEVELOP_USER }}@${{ secrets.DEVELOP_HOST }}" \
"curl -s -o /dev/null -m 10 -w '%{http_code}' http://127.0.0.1:3000/public/login.html" 2>/dev/null || echo 000)
echo "attempt $i: $CODE"
if [ "$CODE" = "200" ]; then
echo "the application is serving on the box"
# Reported, never required: this depends on DNS, Cloudflare and a
# certificate, none of which this deploy touched.
PUB=$(curl -s -o /dev/null -m 20 -w '%{http_code}' https://develop.posnic.io/public/login.html || echo 000)
if [ "$PUB" = "200" ]; then
echo "develop.posnic.io is live"
else
echo "::notice::develop.posnic.io answered ${PUB}. The deploy itself succeeded - check DNS, Cloudflare or the certificate."
fi
exit 0
fi
sleep 10
done
echo "::error::the application did not answer 200 on the box after the deploy"
exit 1