package main import ( "context" "log" "net/http" "os" "os/signal" "syscall" "time" "github.com/mattnite/cairn/internal/blob" "github.com/mattnite/cairn/internal/config" "github.com/mattnite/cairn/internal/database" "github.com/mattnite/cairn/internal/forgejo" "github.com/mattnite/cairn/internal/web" ) func main() { cfg, err := config.Load() if err != nil { log.Fatalf("Loading config: %v", err) } ctx, cancel := context.WithCancel(context.Background()) defer cancel() pool, err := database.Connect(ctx, cfg.DatabaseURL) if err != nil { log.Fatalf("Connecting to database: %v", err) } defer pool.Close() if err := database.Migrate(ctx, pool); err != nil { log.Fatalf("Running migrations: %v", err) } store, err := blob.NewS3Store(cfg.S3Endpoint, cfg.S3AccessKey, cfg.S3SecretKey, cfg.S3Bucket, cfg.S3UseSSL) if err != nil { log.Fatalf("Creating blob store: %v", err) } if err := store.EnsureBucket(ctx); err != nil { log.Fatalf("Ensuring bucket: %v", err) } var forgejoClient *forgejo.Client if cfg.ForgejoURL != "" && cfg.ForgejoToken != "" { forgejoClient = forgejo.NewClient(cfg.ForgejoURL, cfg.ForgejoToken) log.Printf("Forgejo integration enabled: %s", cfg.ForgejoURL) } router, err := web.NewRouter(web.RouterConfig{ Pool: pool, Store: store, ForgejoClient: forgejoClient, WebhookSecret: cfg.ForgejoWebhookSecret, }) if err != nil { log.Fatalf("Creating router: %v", err) } srv := &http.Server{ Addr: cfg.ListenAddr, Handler: router, ReadTimeout: 30 * time.Second, WriteTimeout: 60 * time.Second, IdleTimeout: 120 * time.Second, } go func() { sigCh := make(chan os.Signal, 1) signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM) <-sigCh log.Println("Shutting down...") shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 10*time.Second) defer shutdownCancel() if err := srv.Shutdown(shutdownCtx); err != nil { log.Printf("Shutdown error: %v", err) } }() log.Printf("Cairn server listening on %s", cfg.ListenAddr) if err := srv.ListenAndServe(); err != http.ErrServerClosed { log.Fatalf("Server error: %v", err) } }