mirror of
https://github.com/hhftechnology/middleware-manager.git
synced 2026-04-28 03:29:42 +00:00
245 lines
7.2 KiB
Go
245 lines
7.2 KiB
Go
package main
|
|
|
|
import (
|
|
"flag"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
"os/signal"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/hhftechnology/middleware-manager/api"
|
|
"github.com/hhftechnology/middleware-manager/config"
|
|
"github.com/hhftechnology/middleware-manager/database"
|
|
"github.com/hhftechnology/middleware-manager/services"
|
|
)
|
|
|
|
// Configuration represents the application configuration
|
|
type Configuration struct {
|
|
PangolinAPIURL string
|
|
TraefikAPIURL string
|
|
TraefikConfDir string
|
|
DBPath string
|
|
Port string
|
|
UIPath string
|
|
ConfigDir string
|
|
CheckInterval time.Duration
|
|
GenerateInterval time.Duration
|
|
ServiceInterval time.Duration
|
|
Debug bool
|
|
AllowCORS bool
|
|
CORSOrigin string
|
|
ActiveDataSource string
|
|
TraefikStaticConfigPath string
|
|
}
|
|
|
|
// DiscoverTraefikAPI attempts to discover the Traefik API by trying common URLs
|
|
func DiscoverTraefikAPI() (string, error) {
|
|
client := &http.Client{
|
|
Timeout: 2 * time.Second,
|
|
}
|
|
|
|
urls := []string{
|
|
"http://host.docker.internal:8080",
|
|
"http://localhost:8080",
|
|
"http://127.0.0.1:8080",
|
|
"http://traefik:8080",
|
|
}
|
|
|
|
for _, url := range urls {
|
|
testURL := url + "/api/version"
|
|
resp, err := client.Get(testURL)
|
|
if err == nil && resp.StatusCode == http.StatusOK {
|
|
resp.Body.Close()
|
|
log.Printf("Discovered Traefik API at %s", url)
|
|
return url, nil
|
|
}
|
|
if resp != nil {
|
|
resp.Body.Close()
|
|
}
|
|
}
|
|
return "", nil
|
|
}
|
|
|
|
func main() {
|
|
log.Println("Starting Middleware Manager...")
|
|
|
|
var debug bool
|
|
flag.BoolVar(&debug, "debug", false, "Enable debug mode")
|
|
flag.Parse()
|
|
|
|
cfg := loadConfiguration(debug)
|
|
|
|
if os.Getenv("TRAEFIK_API_URL") == "" {
|
|
if discoveredURL, err := DiscoverTraefikAPI(); err == nil && discoveredURL != "" {
|
|
log.Printf("Auto-discovered Traefik API URL: %s", discoveredURL)
|
|
cfg.TraefikAPIURL = discoveredURL
|
|
}
|
|
}
|
|
|
|
db, err := database.InitDB(cfg.DBPath)
|
|
if err != nil {
|
|
log.Fatalf("Failed to initialize database: %v", err)
|
|
}
|
|
defer db.Close()
|
|
|
|
configDir := cfg.ConfigDir
|
|
if err := config.EnsureConfigDirectory(configDir); err != nil {
|
|
log.Printf("Warning: Failed to create config directory: %v", err)
|
|
}
|
|
|
|
if err := config.SaveTemplateFile(configDir); err != nil {
|
|
log.Printf("Warning: Failed to save default middleware templates: %v", err)
|
|
}
|
|
|
|
if err := config.LoadDefaultTemplates(db); err != nil {
|
|
log.Printf("Warning: Failed to load default middleware templates: %v", err)
|
|
}
|
|
|
|
if err := config.SaveTemplateServicesFile(configDir); err != nil {
|
|
log.Printf("Warning: Failed to save default service templates: %v", err)
|
|
}
|
|
|
|
if err := config.LoadDefaultServiceTemplates(db); err != nil {
|
|
log.Printf("Warning: Failed to load default service templates: %v", err)
|
|
}
|
|
|
|
// Run comprehensive database cleanup on startup
|
|
log.Println("Performing full database cleanup...")
|
|
cleanupOpts := database.DefaultCleanupOptions()
|
|
cleanupOpts.LogLevel = 2 // More verbose logging during startup
|
|
|
|
if err := db.PerformFullCleanup(cleanupOpts); err != nil {
|
|
log.Printf("Warning: Database cleanup encountered issues: %v", err)
|
|
} else {
|
|
log.Println("Database cleanup completed successfully")
|
|
}
|
|
|
|
configManager, err := services.NewConfigManager(filepath.Join(configDir, "config.json"))
|
|
if err != nil {
|
|
log.Fatalf("Failed to initialize config manager: %v", err)
|
|
}
|
|
|
|
configManager.EnsureDefaultDataSources(cfg.PangolinAPIURL, cfg.TraefikAPIURL)
|
|
|
|
stopChan := make(chan struct{})
|
|
|
|
resourceWatcher, err := services.NewResourceWatcher(db, configManager)
|
|
if err != nil {
|
|
log.Fatalf("Failed to create resource watcher: %v", err)
|
|
}
|
|
go resourceWatcher.Start(cfg.CheckInterval)
|
|
|
|
configGenerator := services.NewConfigGenerator(db, cfg.TraefikConfDir, configManager)
|
|
if strings.ToLower(os.Getenv("ENABLE_FILE_CONFIG")) == "true" {
|
|
go configGenerator.Start(cfg.GenerateInterval)
|
|
} else {
|
|
log.Println("File config generator disabled (ENABLE_FILE_CONFIG not true); relying on API proxy only")
|
|
}
|
|
|
|
serverConfig := api.ServerConfig{
|
|
Port: cfg.Port,
|
|
UIPath: cfg.UIPath,
|
|
Debug: cfg.Debug,
|
|
AllowCORS: cfg.AllowCORS,
|
|
CORSOrigin: cfg.CORSOrigin,
|
|
PangolinURL: cfg.PangolinAPIURL,
|
|
}
|
|
|
|
server := api.NewServer(db, serverConfig, configManager, cfg.TraefikStaticConfigPath)
|
|
go func() {
|
|
if err := server.Start(); err != nil {
|
|
log.Printf("Server error: %v", err)
|
|
close(stopChan)
|
|
}
|
|
}()
|
|
|
|
signalChan := make(chan os.Signal, 1)
|
|
signal.Notify(signalChan, os.Interrupt, syscall.SIGTERM)
|
|
|
|
serviceWatcher, err := services.NewServiceWatcher(db, configManager)
|
|
if err != nil {
|
|
log.Printf("Warning: Failed to create service watcher: %v", err)
|
|
serviceWatcher = nil
|
|
} else {
|
|
go serviceWatcher.Start(cfg.ServiceInterval)
|
|
}
|
|
|
|
select {
|
|
case <-signalChan:
|
|
log.Println("Received shutdown signal")
|
|
case <-stopChan:
|
|
log.Println("Received stop signal from server")
|
|
}
|
|
|
|
log.Println("Shutting down...")
|
|
resourceWatcher.Stop()
|
|
if serviceWatcher != nil {
|
|
serviceWatcher.Stop()
|
|
}
|
|
configGenerator.Stop()
|
|
server.Stop()
|
|
log.Println("Middleware Manager stopped")
|
|
}
|
|
|
|
func loadConfiguration(debug bool) Configuration {
|
|
checkInterval := 30 * time.Second
|
|
if intervalStr := getEnv("CHECK_INTERVAL_SECONDS", "30"); intervalStr != "" {
|
|
if interval, err := strconv.Atoi(intervalStr); err == nil && interval > 0 {
|
|
checkInterval = time.Duration(interval) * time.Second
|
|
}
|
|
}
|
|
|
|
generateInterval := 10 * time.Second
|
|
if intervalStr := getEnv("GENERATE_INTERVAL_SECONDS", "10"); intervalStr != "" {
|
|
if interval, err := strconv.Atoi(intervalStr); err == nil && interval > 0 {
|
|
generateInterval = time.Duration(interval) * time.Second
|
|
}
|
|
}
|
|
|
|
parsedServiceInterval := 30 * time.Second
|
|
if intervalStr := getEnv("SERVICE_INTERVAL_SECONDS", "30"); intervalStr != "" {
|
|
if interval, err := strconv.Atoi(intervalStr); err == nil && interval > 0 {
|
|
parsedServiceInterval = time.Duration(interval) * time.Second
|
|
}
|
|
}
|
|
|
|
allowCORS := false
|
|
if corsStr := getEnv("ALLOW_CORS", "false"); corsStr != "" {
|
|
allowCORS = strings.ToLower(corsStr) == "true"
|
|
}
|
|
|
|
if debugStr := getEnv("DEBUG", ""); debugStr != "" {
|
|
debug = strings.ToLower(debugStr) == "true"
|
|
}
|
|
|
|
return Configuration{
|
|
PangolinAPIURL: getEnv("PANGOLIN_API_URL", "http://pangolin:3001/api/v1"),
|
|
// Default to in-network Traefik service; host.docker.internal often fails inside containers
|
|
TraefikAPIURL: getEnv("TRAEFIK_API_URL", "http://traefik:8080"),
|
|
TraefikConfDir: getEnv("TRAEFIK_CONF_DIR", "/conf"),
|
|
DBPath: getEnv("DB_PATH", "/data/middleware.db"),
|
|
Port: getEnv("PORT", "3456"),
|
|
UIPath: getEnv("UI_PATH", "/app/ui/dist"),
|
|
ConfigDir: getEnv("CONFIG_DIR", "/app/config"),
|
|
ActiveDataSource: getEnv("ACTIVE_DATA_SOURCE", "pangolin"),
|
|
CheckInterval: checkInterval,
|
|
GenerateInterval: generateInterval,
|
|
ServiceInterval: parsedServiceInterval,
|
|
Debug: debug,
|
|
AllowCORS: allowCORS,
|
|
CORSOrigin: getEnv("CORS_ORIGIN", ""),
|
|
TraefikStaticConfigPath: getEnv("TRAEFIK_STATIC_CONFIG_PATH", "/etc/traefik/traefik.yml"),
|
|
}
|
|
}
|
|
|
|
func getEnv(key, fallback string) string {
|
|
if value, exists := os.LookupEnv(key); exists {
|
|
return value
|
|
}
|
|
return fallback
|
|
}
|