-
Notifications
You must be signed in to change notification settings - Fork 2k
212 lines (184 loc) · 6.47 KB
/
Copy pathserver.yml
File metadata and controls
212 lines (184 loc) · 6.47 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
name: Server
on:
push:
branches: [ main ]
paths:
- 'packages/happy-server/**'
- 'packages/happy-server-self-host/**'
- 'packages/happy-wire/**'
- 'Dockerfile'
- 'Dockerfile.server'
- 'package.json'
- 'pnpm-lock.yaml'
- 'pnpm-workspace.yaml'
- '.npmrc'
- 'scripts/**'
- 'patches/**'
- '.github/workflows/server.yml'
pull_request:
branches: [ main ]
paths:
- 'packages/happy-server/**'
- 'packages/happy-server-self-host/**'
- 'packages/happy-wire/**'
- 'Dockerfile'
- 'Dockerfile.server'
- 'package.json'
- 'pnpm-lock.yaml'
- 'pnpm-workspace.yaml'
- '.npmrc'
- 'scripts/**'
- 'patches/**'
- '.github/workflows/server.yml'
workflow_dispatch:
jobs:
typecheck-and-test:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 20
cache: pnpm
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build happy-wire
run: pnpm --filter @slopus/happy-wire --fail-if-no-match build
# `build` is the production typecheck (tsconfig.build.json, specs excluded).
# This is exactly what Dockerfile.server runs, so a break here is a break there.
- name: Production typecheck
run: pnpm --filter happy-server --fail-if-no-match build
# The full typecheck additionally covers specs, which reach into happy-app
# for cross-package contract checks.
- name: Full typecheck
run: pnpm --filter happy-server --fail-if-no-match typecheck
- name: Unit tests
run: pnpm --filter happy-server --fail-if-no-match test
# Builds the production image and proves it actually serves traffic.
# A container whose CMD silently no-ops exits 0 immediately and would pass
# every check except starting it, which is how a broken image shipped before.
docker-production:
runs-on: ubuntu-latest
timeout-minutes: 25
services:
postgres:
image: postgres:16
env:
POSTGRES_PASSWORD: postgres
POSTGRES_DB: handy
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 10
env:
DATABASE_URL: postgresql://postgres:postgres@127.0.0.1:5432/handy
HANDY_MASTER_SECRET: ci-master-secret-not-used-in-production
PORT: "3005"
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Build production image
run: docker build -t happy-server:ci -f Dockerfile.server .
- name: Apply migrations
run: |
docker run --rm --network host \
-e DATABASE_URL="$DATABASE_URL" \
-w /repo/packages/happy-server \
--entrypoint sh happy-server:ci \
-c '../../node_modules/.bin/prisma migrate deploy'
- name: Start container
run: |
docker run -d --name happy-server-ci --network host \
-e DATABASE_URL="$DATABASE_URL" \
-e HANDY_MASTER_SECRET="$HANDY_MASTER_SECRET" \
-e PORT="$PORT" \
-e METRICS_ENABLED=false \
happy-server:ci
- name: Wait for health
run: |
UP=0
for i in $(seq 1 60); do
if curl -sf -m 2 "http://127.0.0.1:${PORT}/health" -o /dev/null; then
UP=1; echo "server healthy after ${i}s"; break
fi
# A no-op CMD exits immediately — fail fast and loudly rather than
# waiting out the full timeout.
if [ "$(docker inspect -f '{{.State.Running}}' happy-server-ci)" != "true" ]; then
echo "Error: container exited before becoming healthy"
echo "exit code: $(docker inspect -f '{{.State.ExitCode}}' happy-server-ci)"
docker logs happy-server-ci
exit 1
fi
sleep 1
done
if [ "$UP" != "1" ]; then
echo "Error: server did not become healthy"
docker logs happy-server-ci
exit 1
fi
- name: Exercise endpoints
run: |
echo "--- /health"
curl -sf -m 5 "http://127.0.0.1:${PORT}/health"; echo
# Hits the database through Prisma, so this fails if the query engine
# or the schema is not actually usable in the image.
echo "--- /v1/auth/request/status"
PK=$(node -e "console.log(Buffer.alloc(32).toString('base64'))")
BODY=$(curl -sf -m 5 -G "http://127.0.0.1:${PORT}/v1/auth/request/status" --data-urlencode "publicKey=$PK")
echo "$BODY"
echo "$BODY" | grep -q '"status"' || { echo "Error: Prisma-backed endpoint did not respond"; exit 1; }
- name: Container logs
if: always()
run: docker logs happy-server-ci || true
- name: Stop container
if: always()
run: docker rm -f happy-server-ci || true
# The standalone image is the self-host path: embedded PGlite, local files,
# no external services.
docker-standalone:
runs-on: ubuntu-latest
timeout-minutes: 25
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Build standalone image
run: docker build -t happy-server-standalone:ci -f Dockerfile .
- name: Start container
run: |
docker run -d --name happy-standalone-ci -p 3005:3005 \
-e HANDY_MASTER_SECRET=ci-master-secret-not-used-in-production \
happy-server-standalone:ci
- name: Wait for health
run: |
UP=0
for i in $(seq 1 90); do
if curl -sf -m 2 http://127.0.0.1:3005/health -o /dev/null; then
UP=1; echo "standalone healthy after ${i}s"; break
fi
if [ "$(docker inspect -f '{{.State.Running}}' happy-standalone-ci)" != "true" ]; then
echo "Error: container exited before becoming healthy"
echo "exit code: $(docker inspect -f '{{.State.ExitCode}}' happy-standalone-ci)"
docker logs happy-standalone-ci
exit 1
fi
sleep 1
done
if [ "$UP" != "1" ]; then
echo "Error: standalone server did not become healthy"
docker logs happy-standalone-ci
exit 1
fi
- name: Container logs
if: always()
run: docker logs happy-standalone-ci || true
- name: Stop container
if: always()
run: docker rm -f happy-standalone-ci || true