package main import ( "context" "log" "net/http" "os" "os/signal" "strings" "syscall" "time" "evobgp/internal/birdfmt" "evobgp/internal/config" "evobgp/internal/dbcli" "evobgp/internal/httpapi" "evobgp/internal/logging" "evobgp/internal/observability" "evobgp/internal/platform" "evobgp/internal/version" ) func main() { if len(os.Args) > 1 && os.Args[1] == "db" { os.Exit(dbcli.Run(os.Args[2:])) } cfg := config.Load() if err := config.ValidateProductionEnforce(); err != nil { log.Fatal(err) } seedDemo := os.Getenv("EVOBGP_SEED_DEMO") != "0" opts := httpapi.Options{ APIKeys: os.Getenv("EVOBGP_API_KEYS"), DatabaseURL: cfg.DatabaseURL, InsecureDev: os.Getenv("EVOBGP_DEV_INSECURE") == "1", SeedDemo: seedDemo, BundleSeedHex: strings.TrimSpace(os.Getenv("EVOBGP_BUNDLE_SEED_HEX")), CORSAllowedOrigins: strings.TrimSpace(os.Getenv("EVOBGP_CORS_ORIGINS")), JWTSecret: firstNonEmpty(os.Getenv("EVOBGP_AUTH_JWT_SECRET"), os.Getenv("AUTH_JWT_SECRET")), AuthIssuer: firstNonEmpty(os.Getenv("EVOBGP_AUTH_ISSUER"), os.Getenv("AUTH_ISSUER")), AuthPortalURL: firstNonEmpty(os.Getenv("EVOBGP_AUTH_PORTAL_URL"), os.Getenv("AUTH_PORTAL_URL")), PortalTenantID: strings.TrimSpace(os.Getenv("EVOBGP_PORTAL_TENANT_ID")), AuthRequired: boolFromEnv("EVOBGP_AUTH_REQUIRED", "AUTH_REQUIRED"), AuditIngestSecret: firstNonEmpty(os.Getenv("EVOBGP_AUTH_AUDIT_INGEST_SECRET"), os.Getenv("AUTH_AUDIT_INGEST_SECRET")), } srv, err := httpapi.New(opts) if err != nil { log.Fatal(err) } defer srv.Close() observability.SetBuildInfo(version.Version, version.GitSHA) ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) defer stop() otelShutdown := observability.StartOTelIfEnabled(ctx) defer func() { _ = otelShutdown(context.Background()) }() srv.StartBackground(ctx) startBirdMetricsPoller(ctx) httpSrv := &http.Server{ Addr: cfg.HTTPAddr, Handler: srv.Handler(), ReadHeaderTimeout: 10 * time.Second, ReadTimeout: 60 * time.Second, WriteTimeout: 120 * time.Second, IdleTimeout: 120 * time.Second, } go func() { svc := platform.ServiceName("evobgp-api") logging.Default().Info("listening", "service", svc, "addr", cfg.HTTPAddr) logging.Default().Info("bundle signing public key (base64, set on evobgp-node)", "key", srv.BundlePublicKeyBase64()) if opts.SeedDemo { tid, mCDN, mIP, rev, sp := srv.Store().DemoIDs() logging.Default().Info("demo ids", "tenant", tid, "module_cdn", mCDN, "module_ip_ranges", mIP, "revision", rev, "speaker", sp) logging.Default().Info("example API keys", "hint", "EVOBGP_API_KEYS=op|"+tid+"|operator,node|"+tid+"|node") logging.Default().Info("demo auth: Authorization: Bearer dev (operator, demo tenant only)") } if err := httpSrv.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Fatal(err) } }() <-ctx.Done() shutdownCtx, cancel := context.WithTimeout(context.Background(), 15*time.Second) defer cancel() n := srv.Jobs().RequestCancelAll() if n > 0 { logging.Default().Info("draining jobs", "count", n) if err := srv.Jobs().Drain(shutdownCtx); err != nil { logging.Default().Warn("job drain", "err", err) } } if err := httpSrv.Shutdown(shutdownCtx); err != nil { logging.Default().Warn("HTTP shutdown", "err", err) } logging.Default().Info("stopped", "service", platform.ServiceName("evobgp-api")) } func firstNonEmpty(candidates ...string) string { for _, c := range candidates { if v := strings.TrimSpace(c); v != "" { return v } } return "" } func boolFromEnv(keys ...string) bool { for _, k := range keys { v := strings.TrimSpace(os.Getenv(k)) if v == "" { continue } switch strings.ToLower(v) { case "1", "true", "yes", "on": return true case "0", "false", "no", "off": return false } } return false } func startBirdMetricsPoller(ctx context.Context) { sock := strings.TrimSpace(os.Getenv("EVOBGP_BIRDC_SOCKET")) if sock == "" { return } interval := 30 * time.Second if d, err := time.ParseDuration(strings.TrimSpace(os.Getenv("EVOBGP_BIRDC_INTERVAL"))); err == nil && d > 0 { interval = d } bin := strings.TrimSpace(os.Getenv("EVOBGP_BIRDC_BIN")) observability.StartBirdProtocolsPoller(ctx, sock, bin, interval, func(ctx context.Context, socket, birdcBin string) (string, error) { return birdfmt.ShowProtocols(ctx, socket, birdcBin) }, birdfmt.CountEstablishedBGPSessions, birdfmt.ParseBGPProtocolStates, ) log.Printf("birdc protocols poller enabled (socket=%s interval=%s)", sock, interval) }