Load .env.<APP_ENV>, validate config at boot, keep secrets out of the image

`main.go` only ever loaded `.env`; the `APP_ENV` switch described in
`.env.local` / `.env.production` did not exist, and a missing variable
surfaced one restart at a time as a log.Fatalf inside db.Connect.

config.Load now picks `.env.<APP_ENV>` (default local) then `.env`, with
real environment winning, reads every setting into one typed Config and
reports everything missing in one message. Production insists on a POS
signing secret; local warns when DB_HOST is not a local address. db,
redis and the image store take the Config instead of reading env
themselves.

Also:
- livehub read MQTT_USERNAME while everything else uses MQTT_USER, so the
  console stream connected to the broker unauthenticated. Both accepted.
- .dockerignore: `COPY . .` was baking .env.production into the image.
  Dockerfile sets APP_ENV=production.
- Drop utils/config.go (dead viper loader) and create_table.go (unused,
  hardcoded production DSN); go mod tidy removes viper.
- .env.example lists every variable the code reads; docs/ENVIRONMENT.md.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-15 17:04:33 +05:30
parent be47435547
commit 4474479735
19 changed files with 908 additions and 646 deletions

View File

@@ -7,13 +7,10 @@
# Keep every host here pointing at localhost. The whole point of the split is
# that running the server locally cannot reach live data by accident.
#
# `.env`, `.env.local` and `.env.production` are all gitignored (`.env.example`
# is the one exception) — and for a reason: this repository's history already
# contains a committed `.env` from before 2026-08-03, so those credentials are
# in the history and should be rotated. Do not add another.
#
# `loadEnv()` in main.go reads `.env.$APP_ENV` from the working directory, so
# `go run .` from this folder picks this file up with no flags.
# `config.Load()` reads `.env.$APP_ENV` and then `.env` from the working
# directory, so `go run .` from this folder picks this file up with no flags.
# A real environment variable always wins over either file. The full list of
# settings is in `.env.example`.
# ── Where it listens ────────────────────────────────────────────────────────
# Production serves on 1009 (see .env.production). Change this locally to run
@@ -64,5 +61,8 @@ REDIS_USER=
REDIS_DB=0
# ── Auth ────────────────────────────────────────────────────────────────────
# Signs POS terminal sessions (16+ characters). A throwaway value so a till can
# sign in against the local stack; production sets its own in the platform.
POS_TOKEN_SECRET=local-dev-signing-secret-not-real
JWT_SECRET_KEY=
USER_CONTEXT_KEY=