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

@@ -1,51 +0,0 @@
package utils
import (
"fmt"
"os"
"github.com/spf13/viper"
)
func DevConfig() (Port, DBname, Password, Username, Host, key, secret string) {
viper.SetConfigName("config")
viper.SetConfigType("yaml")
viper.AddConfigPath(".")
err := viper.ReadInConfig()
if err != nil {
fmt.Println("fatal error config file: default \n", err)
os.Exit(1)
}
dbname := viper.GetString("DEV.DATABASE_NAME")
dbpassword := viper.GetString("DEV.DATABASE_PASSWORD")
dbusername := viper.GetString("DEV.DATABASE_USERNAME")
dbport := viper.GetString("DEV.DATABASE_PORT")
dbhost := viper.GetString("DEV.DATABASE_SERVER_HOST")
contextkey := viper.GetString("DEV.USER_CONTEXT_KEY")
jwtkey := viper.GetString("DEV.JWT_SECRET_KEY")
return dbport, dbname, dbpassword, dbusername, dbhost, contextkey, jwtkey
}
func LiveConfig() (Port, DBname, Password, Username, Host, key, secret string) {
viper.SetConfigName("config")
viper.SetConfigType("yaml")
viper.AddConfigPath(".")
err := viper.ReadInConfig()
if err != nil {
fmt.Println("fatal error config file: default \n", err)
os.Exit(1)
}
dbname := viper.GetString("APP.DATABASE_NAME")
dbpassword := viper.GetString("APP.DATABASE_PASSWORD")
dbusername := viper.GetString("APP.DATABASE_USERNAME")
dbport := viper.GetString("APP.DATABASE_PORT")
dbhost := viper.GetString("APP.DATABASE_SERVER_HOST")
contextkey := viper.GetString("APP.USER_CONTEXT_KEY")
jwtkey := viper.GetString("APP.JWT_SECRET_KEY")
return dbport, dbname, dbpassword, dbusername, dbhost, contextkey, jwtkey
}