package main import ( "context" "fmt" "log" "net/http" "os" "os/signal" "syscall" "time" "github.com/gorilla/sessions" "github.com/joho/godotenv" _ "github.com/lib/pq" "github.com/forgeo/forgebucket/internal/api" "github.com/forgeo/forgebucket/internal/config" "github.com/forgeo/forgebucket/internal/db" "github.com/forgeo/forgebucket/internal/domain/ci" gitdomain "github.com/forgeo/forgebucket/internal/domain/git" "github.com/forgeo/forgebucket/internal/domain/gitops" "github.com/forgeo/forgebucket/internal/domain/oci" "github.com/forgeo/forgebucket/internal/domain/scanning" "github.com/forgeo/forgebucket/internal/domain/vulnscan" "github.com/forgeo/forgebucket/internal/domain/sbom" "github.com/forgeo/forgebucket/internal/domain/signing" "github.com/forgeo/forgebucket/internal/events" "github.com/forgeo/forgebucket/internal/observability" "github.com/forgeo/forgebucket/internal/models/migrations" "github.com/forgeo/forgebucket/web" ) func main() { _ = godotenv.Load() cfg, err := config.Load() if err != nil { log.Fatalf("config: %v", err) } engine, err := db.Open(cfg.DatabaseURL, cfg.Debug) if err != nil { log.Fatalf("database: %v", err) } defer engine.Close() if err := migrations.Run(engine); err != nil { log.Fatalf("migrations: %v", err) } gitdomain.SetRepoRoot(cfg.RepoRoot) if err := os.MkdirAll(cfg.ArtifactRoot, 0755); err != nil { log.Fatalf("artifact root: %v", err) } ociRegistry, err := oci.New(cfg.OCIRoot) if err != nil { log.Fatalf("oci: %v", err) } log.Printf("oci: registry initialised at %s", cfg.OCIRoot) bus, err := events.New(cfg.NATSUrl) if err != nil { log.Fatalf("events: %v", err) } defer bus.Close() store := sessions.NewCookieStore([]byte(cfg.SessionSecret)) store.Options = &sessions.Options{ Path: "/", MaxAge: 86400 * 7, HttpOnly: true, Secure: !cfg.Debug, SameSite: http.SameSiteLaxMode, } // Start CI orchestrator and runner manager in background goroutines. ciCtx, ciCancel := context.WithCancel(context.Background()) defer ciCancel() orchestrator := ci.NewOrchestrator(engine, bus) go orchestrator.Start(ciCtx) runnerMgr := ci.NewRunnerManager(engine, bus, cfg, 4) go runnerMgr.Start(ciCtx) gitopsCtrl := gitops.NewController(engine, bus, cfg) go gitopsCtrl.Start(ciCtx) sbomGen := sbom.NewGenerator(engine, bus) go sbomGen.Start(ciCtx) go observability.StartNATSWatcher(ciCtx, bus) // Initialise artifact signing key store. var keyStore *signing.KeyStore if cfg.ArtifactSigningKey != "" { keyStore, err = signing.New(cfg.ArtifactSigningKey) if err != nil { log.Fatalf("signing: %v", err) } } else { keyStore, err = signing.Generate() if err != nil { log.Fatalf("signing: %v", err) } } log.Printf("signing: key store initialised (keyId=%s)", keyStore.KeyID()) secretScanner, err := scanning.New(engine, bus) if err != nil { log.Fatalf("secret scanner: %v", err) } go secretScanner.Start(ciCtx) vulnScanner := vulnscan.NewScanner(engine, bus) go vulnScanner.Start(ciCtx) handler := api.New(cfg, engine, store, bus, cfg.ArtifactRoot, web.FS(), *keyStore, sbomGen, ociRegistry, secretScanner, vulnScanner) srv := &http.Server{ Addr: fmt.Sprintf(":%s", cfg.Port), Handler: handler, ReadTimeout: 15 * time.Second, WriteTimeout: 60 * time.Second, IdleTimeout: 120 * time.Second, } go func() { log.Printf("ForgeBucket listening on http://localhost:%s", cfg.Port) if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Fatalf("server: %v", err) } }() quit := make(chan os.Signal, 1) signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) <-quit ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() if err := srv.Shutdown(ctx); err != nil { log.Printf("shutdown: %v", err) } log.Println("ForgeBucket stopped") }