Files
backend_fiesta/docs/ENVIRONMENT.md
Suriyakumarvijayanayagam 4474479735 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>
2026-09-15 17:04:33 +05:30

104 lines
4.4 KiB
Markdown

# Environment & configuration
Everything the API reads from its environment goes through `config/config.go`.
It loads the right `.env` file, reads every setting into one `Config`, and
refuses to start — listing *everything* that is wrong in one message — before
a single connection is attempted.
## Which file loads
`APP_ENV` names the environment. It defaults to `local`.
| Command | Files loaded, in order |
|----------------------------------|---------------------------------|
| `go run .` | `.env.local`, then `.env` |
| `APP_ENV=production go run .` | `.env.production`, then `.env` |
| `APP_ENV=staging go run .` | `.env.staging`, then `.env` |
Precedence, highest first:
```
real environment > .env.<APP_ENV> > .env
```
A variable that is already set is never overwritten by a file, and no file has
to exist. `APP_ENV` itself is read from the real environment before any file
is opened — a file cannot decide which file gets loaded.
| File | Role |
|-------------------|------------------------------------------------------------|
| `.env.example` | The complete list of settings, with comments. Start here. |
| `.env.local` | The docker-compose stack. Every host is `localhost`. |
| `.env.production` | The live hosts. Loaded only when asked for. |
| `.env` | Shared base: fallbacks for whatever the file above left out. Keep it local. |
## Running locally
```sh
docker compose -f docker-compose.local.yml up -d # postgres :5433, pgvector :5434, redis :6379
go run . # APP_ENV unset → .env.local
```
An empty database is not enough — `main.go` runs migrations that assume the
live schema. See `init/README.md` for loading a schema dump first.
Startup prints what it loaded and where it is pointed:
```
config: loaded .env.local
config: loaded .env
config: APP_ENV=local, listening on :1122, database nearle@localhost:5433/nearledb
```
If `DB_HOST` is not a local address under `APP_ENV=local`, it says so:
```
⚠️ APP_ENV=local but DB_HOST=66.116.x.x is not a local address — every write goes to that database for real
```
That is a warning, not a stop. There is no "local mode" that protects
production: `go run .` against the live host creates real tenants and real
logins, and runs schema migrations on boot.
## Running in production
The container gets **no `.env` file at all** — `.dockerignore` keeps every
`.env*` out of the image — and the `Dockerfile` sets `APP_ENV=production`.
Every value comes from the platform's environment settings (Dokploy today;
ConfigMaps/Secrets under Kubernetes).
Under `APP_ENV=production` startup additionally insists on:
- `POS_TOKEN_SECRET` (or `JWT_SECRET_KEY` as a fallback), at least 16 characters.
A variable added to `.env.production` and not to the platform is a variable
that is unset in production. Missing required ones stop the boot with the
full list; missing optional ones (`MQTT_URL`, `REDIS_HOST`, `USE_S3`,
`CATALOGUE_DB_HOST`) silently disable that subsystem — check the startup log
lines when something is "not working".
## What is required
| Always | Only when enabled |
|----------------------------------------------|---------------------------------------------------------|
| `DB_HOST` `DB_USER` `DB_PASSWORD` `DB_NAME` | `CATALOGUE_DB_HOST` set → `CATALOGUE_DB_USER/PASSWORD/NAME` |
| (production) `POS_TOKEN_SECRET` | `USE_S3=true` → `S3_ENDPOINT/BUCKET/ACCESS_KEY/SECRET_KEY/REGION` |
A half-configured subsystem is an error, not a warning: a warning reads as
"fine" in a log and turns into "why are there no images" a week later.
## The committed credentials
The three `.env` files, including `.env.production`, are currently tracked in
git (commit `be47435`), and an earlier `.env` was committed before
2026-08-03. Every credential in them has to be treated as public:
1. Rotate the database, catalogue, Spaces, MQTT and Redis credentials and the
POS signing secret, and update them in the platform.
2. Take the files back out of the index and restore the ignore rules:
```sh
git rm --cached .env .env.local .env.production
printf '.env\n.env.*\n!.env.example\n' >> .gitignore
```
The files stay on disk; they just stop being committed.