diff --git a/pkg/config/main.go b/pkg/config/main.go new file mode 100644 index 0000000..e408555 --- /dev/null +++ b/pkg/config/main.go @@ -0,0 +1,133 @@ +package main + +import ( + "fmt" + "hash/fnv" + "os" + "os/signal" + "strconv" + "strings" + "syscall" + + "git.solsynth.dev/goatworks/turbine/pkg/shared/registrar" + + "github.com/gofiber/fiber/v3" + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" + "github.com/spf13/viper" +) + +func hash(s string) uint32 { + h := fnv.New32a() + h.Write([]byte(s)) + return h.Sum32() +} + +func init() { + log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) +} + +func main() { + log.Info().Msg("Starting Config Service...") + viper.SetConfigName("settings") + viper.AddConfigPath(".") + viper.SetConfigType("toml") + + if err := viper.ReadInConfig(); err != nil { + log.Fatal().Err(err).Msg("Failed to read config file") + } + log.Info().Msg("Configuration loaded.") + + // --- Service Registration --- + etcdEndpoints := viper.GetStringSlice("etcd.endpoints") + if len(etcdEndpoints) == 0 { + log.Fatal().Msg("etcd.endpoints not configured") + } + + if viper.GetBool("etcd.insecure") { + for i, ep := range etcdEndpoints { + if !strings.HasPrefix(ep, "http://") && !strings.HasPrefix(ep, "https://") { + etcdEndpoints[i] = "http://" + ep + } + } + } + + serviceReg, err := registrar.NewServiceRegistrar(etcdEndpoints) + if err != nil { + log.Fatal().Err(err).Msg("Failed to create service registrar") + } + + listenAddr := viper.GetString("listen") + host := viper.GetString("host") + if host == "" { + log.Fatal().Msg("host not configured") + } + portStr := strings.TrimPrefix(listenAddr, ":") + port, err := strconv.Atoi(portStr) + if err != nil { + log.Fatal().Err(err).Msg("Invalid listen address") + } + + serviceName := "config" + instanceID := fmt.Sprint(hash(fmt.Sprintf("%s-%s-%d", serviceName, host, port)))[:8] + + err = serviceReg.Register(serviceName, "http", instanceID, host, port, 30) + if err != nil { + log.Fatal().Err(err).Msg("Failed to register service") + } + log.Info().Str("service", serviceName).Str("instanceID", instanceID).Msg("Service registered successfully") + + // --- Web Server --- + app := fiber.New(fiber.Config{ + ServerHeader: "Turbine Config", + }) + + // This is the main endpoint that serves the configuration as JSON. + app.Get("/", func(c fiber.Ctx) error { + log.Info().Msg("Serving shared configuration as JSON") + + // Use a new Viper instance to read the shared config + v := viper.New() + v.SetConfigName("shared_config") + v.AddConfigPath(".") // Look in the current directory (pkg/config) + v.SetConfigType("toml") + + if err := v.ReadInConfig(); err != nil { + log.Error().Err(err).Msg("Failed to read shared_config.toml") + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "error": "could not load configuration", + }) + } + + return c.JSON(v.AllSettings()) + }) + + // Health check endpoint + app.Get("/health", func(c fiber.Ctx) error { + return c.SendString("Config service is running") + }) + + // --- Graceful Shutdown --- + go func() { + err := app.Listen(listenAddr, fiber.ListenConfig{DisableStartupMessage: true}) + if err != nil { + log.Fatal().Err(err).Msg("Failed to start server") + } + }() + log.Info().Msg("Server is listening on " + listenAddr) + + stop := make(chan os.Signal, 1) + signal.Notify(stop, syscall.SIGINT, syscall.SIGTERM) + <-stop + + log.Info().Msg("Shutting down service...") + if err := serviceReg.Deregister(); err != nil { + log.Error().Err(err).Msg("Failed to deregister service") + } else { + log.Info().Msg("Service deregistered successfully") + } + + if err := app.Shutdown(); err != nil { + log.Error().Err(err).Msg("Error during server shutdown") + } +} diff --git a/pkg/config/settings.toml b/pkg/config/settings.toml new file mode 100644 index 0000000..72487c3 --- /dev/null +++ b/pkg/config/settings.toml @@ -0,0 +1,12 @@ +# The address the config service will listen on +listen = ":8081" + +# The host address to register with etcd. +# This should be the address that other services can use to reach this service. +# For local testing, 127.0.0.1 is fine. In production, this should be a reachable IP. +host = "127.0.0.1" + +# ETCD configuration for service registration +[etcd] +endpoints = ["etcd.orb.local:2379"] +insecure = true diff --git a/pkg/config/shared_config.toml b/pkg/config/shared_config.toml new file mode 100644 index 0000000..5778c00 --- /dev/null +++ b/pkg/config/shared_config.toml @@ -0,0 +1,5 @@ +[database] +connection_string = "postgres://user:password@db-host:5432/mydatabase?sslmode=require" + +[redis] +address = "redis-host:6379"