feat: монолит iot-service (Фаза 1) — config/store/api/bridge/consumer без k8s, устройства в PG, новые Dockerfile/Makefile

This commit is contained in:
“Naeel”
2026-08-16 10:07:51 +04:00
parent f0c36a11ee
commit 9d08473d5f
27 changed files with 3716 additions and 0 deletions
+150
View File
@@ -0,0 +1,150 @@
// 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
}
+32
View File
@@ -0,0 +1,32 @@
package config
// Defaults — дефолтные значения env-переменных (не секреты).
// Продублированы в Dockerfile ENV.
const (
// DefaultAPIPort — единственный порт контейнера: API + /health + /metrics.
DefaultAPIPort = "9090"
// DefaultSQSEndpoint — внутренний адрес shared-sqs в том же кластере Nubes.
// Обходит внешний шлюз платформы (таймауты 31-33с, MSS 1448, ~150с).
DefaultSQSEndpoint = "http://containerk8s.f1ffb134-7d16-45bd-8bef-69f6ec8ab33c.svc.cluster.local:4100"
// DefaultSQSQueueName — очередь телеметрии IoT.
DefaultSQSQueueName = "iot-telemetry"
// DefaultSQSRegion — регион для self-hosted SQS (не важен, оставлен для SDK).
DefaultSQSRegion = "us-east-1"
// DefaultMQTTHost — внутреннее имя EMQX-контейнера на платформе.
// Реальный UUID namespace EMQX задаётся в deck-UI (MQTT_HOST) при создании.
DefaultMQTTHost = "emqx"
// DefaultMQTTPort — WebSocket-листенер EMQX (wss внутри платформы).
DefaultMQTTPort = "8083"
// DefaultMQTTWSPath — путь MQTT-over-WebSocket на EMQX.
DefaultMQTTWSPath = "/mqtt"
// DefaultMQTTClientID — clientID bridge-подключения к EMQX.
// Используется и в ACL-логике MQTT-auth.
DefaultMQTTClientID = "iot-bridge"
)