From 6893ca6ba9fa0607905f9a645b13c350d082c2e5 Mon Sep 17 00:00:00 2001 From: yiekheng Date: Sun, 10 May 2026 22:13:30 +0800 Subject: [PATCH] fix(web): lazy-parse env so docker build doesn't crash on missing DATABASE_URL `scripts/publish.sh` failed during the web image build at "Collecting page data" with: ZodError: DATABASE_URL: Required next build walks every route module including api/events/route.ts, which imports env from @/env. The previous shape ran envSchema.parse(process.env) at module top level, so the parse fired inside the build container where DATABASE_URL deliberately isn't set. Wrap the parse in a Proxy that resolves on first property access. The build's page-data pass doesn't read any env property, so the parse never runs at build time. Runtime callers (db.ts, media.ts, api/events/route.ts) hit the proxy on first use and get the same strict Zod validation as before. Co-Authored-By: Claude Opus 4.7 (1M context) --- apps/web/src/env.ts | 23 ++++++++++++++++++++- envs/ENV | 50 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 envs/ENV diff --git a/apps/web/src/env.ts b/apps/web/src/env.ts index 1ea7cec..b9fb95b 100644 --- a/apps/web/src/env.ts +++ b/apps/web/src/env.ts @@ -8,4 +8,25 @@ const envSchema = z.object({ }); export type Env = z.infer; -export const env = envSchema.parse(process.env); + +// Lazy parse via Proxy. Next.js's `next build` does a +// "Collecting page data" pass that imports every route module — +// including api/events/route.ts which depends on this env. With a +// top-level `envSchema.parse(process.env)` the parse ran during +// the build container, where DATABASE_URL isn't (and shouldn't be) +// set, and Zod aborted the build with: +// ZodError: DATABASE_URL: Required +// Deferring the parse until first property access lets the build +// finish (no consumer accesses env during page-data collection) +// while still failing loudly at runtime if the var is missing. +let cached: Env | null = null; +function read(): Env { + if (cached) return cached; + cached = envSchema.parse(process.env); + return cached; +} +export const env: Env = new Proxy({} as Env, { + get(_t, prop) { + return read()[prop as keyof Env]; + }, +}) as Env; diff --git a/envs/ENV b/envs/ENV new file mode 100644 index 0000000..d20677e --- /dev/null +++ b/envs/ENV @@ -0,0 +1,50 @@ +# === Postgres === +DATABASE_URL=postgres://waBot:cJe3SGjHHAitNBE4@192.168.0.210:5432/wabot + +# === App data paths (inside containers) === +DATA_DIR=/data +SESSIONS_DIR=/data/sessions +MEDIA_DIR=/data/media + +# === Bot service === +BOT_HEALTH_PORT=8081 +BOT_LOG_LEVEL=info + +# Reminder fan-out tuning. Defaults aim for an established WhatsApp +# account (~30-60 msg/min safe band). Bump cautiously. +# BOT_FIRE_CONCURRENCY pg-boss workers; max accounts firing in parallel. +# BOT_GROUP_CONCURRENCY per-account parallel group sends; parts within a +# group stay serial. +# BOT_MAX_SEND_PER_MINUTE per-account token-bucket rate. +BOT_FIRE_CONCURRENCY=8 +BOT_GROUP_CONCURRENCY=3 +BOT_MAX_SEND_PER_MINUTE=40 + +# === Seed (used by scripts/db.sh seed) === +# The bootstrap operator's username. After seed, set their password +# via: echo 'change-me-now' | scripts/set-password.sh admin +SEED_OPERATOR_USERNAME=admin +SEED_OPERATOR_NAME=Operator + +# === Web / Auth === +# Port the Next.js container exposes on the host. Production deployment +# (wabot.04080616.xyz) uses 8100; dev/staging (test.04080616.xyz) uses 9000. +WEB_PORT=8100 + +# 32-byte secret used to derive the AES-256-GCM key for session cookies. +# DO NOT leave blank — the web container will refuse to issue cookies. +# Generate via: scripts/gen_auth_secret.sh --write +AUTH_SECRET=86f656580a58f03b6ccb43d257e0e801ecd5356e042e8886b3c7c569e29ff13c + +# Bumping this invalidates every outstanding session cookie globally on +# the next request. Treat it as a kill switch (e.g. after a key leak) +# rather than a routine value. +OPERATOR_TOKEN_VERSION=1 + +# === Docker Registry (used by scripts/publish.sh) === +# Tag pushed alongside latest. Override with the CLI arg or +# DOCKER_IMAGE_TAG=v1.2.3 scripts/publish.sh. +DOCKER_IMAGE_TAG=latest +# Buildx target platforms. linux/amd64 is the prod host arch; add +# linux/arm64 if you cross-build for an Apple-silicon runner. +CM_IMAGE_PLATFORMS=linux/amd64