@stackframe/stack-cli 2.8.82 → 2.8.84
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.env.development +89 -0
- package/dist/emulator/cloud-init/emulator/meta-data +2 -0
- package/dist/emulator/cloud-init/emulator/user-data +615 -0
- package/dist/emulator/common.sh +70 -0
- package/dist/emulator/run-emulator.sh +402 -0
- package/dist/index.js +167 -24
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
|
@@ -0,0 +1,615 @@
|
|
|
1
|
+
#cloud-config
|
|
2
|
+
|
|
3
|
+
hostname: stack-emulator
|
|
4
|
+
manage_etc_hosts: true
|
|
5
|
+
|
|
6
|
+
users:
|
|
7
|
+
- name: stack
|
|
8
|
+
shell: /bin/bash
|
|
9
|
+
sudo: ALL=(ALL) NOPASSWD:ALL
|
|
10
|
+
lock_passwd: false
|
|
11
|
+
|
|
12
|
+
chpasswd:
|
|
13
|
+
list: |
|
|
14
|
+
root:stack-emulator
|
|
15
|
+
stack:stack-emulator
|
|
16
|
+
expire: false
|
|
17
|
+
|
|
18
|
+
ssh_pwauth: false
|
|
19
|
+
|
|
20
|
+
package_update: true
|
|
21
|
+
package_upgrade: false
|
|
22
|
+
|
|
23
|
+
packages:
|
|
24
|
+
- docker.io
|
|
25
|
+
- ca-certificates
|
|
26
|
+
- curl
|
|
27
|
+
- netcat-openbsd
|
|
28
|
+
- qemu-guest-agent
|
|
29
|
+
|
|
30
|
+
write_files:
|
|
31
|
+
- path: /usr/local/bin/install-emulator-containers
|
|
32
|
+
permissions: '0755'
|
|
33
|
+
content: |
|
|
34
|
+
#!/bin/bash
|
|
35
|
+
set -euo pipefail
|
|
36
|
+
|
|
37
|
+
mkdir -p /mnt/stack-bundle
|
|
38
|
+
bundle_device="$(readlink -f /dev/disk/by-label/STACKBUNDLE)"
|
|
39
|
+
mount -o ro "$bundle_device" /mnt/stack-bundle
|
|
40
|
+
|
|
41
|
+
systemctl enable --now docker
|
|
42
|
+
until docker info >/dev/null 2>&1; do sleep 1; done
|
|
43
|
+
|
|
44
|
+
gzip -dc /mnt/stack-bundle/img.tgz | docker load
|
|
45
|
+
|
|
46
|
+
if [ -f /mnt/stack-bundle/build.env ]; then
|
|
47
|
+
cp /mnt/stack-bundle/build.env /etc/stack-build.env
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
# build-arch.env lets the guest skip the smoke test on cross-arch TCG.
|
|
51
|
+
if [ -f /mnt/stack-bundle/build-arch.env ]; then
|
|
52
|
+
cp /mnt/stack-bundle/build-arch.env /etc/stack-build-arch.env
|
|
53
|
+
fi
|
|
54
|
+
|
|
55
|
+
- path: /usr/local/bin/render-stack-env
|
|
56
|
+
permissions: '0755'
|
|
57
|
+
content: |
|
|
58
|
+
#!/bin/bash
|
|
59
|
+
set -euo pipefail
|
|
60
|
+
|
|
61
|
+
mkdir -p /mnt/stack-runtime /run/stack-auth /var/lib/stack-auth
|
|
62
|
+
runtime_device="$(readlink -f /dev/disk/by-label/STACKCFG)"
|
|
63
|
+
mountpoint -q /mnt/stack-runtime || mount -o ro "$runtime_device" /mnt/stack-runtime
|
|
64
|
+
|
|
65
|
+
set -a
|
|
66
|
+
source /mnt/stack-runtime/runtime.env
|
|
67
|
+
source /mnt/stack-runtime/base.env
|
|
68
|
+
set +a
|
|
69
|
+
|
|
70
|
+
# Generate and persist the internal-project keys on first boot; reuse
|
|
71
|
+
# across container restarts so the dashboard keeps its internal-project
|
|
72
|
+
# session. Reset via `stack emulator reset`.
|
|
73
|
+
#
|
|
74
|
+
# pck: used by stack-cli to auth against /api/v1/internal/local-emulator/project
|
|
75
|
+
# ssk/sak: required by the emulator's own dashboard (StackServerApp
|
|
76
|
+
# construction throws without them). Not used by user-app flows; the
|
|
77
|
+
# /local-emulator/project route mints separate per-project credentials.
|
|
78
|
+
umask 077
|
|
79
|
+
for key in internal-pck internal-ssk internal-sak; do
|
|
80
|
+
if [ ! -s "/var/lib/stack-auth/$key" ]; then
|
|
81
|
+
openssl rand -hex 32 > "/var/lib/stack-auth/$key"
|
|
82
|
+
fi
|
|
83
|
+
done
|
|
84
|
+
INTERNAL_PCK="$(cat /var/lib/stack-auth/internal-pck)"
|
|
85
|
+
INTERNAL_SSK="$(cat /var/lib/stack-auth/internal-ssk)"
|
|
86
|
+
INTERNAL_SAK="$(cat /var/lib/stack-auth/internal-sak)"
|
|
87
|
+
|
|
88
|
+
# Container-local dependencies run on localhost. Host-only development
|
|
89
|
+
# services (such as the OAuth mock server) are reachable via the QEMU
|
|
90
|
+
# user-network host alias.
|
|
91
|
+
DEPS_HOST=127.0.0.1
|
|
92
|
+
HOST_SERVICES_HOST=10.0.2.2
|
|
93
|
+
P="$STACK_EMULATOR_PORT_PREFIX"
|
|
94
|
+
|
|
95
|
+
{
|
|
96
|
+
# Static vars from base config and runtime (e.g. API keys, feature flags)
|
|
97
|
+
cat /mnt/stack-runtime/base.env
|
|
98
|
+
cat /mnt/stack-runtime/runtime.env
|
|
99
|
+
printf 'STACK_SEED_INTERNAL_PROJECT_PUBLISHABLE_CLIENT_KEY=%s\n' "$INTERNAL_PCK"
|
|
100
|
+
printf 'STACK_SEED_INTERNAL_PROJECT_SECRET_SERVER_KEY=%s\n' "$INTERNAL_SSK"
|
|
101
|
+
printf 'STACK_SEED_INTERNAL_PROJECT_SUPER_SECRET_ADMIN_KEY=%s\n' "$INTERNAL_SAK"
|
|
102
|
+
|
|
103
|
+
# Computed vars — depend on port prefix or deps host
|
|
104
|
+
# Host-side ports (for browser URLs — browser runs on host, not in VM)
|
|
105
|
+
HP_BACKEND="$STACK_EMULATOR_BACKEND_HOST_PORT"
|
|
106
|
+
HP_DASHBOARD="$STACK_EMULATOR_DASHBOARD_HOST_PORT"
|
|
107
|
+
HP_MINIO="$STACK_EMULATOR_MINIO_HOST_PORT"
|
|
108
|
+
HP_INBUCKET="$STACK_EMULATOR_INBUCKET_HOST_PORT"
|
|
109
|
+
|
|
110
|
+
cat <<COMPUTED
|
|
111
|
+
STACK_SKIP_MIGRATIONS=true
|
|
112
|
+
STACK_SKIP_SEED_SCRIPT=true
|
|
113
|
+
NEXT_PUBLIC_STACK_PORT_PREFIX=${P}
|
|
114
|
+
STACK_RUNTIME_WORK_DIR=/app
|
|
115
|
+
STACK_LOCAL_EMULATOR_HOST_MOUNT_ROOT=/host
|
|
116
|
+
NEXT_PUBLIC_STACK_API_URL=http://localhost:${HP_BACKEND}
|
|
117
|
+
NEXT_PUBLIC_STACK_DASHBOARD_URL=http://localhost:${HP_DASHBOARD}
|
|
118
|
+
NEXT_PUBLIC_BROWSER_STACK_API_URL=http://localhost:${HP_BACKEND}
|
|
119
|
+
NEXT_PUBLIC_BROWSER_STACK_DASHBOARD_URL=http://localhost:${HP_DASHBOARD}
|
|
120
|
+
NEXT_PUBLIC_SERVER_STACK_API_URL=http://127.0.0.1:${P}02
|
|
121
|
+
NEXT_PUBLIC_SERVER_STACK_DASHBOARD_URL=http://127.0.0.1:${P}01
|
|
122
|
+
NEXT_PUBLIC_STACK_SVIX_SERVER_URL=http://localhost:${HP_BACKEND}
|
|
123
|
+
STACK_DATABASE_CONNECTION_STRING=postgres://postgres:PASSWORD-PLACEHOLDER--uqfEC1hmmv@${DEPS_HOST}:5432/stackframe
|
|
124
|
+
STACK_EMAIL_HOST=${DEPS_HOST}
|
|
125
|
+
STACK_SVIX_SERVER_URL=http://${DEPS_HOST}:8071
|
|
126
|
+
STACK_S3_ENDPOINT=http://${DEPS_HOST}:9090
|
|
127
|
+
STACK_S3_PUBLIC_ENDPOINT=http://localhost:${HP_MINIO}/stack-storage
|
|
128
|
+
STACK_QSTASH_URL=http://${DEPS_HOST}:8080
|
|
129
|
+
STACK_CLICKHOUSE_URL=http://${DEPS_HOST}:8123
|
|
130
|
+
STACK_EMAIL_MONITOR_VERIFICATION_CALLBACK_URL=http://localhost:${HP_DASHBOARD}/handler/email-verification
|
|
131
|
+
STACK_EMAIL_MONITOR_INBUCKET_API_URL=http://${DEPS_HOST}:9001
|
|
132
|
+
STACK_OAUTH_MOCK_URL=http://localhost:${P}14
|
|
133
|
+
STACK_FREESTYLE_API_ENDPOINT=http://${DEPS_HOST}:8180
|
|
134
|
+
STACK_STRIPE_MOCK_PORT=12111
|
|
135
|
+
NEXT_PUBLIC_STACK_STRIPE_PUBLISHABLE_KEY=pk_test_mock_publishable_key_for_local_emulator
|
|
136
|
+
BACKEND_PORT=${P}02
|
|
137
|
+
DASHBOARD_PORT=${P}01
|
|
138
|
+
COMPUTED
|
|
139
|
+
} > /run/stack-auth/local-emulator.env
|
|
140
|
+
|
|
141
|
+
- path: /usr/local/bin/mount-host-fs
|
|
142
|
+
permissions: '0755'
|
|
143
|
+
content: |
|
|
144
|
+
#!/bin/bash
|
|
145
|
+
set -euo pipefail
|
|
146
|
+
mkdir -p /host
|
|
147
|
+
if ! mountpoint -q /host; then
|
|
148
|
+
if ! mount -t 9p -o trans=virtio,version=9p2000.L hostfs /host; then
|
|
149
|
+
echo "Failed to mount host filesystem at /host" >&2
|
|
150
|
+
exit 1
|
|
151
|
+
fi
|
|
152
|
+
fi
|
|
153
|
+
|
|
154
|
+
- path: /usr/local/bin/run-stack-container
|
|
155
|
+
permissions: '0755'
|
|
156
|
+
content: |
|
|
157
|
+
#!/bin/bash
|
|
158
|
+
set -euo pipefail
|
|
159
|
+
|
|
160
|
+
/usr/local/bin/mount-host-fs
|
|
161
|
+
/usr/local/bin/render-stack-env
|
|
162
|
+
|
|
163
|
+
# Publish the internal publishable client key to the host via 9p so the
|
|
164
|
+
# stack-cli can authenticate its bootstrap call to
|
|
165
|
+
# /api/v1/internal/local-emulator/project.
|
|
166
|
+
set -a
|
|
167
|
+
source /mnt/stack-runtime/runtime.env
|
|
168
|
+
set +a
|
|
169
|
+
if [ -n "${STACK_EMULATOR_VM_DIR_HOST:-}" ] && [ -s /var/lib/stack-auth/internal-pck ]; then
|
|
170
|
+
install -m 0600 /var/lib/stack-auth/internal-pck \
|
|
171
|
+
"/host${STACK_EMULATOR_VM_DIR_HOST}/internal-pck"
|
|
172
|
+
fi
|
|
173
|
+
|
|
174
|
+
docker rm -f stack >/dev/null 2>&1 || true
|
|
175
|
+
|
|
176
|
+
# Mirror container stdout/stderr to a host-visible log for debugging.
|
|
177
|
+
# The container already bind-mounts /host:/host, so we reuse that path.
|
|
178
|
+
# Falls back to stdout (captured by systemd-journald) when no host log is set.
|
|
179
|
+
if [ -n "${STACK_EMULATOR_VM_DIR_HOST:-}" ]; then
|
|
180
|
+
host_log="/host${STACK_EMULATOR_VM_DIR_HOST}/stack.log"
|
|
181
|
+
: > "$host_log" 2>/dev/null || true
|
|
182
|
+
exec docker run \
|
|
183
|
+
--rm \
|
|
184
|
+
--name stack \
|
|
185
|
+
--network host \
|
|
186
|
+
--add-host host.docker.internal:host-gateway \
|
|
187
|
+
--env-file /run/stack-auth/local-emulator.env \
|
|
188
|
+
-v stack-postgres-data:/data/postgres \
|
|
189
|
+
-v stack-redis-data:/data/redis \
|
|
190
|
+
-v stack-clickhouse-data:/data/clickhouse \
|
|
191
|
+
-v stack-minio-data:/data/minio \
|
|
192
|
+
-v stack-inbucket-data:/data/inbucket \
|
|
193
|
+
-v /host:/host \
|
|
194
|
+
stack-local-emulator 2>&1 | tee -a "$host_log"
|
|
195
|
+
else
|
|
196
|
+
exec docker run \
|
|
197
|
+
--rm \
|
|
198
|
+
--name stack \
|
|
199
|
+
--network host \
|
|
200
|
+
--add-host host.docker.internal:host-gateway \
|
|
201
|
+
--env-file /run/stack-auth/local-emulator.env \
|
|
202
|
+
-v stack-postgres-data:/data/postgres \
|
|
203
|
+
-v stack-redis-data:/data/redis \
|
|
204
|
+
-v stack-clickhouse-data:/data/clickhouse \
|
|
205
|
+
-v stack-minio-data:/data/minio \
|
|
206
|
+
-v stack-inbucket-data:/data/inbucket \
|
|
207
|
+
-v /host:/host \
|
|
208
|
+
stack-local-emulator
|
|
209
|
+
fi
|
|
210
|
+
|
|
211
|
+
- path: /usr/local/bin/wait-for-deps
|
|
212
|
+
permissions: '0755'
|
|
213
|
+
content: |
|
|
214
|
+
#!/bin/bash
|
|
215
|
+
set -uo pipefail
|
|
216
|
+
|
|
217
|
+
# Hard upper bound across the whole dep wait. Under TCG every service
|
|
218
|
+
# init is 5-20x slower than native, so we allow a generous budget, but
|
|
219
|
+
# if we cross it something is genuinely stuck and we need to surface it.
|
|
220
|
+
DEPS_TIMEOUT="${STACK_DEPS_TIMEOUT:-1500}"
|
|
221
|
+
DEPS_CONTAINER="${STACK_DEPS_CONTAINER:-stack-build-init}"
|
|
222
|
+
start=$SECONDS
|
|
223
|
+
log() { /usr/local/bin/log-provision "wait-for-deps: $*"; }
|
|
224
|
+
|
|
225
|
+
# name|probe pairs — probe runs through `eval` and must exit 0 when ready.
|
|
226
|
+
# No --max-time on these: under slow TCG a service may take >3s to
|
|
227
|
+
# respond; let curl wait, outer DEPS_TIMEOUT bounds the whole dep wait.
|
|
228
|
+
SERVICES=(
|
|
229
|
+
'postgres|nc -z 127.0.0.1 5432'
|
|
230
|
+
'clickhouse|curl -sf http://127.0.0.1:8123/ping'
|
|
231
|
+
'svix|curl -sf http://127.0.0.1:8071/api/v1/health/'
|
|
232
|
+
'minio|curl -sf http://127.0.0.1:9090/minio/health/live'
|
|
233
|
+
'qstash|[ "$(curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:8080/ 2>/dev/null || true)" = "401" ]'
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
dump_diagnostics() {
|
|
237
|
+
log "dumping diagnostics for stuck dep wait..."
|
|
238
|
+
log "--- docker ps -a ---"
|
|
239
|
+
docker ps -a 2>&1 | /usr/local/bin/log-provision-stream "wait-for-deps: ps" || true
|
|
240
|
+
log "--- docker logs ${DEPS_CONTAINER} (last 300 lines) ---"
|
|
241
|
+
docker logs --tail 300 "$DEPS_CONTAINER" 2>&1 | /usr/local/bin/log-provision-stream "wait-for-deps: deps" || true
|
|
242
|
+
log "--- per-service probes (3s timeout) ---"
|
|
243
|
+
nc -z -w 3 127.0.0.1 5432 >/dev/null 2>&1 && log "postgres:5432 reachable" || log "postgres:5432 NOT reachable"
|
|
244
|
+
curl -sf --max-time 3 http://127.0.0.1:8123/ping >/dev/null 2>&1 && log "clickhouse:8123 reachable" || log "clickhouse:8123 NOT reachable"
|
|
245
|
+
curl -sf --max-time 3 http://127.0.0.1:8071/api/v1/health/ >/dev/null 2>&1 && log "svix:8071 reachable" || log "svix:8071 NOT reachable"
|
|
246
|
+
curl -sf --max-time 3 http://127.0.0.1:9090/minio/health/live >/dev/null 2>&1 && log "minio:9090 reachable" || log "minio:9090 NOT reachable"
|
|
247
|
+
code=$(curl -s -o /dev/null -w '%{http_code}' --max-time 3 http://127.0.0.1:8080/ 2>/dev/null || true)
|
|
248
|
+
[ "$code" = "401" ] && log "qstash:8080 reachable (401)" || log "qstash:8080 NOT reachable (code=${code:-none})"
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
wait_for() {
|
|
252
|
+
local name="$1" probe="$2" elapsed
|
|
253
|
+
local svc_start=$SECONDS
|
|
254
|
+
local next_heartbeat=$((svc_start + 30))
|
|
255
|
+
while true; do
|
|
256
|
+
if eval "$probe" >/dev/null 2>&1; then
|
|
257
|
+
elapsed=$((SECONDS - svc_start))
|
|
258
|
+
log "${name} ready (${elapsed}s)"
|
|
259
|
+
return 0
|
|
260
|
+
fi
|
|
261
|
+
if [ "$SECONDS" -ge "$next_heartbeat" ]; then
|
|
262
|
+
log "still waiting for ${name} ($((SECONDS - svc_start))s elapsed)"
|
|
263
|
+
next_heartbeat=$((SECONDS + 30))
|
|
264
|
+
fi
|
|
265
|
+
if [ "$((SECONDS - start))" -ge "$DEPS_TIMEOUT" ]; then
|
|
266
|
+
elapsed=$((SECONDS - start))
|
|
267
|
+
log "TIMEOUT waiting for ${name} after ${elapsed}s (hard cap ${DEPS_TIMEOUT}s)"
|
|
268
|
+
dump_diagnostics
|
|
269
|
+
exit 1
|
|
270
|
+
fi
|
|
271
|
+
sleep 2
|
|
272
|
+
done
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
log "starting dep wait (timeout=${DEPS_TIMEOUT}s)"
|
|
276
|
+
for entry in "${SERVICES[@]}"; do
|
|
277
|
+
wait_for "${entry%%|*}" "${entry#*|}"
|
|
278
|
+
done
|
|
279
|
+
log "all deps ready ($((SECONDS - start))s total)"
|
|
280
|
+
|
|
281
|
+
- path: /etc/stack-build-computed.env
|
|
282
|
+
content: |
|
|
283
|
+
USE_INLINE_ENV_VARS=true
|
|
284
|
+
NEXT_PUBLIC_STACK_API_URL=http://localhost:8102
|
|
285
|
+
NEXT_PUBLIC_STACK_DASHBOARD_URL=http://localhost:8101
|
|
286
|
+
NEXT_PUBLIC_BROWSER_STACK_API_URL=http://localhost:8102
|
|
287
|
+
NEXT_PUBLIC_BROWSER_STACK_DASHBOARD_URL=http://localhost:8101
|
|
288
|
+
NEXT_PUBLIC_SERVER_STACK_API_URL=http://127.0.0.1:8102
|
|
289
|
+
NEXT_PUBLIC_SERVER_STACK_DASHBOARD_URL=http://127.0.0.1:8101
|
|
290
|
+
NEXT_PUBLIC_STACK_SVIX_SERVER_URL=http://localhost:8071
|
|
291
|
+
NEXT_PUBLIC_STACK_PORT_PREFIX=81
|
|
292
|
+
STACK_CLICKHOUSE_DATABASE=default
|
|
293
|
+
BACKEND_PORT=8102
|
|
294
|
+
DASHBOARD_PORT=8101
|
|
295
|
+
|
|
296
|
+
- path: /usr/local/bin/log-provision
|
|
297
|
+
permissions: '0755'
|
|
298
|
+
content: |
|
|
299
|
+
#!/bin/bash
|
|
300
|
+
set -euo pipefail
|
|
301
|
+
|
|
302
|
+
msg="$*"
|
|
303
|
+
echo "STACK_PROVISION: $msg"
|
|
304
|
+
if [ -n "${STACK_PROVISION_LOG_FILE:-}" ]; then
|
|
305
|
+
printf '%s\n' "$msg" >> "$STACK_PROVISION_LOG_FILE"
|
|
306
|
+
fi
|
|
307
|
+
|
|
308
|
+
- path: /usr/local/bin/log-provision-stream
|
|
309
|
+
permissions: '0755'
|
|
310
|
+
content: |
|
|
311
|
+
#!/bin/bash
|
|
312
|
+
set -uo pipefail
|
|
313
|
+
|
|
314
|
+
prefix="${1:-}"
|
|
315
|
+
while IFS= read -r line; do
|
|
316
|
+
/usr/local/bin/log-provision "${prefix}: ${line}"
|
|
317
|
+
done
|
|
318
|
+
|
|
319
|
+
- path: /usr/local/bin/run-build-migrations
|
|
320
|
+
permissions: '0755'
|
|
321
|
+
content: |
|
|
322
|
+
#!/bin/bash
|
|
323
|
+
set -euo pipefail
|
|
324
|
+
|
|
325
|
+
log() { /usr/local/bin/log-provision "$*"; }
|
|
326
|
+
|
|
327
|
+
log "Starting deps container..."
|
|
328
|
+
docker run --rm --name stack-build-init \
|
|
329
|
+
--network host \
|
|
330
|
+
-e STACK_DEPS_ONLY=true \
|
|
331
|
+
-v stack-postgres-data:/data/postgres \
|
|
332
|
+
-v stack-redis-data:/data/redis \
|
|
333
|
+
-v stack-clickhouse-data:/data/clickhouse \
|
|
334
|
+
-v stack-minio-data:/data/minio \
|
|
335
|
+
-v stack-inbucket-data:/data/inbucket \
|
|
336
|
+
-d stack-local-emulator
|
|
337
|
+
|
|
338
|
+
log "Waiting for deps (postgres, redis, clickhouse, minio, qstash)..."
|
|
339
|
+
/usr/local/bin/wait-for-deps
|
|
340
|
+
log "Deps ready."
|
|
341
|
+
|
|
342
|
+
# Wait for init-services.sh (MinIO buckets, ClickHouse DB creation)
|
|
343
|
+
log "Waiting for init-services.sh..."
|
|
344
|
+
timeout=120
|
|
345
|
+
elapsed=0
|
|
346
|
+
while [ "$elapsed" -lt "$timeout" ]; do
|
|
347
|
+
if docker exec stack-build-init test -f /var/run/stack-local-init-services.done 2>/dev/null; then
|
|
348
|
+
break
|
|
349
|
+
fi
|
|
350
|
+
sleep 1
|
|
351
|
+
elapsed=$((elapsed + 1))
|
|
352
|
+
done
|
|
353
|
+
if [ "$elapsed" -ge "$timeout" ]; then
|
|
354
|
+
log "ERROR: init-services.sh did not finish within ${timeout}s"
|
|
355
|
+
exit 1
|
|
356
|
+
fi
|
|
357
|
+
log "init-services done (${elapsed}s)."
|
|
358
|
+
|
|
359
|
+
log "Running migrations..."
|
|
360
|
+
# Cross-arch TCG mistranslates V8's JIT-emitted arm64, and V8's wasm
|
|
361
|
+
# tier-up path trips an InnerPointerToCodeCache check deep in the heap
|
|
362
|
+
# (Runtime_WasmTriggerTierUp → StackFrameIterator::Advance crashes
|
|
363
|
+
# when Wasm code has been freed while a frame still references it).
|
|
364
|
+
# --no-opt keeps JS off TurboFan/Maglev
|
|
365
|
+
# --no-wasm-tier-up keeps Wasm on Liftoff (no TurboFan)
|
|
366
|
+
# --no-wasm-dynamic-tiering suppresses the tier-up decision runtime call
|
|
367
|
+
# --no-wasm-code-gc keeps Wasm code alive across stack walks
|
|
368
|
+
# All four are no-ops under KVM, and must be passed on node's CLI
|
|
369
|
+
# (NODE_OPTIONS rejects them).
|
|
370
|
+
migrate_log="$(mktemp)"
|
|
371
|
+
set +e
|
|
372
|
+
docker exec \
|
|
373
|
+
--env-file /etc/stack-build.env \
|
|
374
|
+
--env-file /etc/stack-build-computed.env \
|
|
375
|
+
stack-build-init \
|
|
376
|
+
sh -c 'cd /app/apps/backend && node --no-opt --no-wasm-tier-up --no-wasm-dynamic-tiering --no-wasm-code-gc dist/db-migrations.mjs migrate && node --no-opt --no-wasm-tier-up --no-wasm-dynamic-tiering --no-wasm-code-gc dist/db-migrations.mjs seed' \
|
|
377
|
+
> "$migrate_log" 2>&1
|
|
378
|
+
migrate_status=$?
|
|
379
|
+
set -e
|
|
380
|
+
if [ "$migrate_status" -ne 0 ]; then
|
|
381
|
+
log "MIGRATIONS FAILED (exit ${migrate_status}) — last 200 lines of migration output:"
|
|
382
|
+
tail -200 "$migrate_log" | /usr/local/bin/log-provision-stream "migrate" || true
|
|
383
|
+
rm -f "$migrate_log"
|
|
384
|
+
exit "$migrate_status"
|
|
385
|
+
fi
|
|
386
|
+
rm -f "$migrate_log"
|
|
387
|
+
log "Migrations + seed complete."
|
|
388
|
+
|
|
389
|
+
log "Stopping deps container..."
|
|
390
|
+
docker stop stack-build-init || true
|
|
391
|
+
log "run-build-migrations done."
|
|
392
|
+
|
|
393
|
+
- path: /usr/local/bin/slim-docker-image
|
|
394
|
+
permissions: '0755'
|
|
395
|
+
content: |
|
|
396
|
+
#!/bin/bash
|
|
397
|
+
set -euo pipefail
|
|
398
|
+
|
|
399
|
+
log() { /usr/local/bin/log-provision "$*"; }
|
|
400
|
+
|
|
401
|
+
log "Building slim Docker image..."
|
|
402
|
+
docker build -t stack-local-emulator-slim - <<'DOCKERFILE'
|
|
403
|
+
FROM stack-local-emulator
|
|
404
|
+
RUN rm -rf /app/node_modules /app/apps/backend/dist && \
|
|
405
|
+
mv /app/node_modules.standalone /app/node_modules && \
|
|
406
|
+
for entry in /app/node_modules/.pnpm/node_modules/*; do \
|
|
407
|
+
name="$(basename "$entry")"; \
|
|
408
|
+
[ "$name" = ".bin" ] && continue; \
|
|
409
|
+
ln -sf ".pnpm/node_modules/$name" "/app/node_modules/$name" 2>/dev/null || true; \
|
|
410
|
+
done
|
|
411
|
+
DOCKERFILE
|
|
412
|
+
log "Slim image built."
|
|
413
|
+
|
|
414
|
+
# Determine build arch to decide whether to run the smoke test. Cross-arch
|
|
415
|
+
# (TCG) builds can't reliably run the Next.js backend inside the smoke
|
|
416
|
+
# test container: V8 JIT ↔ QEMU TCG mistranslations crash the process,
|
|
417
|
+
# and even with --jitless the backend is too slow to respond within any
|
|
418
|
+
# sane timeout. amd64 builds run under KVM and are unaffected.
|
|
419
|
+
BUILD_ARCH=""
|
|
420
|
+
if [ -f /etc/stack-build-arch.env ]; then
|
|
421
|
+
# shellcheck disable=SC1091
|
|
422
|
+
. /etc/stack-build-arch.env
|
|
423
|
+
BUILD_ARCH="${STACK_EMULATOR_BUILD_ARCH:-}"
|
|
424
|
+
fi
|
|
425
|
+
|
|
426
|
+
if [ "$BUILD_ARCH" = "arm64" ]; then
|
|
427
|
+
log "Skipping smoke test: build arch is arm64 and cross-arch TCG can't reliably run the backend."
|
|
428
|
+
else
|
|
429
|
+
log "Running smoke test on slim image..."
|
|
430
|
+
# build.env sets NEXT_PUBLIC_STACK_IS_LOCAL_EMULATOR=true, which makes
|
|
431
|
+
# docker/server/entrypoint.sh require the three internal SEED keys.
|
|
432
|
+
# At real-VM boot those come from render-stack-env via
|
|
433
|
+
# /run/stack-auth/local-emulator.env, but that path doesn't run during
|
|
434
|
+
# the build-time smoke test. Mint throwaway hex keys for this container
|
|
435
|
+
# only; they must be hex because entrypoint.sh also validates that
|
|
436
|
+
# before the internal ApiKeySet bootstrap SQL.
|
|
437
|
+
SMOKE_PCK="$(openssl rand -hex 32)"
|
|
438
|
+
SMOKE_SSK="$(openssl rand -hex 32)"
|
|
439
|
+
SMOKE_SAK="$(openssl rand -hex 32)"
|
|
440
|
+
docker run --rm --name smoke-test \
|
|
441
|
+
--network host \
|
|
442
|
+
--env-file /etc/stack-build.env \
|
|
443
|
+
--env-file /etc/stack-build-computed.env \
|
|
444
|
+
-e STACK_SEED_INTERNAL_PROJECT_PUBLISHABLE_CLIENT_KEY="$SMOKE_PCK" \
|
|
445
|
+
-e STACK_SEED_INTERNAL_PROJECT_SECRET_SERVER_KEY="$SMOKE_SSK" \
|
|
446
|
+
-e STACK_SEED_INTERNAL_PROJECT_SUPER_SECRET_ADMIN_KEY="$SMOKE_SAK" \
|
|
447
|
+
-e STACK_SKIP_MIGRATIONS=true \
|
|
448
|
+
-e STACK_SKIP_SEED_SCRIPT=true \
|
|
449
|
+
-e STACK_RUNTIME_WORK_DIR=/app \
|
|
450
|
+
-v stack-postgres-data:/data/postgres \
|
|
451
|
+
-v stack-redis-data:/data/redis \
|
|
452
|
+
-v stack-clickhouse-data:/data/clickhouse \
|
|
453
|
+
-v stack-minio-data:/data/minio \
|
|
454
|
+
-v stack-inbucket-data:/data/inbucket \
|
|
455
|
+
-d stack-local-emulator-slim
|
|
456
|
+
|
|
457
|
+
smoke_timeout=300
|
|
458
|
+
smoke_elapsed=0
|
|
459
|
+
smoke_passed=false
|
|
460
|
+
while [ "$smoke_elapsed" -lt "$smoke_timeout" ]; do
|
|
461
|
+
code=$(curl -s -o /dev/null -w "%{http_code}" --max-time 3 http://127.0.0.1:8102/health?db=1 2>/dev/null || true)
|
|
462
|
+
if [ "$code" = "200" ]; then
|
|
463
|
+
smoke_passed=true
|
|
464
|
+
break
|
|
465
|
+
fi
|
|
466
|
+
sleep 2
|
|
467
|
+
smoke_elapsed=$((smoke_elapsed + 2))
|
|
468
|
+
done
|
|
469
|
+
|
|
470
|
+
if [ "$smoke_passed" = "false" ]; then
|
|
471
|
+
log "SMOKE TEST FAILED: backend /health?db=1 did not return 200 within ${smoke_timeout}s"
|
|
472
|
+
log "--- docker ps -a ---"
|
|
473
|
+
docker ps -a 2>&1 | /usr/local/bin/log-provision-stream "ps" || true
|
|
474
|
+
log "--- smoke-test container logs (last 200 lines) ---"
|
|
475
|
+
docker logs --tail 200 smoke-test 2>&1 | /usr/local/bin/log-provision-stream "smoke-test" || true
|
|
476
|
+
log "--- free -m ---"
|
|
477
|
+
free -m 2>&1 | /usr/local/bin/log-provision-stream "mem" || true
|
|
478
|
+
log "--- curl -v /health?db=1 ---"
|
|
479
|
+
curl -v --max-time 5 http://127.0.0.1:8102/health?db=1 2>&1 | /usr/local/bin/log-provision-stream "curl" || true
|
|
480
|
+
docker stop smoke-test 2>/dev/null || true
|
|
481
|
+
exit 1
|
|
482
|
+
fi
|
|
483
|
+
|
|
484
|
+
docker stop smoke-test 2>/dev/null || true
|
|
485
|
+
sleep 2
|
|
486
|
+
log "Smoke test passed (${smoke_elapsed}s)."
|
|
487
|
+
fi
|
|
488
|
+
|
|
489
|
+
log "Flattening image (docker export/import)..."
|
|
490
|
+
docker create --name flatten stack-local-emulator-slim /bin/true
|
|
491
|
+
docker export flatten | docker import \
|
|
492
|
+
--change 'WORKDIR /app' \
|
|
493
|
+
--change 'ENTRYPOINT ["/entrypoint.sh"]' \
|
|
494
|
+
--change 'EXPOSE 5432 6379 2500 9001 1100 8071 8123 9009 9090 8080 8101 8102' \
|
|
495
|
+
--change 'ENV DEBIAN_FRONTEND=noninteractive' \
|
|
496
|
+
- stack-local-emulator:final
|
|
497
|
+
log "Flatten done."
|
|
498
|
+
|
|
499
|
+
log "Saving final image to /var/tmp..."
|
|
500
|
+
docker rm flatten
|
|
501
|
+
docker save stack-local-emulator:final -o /var/tmp/final-image.tar
|
|
502
|
+
mv /var/lib/docker/volumes /var/tmp/volumes-backup
|
|
503
|
+
log "Nuking Docker storage and reloading..."
|
|
504
|
+
systemctl stop docker containerd
|
|
505
|
+
rm -rf /var/lib/docker /var/lib/containerd
|
|
506
|
+
systemctl start docker containerd
|
|
507
|
+
until docker info >/dev/null 2>&1; do sleep 1; done
|
|
508
|
+
docker load -i /var/tmp/final-image.tar
|
|
509
|
+
docker tag stack-local-emulator:final stack-local-emulator
|
|
510
|
+
docker rmi stack-local-emulator:final || true
|
|
511
|
+
rm -f /var/tmp/final-image.tar
|
|
512
|
+
systemctl stop docker
|
|
513
|
+
rm -rf /var/lib/docker/volumes
|
|
514
|
+
mv /var/tmp/volumes-backup /var/lib/docker/volumes
|
|
515
|
+
systemctl start docker
|
|
516
|
+
log "Docker storage rebuilt."
|
|
517
|
+
|
|
518
|
+
log "Zeroing free space for qcow2 compression..."
|
|
519
|
+
dd if=/dev/zero of=/zero.fill bs=1M 2>/dev/null || true
|
|
520
|
+
rm -f /zero.fill
|
|
521
|
+
sync
|
|
522
|
+
fstrim -av 2>/dev/null || true
|
|
523
|
+
log "slim-docker-image done."
|
|
524
|
+
|
|
525
|
+
- path: /etc/systemd/system/stack.service
|
|
526
|
+
content: |
|
|
527
|
+
[Unit]
|
|
528
|
+
Description=Stack Auth local emulator
|
|
529
|
+
Wants=network-online.target docker.service
|
|
530
|
+
After=network-online.target docker.service
|
|
531
|
+
|
|
532
|
+
[Service]
|
|
533
|
+
Restart=always
|
|
534
|
+
RestartSec=5
|
|
535
|
+
TimeoutStartSec=0
|
|
536
|
+
ExecStart=/usr/local/bin/run-stack-container
|
|
537
|
+
ExecStop=/usr/bin/docker stop stack
|
|
538
|
+
|
|
539
|
+
[Install]
|
|
540
|
+
WantedBy=multi-user.target
|
|
541
|
+
|
|
542
|
+
- path: /usr/local/bin/provision-build
|
|
543
|
+
permissions: '0755'
|
|
544
|
+
content: |
|
|
545
|
+
#!/bin/bash
|
|
546
|
+
set -euo pipefail
|
|
547
|
+
|
|
548
|
+
if bash /usr/local/bin/mount-host-fs 2>/dev/null; then
|
|
549
|
+
export STACK_PROVISION_LOG_FILE=/host/provision.log
|
|
550
|
+
: > "$STACK_PROVISION_LOG_FILE"
|
|
551
|
+
else
|
|
552
|
+
export STACK_PROVISION_LOG_FILE=""
|
|
553
|
+
fi
|
|
554
|
+
|
|
555
|
+
write_marker_to_consoles() {
|
|
556
|
+
local marker="$1"
|
|
557
|
+
for dev in /dev/console /dev/ttyAMA0 /dev/ttyS0; do
|
|
558
|
+
echo "$marker" > "$dev" 2>/dev/null || true
|
|
559
|
+
done
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
cleanup() {
|
|
563
|
+
local status=$?
|
|
564
|
+
if [ "$status" -ne 0 ]; then
|
|
565
|
+
if [ -n "${STACK_PROVISION_LOG_FILE:-}" ]; then
|
|
566
|
+
printf 'ERROR: provision-build exited with code %s\n' "$status" >> "$STACK_PROVISION_LOG_FILE"
|
|
567
|
+
printf '%s\n' "STACK_CLOUD_INIT_FAILED" >> "$STACK_PROVISION_LOG_FILE"
|
|
568
|
+
fi
|
|
569
|
+
write_marker_to_consoles "STACK_CLOUD_INIT_FAILED"
|
|
570
|
+
sync || true
|
|
571
|
+
(sleep 2 && shutdown -P now) &
|
|
572
|
+
(sleep 15 && poweroff -f) &
|
|
573
|
+
fi
|
|
574
|
+
}
|
|
575
|
+
trap cleanup EXIT
|
|
576
|
+
|
|
577
|
+
SERIAL=""
|
|
578
|
+
for d in /dev/ttyAMA0 /dev/ttyS0; do
|
|
579
|
+
[ -c "$d" ] && SERIAL="$d" && break
|
|
580
|
+
done
|
|
581
|
+
if [ -n "$SERIAL" ]; then
|
|
582
|
+
exec > >(tee -a "$SERIAL") 2>&1
|
|
583
|
+
fi
|
|
584
|
+
|
|
585
|
+
log_provision() {
|
|
586
|
+
/usr/local/bin/log-provision "$*"
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
log_provision "runcmd starting"
|
|
590
|
+
|
|
591
|
+
systemctl disable --now ssh || true
|
|
592
|
+
systemctl mask ssh || true
|
|
593
|
+
|
|
594
|
+
log_provision "installing emulator containers"
|
|
595
|
+
bash /usr/local/bin/install-emulator-containers
|
|
596
|
+
|
|
597
|
+
systemctl daemon-reload
|
|
598
|
+
systemctl enable stack.service
|
|
599
|
+
|
|
600
|
+
log_provision "starting build migrations"
|
|
601
|
+
bash /usr/local/bin/run-build-migrations
|
|
602
|
+
|
|
603
|
+
log_provision "starting slim-docker-image"
|
|
604
|
+
bash /usr/local/bin/slim-docker-image
|
|
605
|
+
|
|
606
|
+
log_provision "build pipeline complete"
|
|
607
|
+
if [ -n "${STACK_PROVISION_LOG_FILE:-}" ]; then
|
|
608
|
+
printf '%s\n' "STACK_CLOUD_INIT_DONE" >> "$STACK_PROVISION_LOG_FILE"
|
|
609
|
+
fi
|
|
610
|
+
write_marker_to_consoles "STACK_CLOUD_INIT_DONE"
|
|
611
|
+
|
|
612
|
+
shutdown -P now
|
|
613
|
+
|
|
614
|
+
runcmd:
|
|
615
|
+
- [bash, /usr/local/bin/provision-build]
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Shared helpers for QEMU emulator scripts.
|
|
3
|
+
# Source this file; do not execute it directly.
|
|
4
|
+
|
|
5
|
+
AARCH64_FIRMWARE_PATHS=(
|
|
6
|
+
/opt/homebrew/share/qemu/edk2-aarch64-code.fd
|
|
7
|
+
/usr/share/qemu/edk2-aarch64-code.fd
|
|
8
|
+
/usr/share/AAVMF/AAVMF_CODE.fd
|
|
9
|
+
/usr/share/qemu-efi-aarch64/QEMU_EFI.fd
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
detect_host() {
|
|
13
|
+
case "$(uname -m)" in
|
|
14
|
+
arm64|aarch64) HOST_ARCH="arm64" ;;
|
|
15
|
+
x86_64|amd64) HOST_ARCH="amd64" ;;
|
|
16
|
+
*) echo "Unsupported host architecture: $(uname -m)" >&2; exit 1 ;;
|
|
17
|
+
esac
|
|
18
|
+
|
|
19
|
+
case "$(uname -s)" in
|
|
20
|
+
Darwin) HOST_OS="darwin" ;;
|
|
21
|
+
Linux) HOST_OS="linux" ;;
|
|
22
|
+
MINGW*|MSYS*|CYGWIN*) HOST_OS="windows" ;;
|
|
23
|
+
*) HOST_OS="unknown" ;;
|
|
24
|
+
esac
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
qemu_binary_for_arch() {
|
|
28
|
+
case "$1" in
|
|
29
|
+
arm64) echo "qemu-system-aarch64" ;;
|
|
30
|
+
amd64) echo "qemu-system-x86_64" ;;
|
|
31
|
+
*) return 1 ;;
|
|
32
|
+
esac
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
find_aarch64_firmware() {
|
|
36
|
+
local p
|
|
37
|
+
for p in "${AARCH64_FIRMWARE_PATHS[@]}"; do
|
|
38
|
+
if [ -f "$p" ]; then
|
|
39
|
+
echo "$p"
|
|
40
|
+
return 0
|
|
41
|
+
fi
|
|
42
|
+
done
|
|
43
|
+
echo "No aarch64 UEFI firmware found." >&2
|
|
44
|
+
return 1
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
make_iso_from_dir() {
|
|
48
|
+
local iso_path="$1"
|
|
49
|
+
local volume_name="$2"
|
|
50
|
+
local source_dir="$3"
|
|
51
|
+
|
|
52
|
+
rm -f "$iso_path" "${iso_path}.iso"
|
|
53
|
+
if command -v hdiutil >/dev/null 2>&1; then
|
|
54
|
+
local tmp_dir
|
|
55
|
+
tmp_dir="$(mktemp -d /tmp/stack-emulator-iso-XXXXXX)"
|
|
56
|
+
cp -R "$source_dir/." "$tmp_dir/"
|
|
57
|
+
hdiutil makehybrid -o "$iso_path" "$tmp_dir" -joliet -iso -default-volume-name "$volume_name" 2>/dev/null
|
|
58
|
+
if [ -f "${iso_path}.iso" ]; then
|
|
59
|
+
mv "${iso_path}.iso" "$iso_path"
|
|
60
|
+
fi
|
|
61
|
+
rm -rf "$tmp_dir"
|
|
62
|
+
elif command -v mkisofs >/dev/null 2>&1; then
|
|
63
|
+
mkisofs -output "$iso_path" -volid "$volume_name" -joliet -rock "$source_dir" >/dev/null 2>&1
|
|
64
|
+
elif command -v genisoimage >/dev/null 2>&1; then
|
|
65
|
+
genisoimage -output "$iso_path" -volid "$volume_name" -joliet -rock "$source_dir" >/dev/null 2>&1
|
|
66
|
+
else
|
|
67
|
+
echo "Missing ISO creation tool (need hdiutil, mkisofs, or genisoimage)" >&2
|
|
68
|
+
exit 1
|
|
69
|
+
fi
|
|
70
|
+
}
|