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/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) } 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) go observability.StartNATSWatcher(ciCtx, bus) handler := api.New(cfg, engine, store, bus, cfg.ArtifactRoot, web.FS()) 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") }