130 lines
4.1 KiB
Go
130 lines
4.1 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"net"
|
|
"os"
|
|
"os/signal"
|
|
"syscall"
|
|
|
|
"github.com/joho/godotenv"
|
|
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
|
|
"google.golang.org/grpc"
|
|
"google.golang.org/grpc/health"
|
|
grpc_health_v1 "google.golang.org/grpc/health/grpc_health_v1"
|
|
"google.golang.org/grpc/reflection"
|
|
|
|
hav1 "gitea.nik4nao.com/nik/home-services/gen/ha/v1"
|
|
grpcadapter "gitea.nik4nao.com/nik/home-services/ha-gateway/internal/adapters/primary/grpc"
|
|
"gitea.nik4nao.com/nik/home-services/ha-gateway/internal/adapters/secondary/ha"
|
|
"gitea.nik4nao.com/nik/home-services/ha-gateway/internal/app"
|
|
"gitea.nik4nao.com/nik/home-services/ha-gateway/internal/config"
|
|
"gitea.nik4nao.com/nik/home-services/ha-gateway/internal/logger"
|
|
"gitea.nik4nao.com/nik/home-services/ha-gateway/internal/telemetry"
|
|
)
|
|
|
|
// MEMO: auth is not implemented.
|
|
// Add one of the following before exposing this service to any untrusted network:
|
|
// Option A — shared API key per client: unary + stream interceptors read
|
|
// "authorization" from gRPC metadata and compare to a secret
|
|
// from config. Good for small number of known clients.
|
|
// Option B — mTLS (recommended): tls.Config with ClientAuth: RequireAndVerifyClientCert,
|
|
// cert pool from the internal CA. Each client gets a cert from
|
|
// cert-manager. No runtime auth dependency, identity in the cert CN/SAN.
|
|
|
|
// version is set at build time via -ldflags "-X main.version=<tag>".
|
|
var version = "dev"
|
|
|
|
func main() {
|
|
_ = godotenv.Load()
|
|
|
|
cfg, err := config.Load()
|
|
if err != nil {
|
|
os.Stderr.WriteString("config error: " + err.Error() + "\n")
|
|
os.Exit(1)
|
|
}
|
|
|
|
log := logger.New(cfg.LogFormat, cfg.LogLevel)
|
|
log.Info("starting ha-gateway",
|
|
"version", version,
|
|
"grpc_port", cfg.GRPCPort,
|
|
"ha_base_url", cfg.HABaseURL,
|
|
"ha_token", redactToken(cfg.HAToken),
|
|
"otel_endpoint", cfg.OTELEndpoint,
|
|
"log_level", cfg.LogLevel,
|
|
"log_format", cfg.LogFormat,
|
|
)
|
|
|
|
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGTERM, syscall.SIGINT)
|
|
defer stop()
|
|
ctx = logger.WithLogger(ctx, log)
|
|
|
|
shutdown, err := telemetry.Setup(ctx, cfg, version)
|
|
if err != nil {
|
|
log.Error("telemetry setup failed", "err", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
haClient := ha.NewClient(cfg, log)
|
|
|
|
entityApp := app.NewEntityApp(haClient)
|
|
lightApp := app.NewLightApp(haClient)
|
|
switchApp := app.NewSwitchApp(haClient)
|
|
|
|
if err := lightApp.Refresh(ctx); err != nil {
|
|
log.Warn("initial light discovery failed, will retry on first request", "err", err)
|
|
}
|
|
if err := switchApp.Refresh(ctx); err != nil {
|
|
log.Warn("initial switch discovery failed, will retry on first request", "err", err)
|
|
}
|
|
|
|
srv := grpc.NewServer(
|
|
grpc.StatsHandler(otelgrpc.NewServerHandler()),
|
|
grpc.ChainUnaryInterceptor(grpcadapter.LoggingUnaryInterceptor(log)),
|
|
grpc.ChainStreamInterceptor(grpcadapter.LoggingStreamInterceptor(log)),
|
|
)
|
|
healthSrv := health.NewServer()
|
|
healthSrv.SetServingStatus("", grpc_health_v1.HealthCheckResponse_SERVING)
|
|
|
|
hav1.RegisterEntityServiceServer(srv, grpcadapter.NewEntityGRPC(entityApp))
|
|
hav1.RegisterLightServiceServer(srv, grpcadapter.NewLightGRPC(lightApp))
|
|
hav1.RegisterSwitchServiceServer(srv, grpcadapter.NewSwitchGRPC(switchApp))
|
|
hav1.RegisterEventServiceServer(srv, &grpcadapter.EventGRPC{})
|
|
grpc_health_v1.RegisterHealthServer(srv, healthSrv)
|
|
reflection.Register(srv)
|
|
|
|
lis, err := net.Listen("tcp", ":"+cfg.GRPCPort)
|
|
if err != nil {
|
|
log.Error("listen failed", "err", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
go func() {
|
|
log.Info("ha-gateway listening", "addr", lis.Addr().String())
|
|
if err := srv.Serve(lis); err != nil {
|
|
log.Error("serve failed", "err", err)
|
|
}
|
|
}()
|
|
|
|
<-ctx.Done()
|
|
log.Info("shutdown signal received, draining")
|
|
|
|
healthSrv.SetServingStatus("", grpc_health_v1.HealthCheckResponse_NOT_SERVING)
|
|
srv.GracefulStop()
|
|
log.Info("shutdown complete")
|
|
|
|
if err := shutdown(context.Background()); err != nil {
|
|
log.Error("telemetry shutdown error", "err", err)
|
|
}
|
|
}
|
|
|
|
func redactToken(token string) string {
|
|
if token == "" {
|
|
return "[not set]"
|
|
}
|
|
if len(token) <= 8 {
|
|
return token + "..."
|
|
}
|
|
return token[:8] + "..."
|
|
}
|