Nik Afiq 8f7024edfa
All checks were successful
CI / changes (push) Successful in 19s
CI / test (push) Successful in 24s
CI / build-ai-gateway (push) Successful in 1m6s
CI / build-ha-gateway (push) Successful in 1m3s
CI / build-discord-bot (push) Successful in 1m4s
CI / build-alexa-bridge (push) Successful in 1m15s
CI / build-tts-gateway (push) Successful in 1m5s
CI / build-tts-sidecar (push) Has been skipped
CI / build-tts-model (push) Has been skipped
feat(climate): add SetTemperature method to ClimateService
- Implemented SetTemperature in ClimateService for setting an absolute target temperature.
- Updated ClimateServiceClient and ClimateServiceServer interfaces to include SetTemperature.
- Added corresponding handler and tests for SetTemperature in ClimateGRPC.
- Modified ClimateApp to handle SetTemperature requests without clamping.
- Updated climate.proto to define SetTemperatureRequest message.
- Adjusted Dockerfiles to include alexa-bridge dependencies.
2026-07-25 12:08:24 +09:00

87 lines
2.9 KiB
Go

package config
import (
"errors"
"fmt"
"os"
"path/filepath"
"time"
)
// Config holds runtime configuration for the alexa-bridge process.
type Config struct {
HTTPPort string // HTTP_PORT, default "8080"
AlexaSkillID string // ALEXA_SKILL_ID — verified against the request envelope's application ID (required)
HAGatewayAddr string // HA_GATEWAY_ADDR, default "ha-gateway.home-services.svc.cluster.local:50051"
HAGatewayServerName string // HA_GATEWAY_SERVER_NAME, default "ha-gateway.home-services.svc.cluster.local"
TLSDir string // TLS_DIR, default "/tls" — unlike ai-gateway/discord-bot, mTLS is on by default here since alexa-bridge is internet-facing; set empty to disable for local dev
EntityRefreshInterval time.Duration // ENTITY_REFRESH_INTERVAL, default 5m
OTELEndpoint string // OTEL_ENDPOINT, empty disables telemetry
LogLevel string // LOG_LEVEL, default "info"
LogFormat string // LOG_FORMAT, default "json"
}
// Load reads configuration from environment variables and applies defaults.
func Load() (*Config, error) {
skillID := os.Getenv("ALEXA_SKILL_ID")
if skillID == "" {
return nil, errors.New("ALEXA_SKILL_ID is required but not set")
}
refreshInterval, err := parseDurationEnv("ENTITY_REFRESH_INTERVAL", 5*time.Minute)
if err != nil {
return nil, err
}
cfg := &Config{
HTTPPort: getenvDefault("HTTP_PORT", "8080"),
AlexaSkillID: skillID,
HAGatewayAddr: getenvDefault("HA_GATEWAY_ADDR", "ha-gateway.home-services.svc.cluster.local:50051"),
HAGatewayServerName: getenvDefault("HA_GATEWAY_SERVER_NAME", "ha-gateway.home-services.svc.cluster.local"),
TLSDir: getenvDefault("TLS_DIR", "/tls"),
EntityRefreshInterval: refreshInterval,
OTELEndpoint: os.Getenv("OTEL_ENDPOINT"),
LogLevel: getenvDefault("LOG_LEVEL", "info"),
LogFormat: getenvDefault("LOG_FORMAT", "json"),
}
if cfg.TLSDir != "" {
if err := validateTLSDir(cfg.TLSDir); err != nil {
return nil, err
}
}
return cfg, nil
}
func getenvDefault(key, fallback string) string {
if v := os.Getenv(key); v != "" {
return v
}
return fallback
}
func parseDurationEnv(key string, fallback time.Duration) (time.Duration, error) {
if v := os.Getenv(key); v != "" {
d, err := time.ParseDuration(v)
if err != nil {
return 0, fmt.Errorf("parse %s: %w", key, err)
}
return d, nil
}
return fallback, nil
}
func validateTLSDir(dir string) error {
required := []string{"tls.crt", "tls.key", "ca.crt"}
for _, name := range required {
path := filepath.Join(dir, name)
info, err := os.Stat(path)
if err != nil {
return fmt.Errorf("tls dir validation failed for %s: %w", path, err)
}
if info.IsDir() {
return fmt.Errorf("tls dir validation failed for %s: %w", path, errors.New("expected file"))
}
}
return nil
}