check env vars in dev builds

This commit is contained in:
swithek 2026-09-01 21:13:48 +02:00
parent 37da275e0a
commit e0754cb229
3 changed files with 124 additions and 5 deletions

View File

@ -111,7 +111,7 @@ Strong success criteria let you loop independently. Weak criteria ("make it work
- `datagen/` — demo-data generator; separate Go module `github.com/oxynote/oxynote/datagen`. Demo/testing only.
- `e2e/` — Playwright end-to-end suite (`@oxynote/e2e`, **pnpm**) plus the two docker-compose stacks it drives: the dev stack built from this repo, and the all-in-one image `docker/prod/` produces. Not shipped; it exercises the composed product through a real backend. Every file, script and make target belonging to one stack is named `dev` or `prod` — neither is the unnamed default. Details: [e2e/CLAUDE.md](e2e/CLAUDE.md).
- `scripts/` — helpers the root Makefile calls. `run-quietly.sh` runs a build step with its output held back, replaying the log only if the step fails.
- `docker/` — dev docker-compose stack, Caddyfile, `env/` (committed `*.example.env` templates; `make setup` copies them to the gitignored `*.local.env` files the compose stack reads, and `web.example.env` also to `web/.env` for the host dev server and electron builds), `demo/` (demo-data configs for mariadb/postgres).
- `docker/` — dev docker-compose stack, Caddyfile, `env/` (committed `*.example.env` templates; `make setup` copies them to the gitignored `*.local.env` files the compose stack reads, and `web.example.env` also to `web/.env` for the host dev server and electron builds; `sync-env.sh` reconciles an existing `*.local.env` with its template, since `make setup` only copies when the local file is missing), `demo/` (demo-data configs for mariadb/postgres).
- `docker/prod/` — the production all-in-one image: alpine-based Dockerfile (built via `make prod-build`; core comes from goreleaser), the image's Caddyfile (sibling of `docker/Caddyfile`), the TS launcher (`@oxynote/launcher`, **pnpm**) that validates the flat public `OXYNOTE_*` env, generates internal secrets, and supervises caddy + web + core + auth-realtime, plus the example compose deployment and the local override `make prod-run` layers on it. Details: [docker/prod/CLAUDE.md](docker/prod/CLAUDE.md).
**One `.gitignore`, and it lives at the repository root.** Components do not carry their own — a rule for a nested directory is written with its path (`e2e/test-results/`, `web/coverage/`), so every exclusion in the repository is readable in one file.
@ -127,6 +127,8 @@ make run # build images + run the dev stack in the foreground (ctrl-c stop
make start # build images + run the dev stack in the background
make dev # backend containers + web dev server on the host (hot reload, :3000)
make stop # stop the dev stack
make check-env # report variables that drifted between *.example.env and *.local.env
make sync-env # rewrite *.local.env from the templates, keeping existing values
make lint # fix lint/format/type issues in web, auth-realtime, core, datagen, e2e, launcher
make check-lint # the same gates, verification only

View File

@ -28,18 +28,18 @@ QUIET := scripts/run-quietly.sh
# backend containers + web dev server on the host with hot reload. Ctrl-c
# stops the web dev server; `make stop` stops the containers.
.PHONY: dev
dev: build-go sync-changedetection-key
dev: check-env build-go sync-changedetection-key
$(COMPOSE) up --build -d
cd web && NUXT_PUBLIC_APP_BASE_URL=http://localhost:3000 pnpm run start:dev:web
# foreground: streams all logs, ctrl-c stops the stack
.PHONY: run
run: build-go sync-changedetection-key
run: check-env build-go sync-changedetection-key
$(COMPOSE) --profile web up --build
# background
.PHONY: start
start: build-go sync-changedetection-key
start: check-env build-go sync-changedetection-key
$(COMPOSE) --profile web up --build -d
.PHONY: stop
@ -189,4 +189,16 @@ prod-stop:
.PHONY: sync-changedetection-key
sync-changedetection-key:
$(COMPOSE) up -d changedetection
./docker/sync-changedetection-key.sh
./docker/sync-changedetection-key.sh
# a variable renamed in a template never reaches an env file that already
# exists, so the stack starts against a name nobody reads. check-env fails
# before that happens; sync-env rewrites the local files from the
# templates, keeping the value of every variable they still list.
.PHONY: check-env
check-env:
./docker/sync-env.sh
.PHONY: sync-env
sync-env:
./docker/sync-env.sh --fix

105
docker/sync-env.sh Executable file
View File

@ -0,0 +1,105 @@
#!/bin/sh
set -eu
# `make setup` copies each *.example.env to its *.local.env only when the
# local file is missing, so a variable that is renamed, added or removed in
# a template never reaches an env file that already exists. Core then boots
# against a name nobody reads: at best a dependency silently disables
# itself, at worst it refuses to start. This script compares the variable
# names in every template against its local file — reporting the difference
# by default, rewriting the local file to match the template with --fix.
#
# --fix keeps the local value of every variable the template still lists,
# so secrets and local tweaks survive; variables the template has dropped
# are removed, which is the point. Run from the repository root.
fix=false
if [ "${1:-}" = "--fix" ]; then
fix=true
elif [ $# -gt 0 ]; then
echo "usage: sync-env.sh [--fix]" >&2
exit 2
fi
keys() {
grep -oE '^[A-Za-z_][A-Za-z0-9_]*=' "$1" | tr -d '='
}
example_keys=$(mktemp)
local_keys=$(mktemp)
trap 'rm -f "$example_keys" "$local_keys"' EXIT
drifted=false
for name in core auth-realtime web; do
example="docker/env/$name.example.env"
local_file="docker/env/$name.local.env"
if [ ! -f "$local_file" ]; then
echo "sync-env: $local_file not found — run 'make setup' first" >&2
exit 1
fi
keys "$example" | sort > "$example_keys"
keys "$local_file" | sort > "$local_keys"
missing=$(comm -23 "$example_keys" "$local_keys")
unknown=$(comm -13 "$example_keys" "$local_keys")
if [ -z "$missing" ] && [ -z "$unknown" ]; then
continue
fi
drifted=true
if [ "$fix" = false ]; then
echo "$local_file has drifted from $example:"
echo "$missing" | sed '/^$/d; s/^/ missing: /'
echo "$unknown" | sed '/^$/d; s/^/ unknown: /'
continue
fi
# rebuild the local file from the template so it picks up new
# variables, renames and comments, substituting the local value
# wherever the template still lists the variable.
awk '
NR == FNR {
if ($0 ~ /^[A-Za-z_][A-Za-z0-9_]*=/) {
eq = index($0, "=")
value[substr($0, 1, eq - 1)] = substr($0, eq + 1)
}
next
}
{
if ($0 ~ /^[A-Za-z_][A-Za-z0-9_]*=/) {
eq = index($0, "=")
key = substr($0, 1, eq - 1)
if (key in value) {
print key "=" value[key]
next
}
}
print
}
' "$local_file" "$example" > "$local_file.new"
mv "$local_file.new" "$local_file"
echo "sync-env: rewrote $local_file from $example"
echo "$missing" | sed '/^$/d; s/^/ added: /'
echo "$unknown" | sed '/^$/d; s/^/ dropped: /'
done
if [ "$drifted" = false ]; then
echo "sync-env: env files are in sync"
elif [ "$fix" = false ]; then
echo "run 'make sync-env' to bring them in sync"
exit 1
fi