package main import ( "context" "log" "net/http" "os" "os/signal" "strings" "syscall" "time" "evobgp/internal/birdfmt" "evobgp/internal/config" "evobgp/internal/dbcli" "evobgp/internal/deploy" "evobgp/internal/httpapi" "evobgp/internal/ingest" "evobgp/internal/observability" "evobgp/internal/platform" "evobgp/internal/render" "evobgp/internal/scheduler" "evobgp/internal/version" ) // microVPS entrypoint: один процесс — HTTP API и фоновые воркеры scheduler, ingest, render, deploy (общий store и jobs.Registry). 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) } opts := httpapi.Options{ APIKeys: os.Getenv("EVOBGP_API_KEYS"), DatabaseURL: cfg.DatabaseURL, InsecureDev: os.Getenv("EVOBGP_DEV_INSECURE") == "1", SeedDemo: os.Getenv("EVOBGP_SEED_DEMO") != "0", BundleSeedHex: strings.TrimSpace(os.Getenv("EVOBGP_BUNDLE_SEED_HEX")), CORSAllowedOrigins: strings.TrimSpace(os.Getenv("EVOBGP_CORS_ORIGINS")), RuntimeLogsPolicyTenant: cfg.RuntimeLogsPolicyTenant, 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() schedDeps := &scheduler.Deps{Store: srv.Store(), Jobs: srv.Jobs()} ingestDeps := &ingest.Deps{Store: srv.Store()} renderDeps := &render.Deps{Store: srv.Store()} deployDeps := &deploy.Deps{Store: srv.Store()} go scheduler.Run(ctx, schedDeps) go ingest.Run(ctx, ingestDeps) go render.Run(ctx, renderDeps) go deploy.Run(ctx, deployDeps) 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-all") log.Printf("%s (microVPS): HTTP API on %s", svc, cfg.HTTPAddr) log.Printf("bundle public key (base64): %s", srv.BundlePublicKeyBase64()) 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 { log.Printf("draining %d job(s)…", n) if err := srv.Jobs().Drain(shutdownCtx); err != nil { log.Printf("job drain: %v", err) } } if err := httpSrv.Shutdown(shutdownCtx); err != nil { log.Printf("HTTP shutdown: %v", err) } log.Printf("%s stopped", platform.ServiceName("evobgp-all")) } 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) }