// Package config — конфигурация монолита iot-service из env-переменных. // // Принцип: несекретные значения имеют дефолты (продублированы ENV в Dockerfile), // секреты — обязательны и задаются в deck-UI при создании контейнера. // Env-переменные после деплоя не меняются — поэтому дефолты вшиты в образ. package config import ( "fmt" "log/slog" "os" "strconv" "strings" ) // Config — вся конфигурация iot-service. type Config struct { // HTTP API APIPort string // PostgreSQL (managed, общий для устройств и телеметрии) IOTPGDSN string // shared-SQS SQSEndpoint string SQSAccessKey string SQSSecretKey string SQSQueueName string SQSRegion string // SQS-потребитель SQSLongPollSeconds int SQSVisibilityTimeout int // MQTT (EMQX) MQTTBrokerURL string MQTTUsername string MQTTPassword string MQTTClientID string // Админ-статистика AdminStatsToken string // Безопасность AuthTestMode bool // Логирование LogLevel slog.Level } // getEnv возвращает значение env или дефолт. func getEnv(key, def string) string { if v := os.Getenv(key); v != "" { return v } return def } // getEnvInt возвращает целое значение env или дефолт (неположительное — дефолт). func getEnvInt(key string, def int) int { if v := os.Getenv(key); v != "" { if n, err := strconv.Atoi(v); err == nil && n > 0 { return n } } return def } // getEnvBool разбирает булево значение env. func getEnvBool(key string, def bool) bool { v := os.Getenv(key) if v == "" { return def } switch strings.ToLower(v) { case "1", "true", "yes", "on": return true case "0", "false", "no", "off": return false default: return def } } // parseLogLevel разбирает уровень логирования. func parseLogLevel(v string, def slog.Level) slog.Level { switch strings.ToLower(v) { case "debug": return slog.LevelDebug case "warn", "warning": return slog.LevelWarn case "error": return slog.LevelError default: return def } } // Load читает конфигурацию из env, подставляет дефолты // и проверяет обязательные (секретные) переменные. func Load() (*Config, error) { cfg := &Config{ APIPort: getEnv("API_PORT", DefaultAPIPort), IOTPGDSN: os.Getenv("IOT_PG_DSN"), SQSEndpoint: getEnv("SQS_ENDPOINT", DefaultSQSEndpoint), SQSAccessKey: os.Getenv("SQS_ACCESS_KEY"), SQSSecretKey: os.Getenv("SQS_SECRET_KEY"), SQSQueueName: getEnv("SQS_QUEUE_NAME", DefaultSQSQueueName), SQSRegion: getEnv("SQS_REGION", DefaultSQSRegion), SQSLongPollSeconds: getEnvInt("SQS_LONG_POLL_SECONDS", 20), SQSVisibilityTimeout: getEnvInt("SQS_VISIBILITY_TIMEOUT", 30), MQTTUsername: os.Getenv("MQTT_USERNAME"), MQTTPassword: os.Getenv("MQTT_PASSWORD"), MQTTClientID: getEnv("MQTT_CLIENT_ID", DefaultMQTTClientID), AdminStatsToken: os.Getenv("ADMIN_STATS_TOKEN"), AuthTestMode: getEnvBool("AUTH_TEST_MODE", false), LogLevel: parseLogLevel(os.Getenv("LOG_LEVEL"), slog.LevelInfo), } // MQTT broker URL: либо полный MQTT_BROKER_URL, либо из HOST/PORT/WS_PATH cfg.MQTTBrokerURL = os.Getenv("MQTT_BROKER_URL") if cfg.MQTTBrokerURL == "" { host := getEnv("MQTT_HOST", DefaultMQTTHost) port := getEnv("MQTT_PORT", DefaultMQTTPort) path := getEnv("MQTT_WS_PATH", DefaultMQTTWSPath) cfg.MQTTBrokerURL = "ws://" + host + ":" + port + path } var missing []string if cfg.IOTPGDSN == "" { missing = append(missing, "IOT_PG_DSN") } if cfg.SQSAccessKey == "" { missing = append(missing, "SQS_ACCESS_KEY") } if cfg.SQSSecretKey == "" { missing = append(missing, "SQS_SECRET_KEY") } if cfg.MQTTUsername == "" { missing = append(missing, "MQTT_USERNAME") } if cfg.MQTTPassword == "" { missing = append(missing, "MQTT_PASSWORD") } if len(missing) > 0 { return nil, fmt.Errorf("required env vars not set: %s", strings.Join(missing, ", ")) } return cfg, nil }