`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>
104 lines
4.4 KiB
Markdown
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.
|