package main import ( "context" "flag" "time" "gitcitadel-online/internal/cache" "gitcitadel-online/internal/config" "gitcitadel-online/internal/generator" "gitcitadel-online/internal/logger" "gitcitadel-online/internal/nostr" "gitcitadel-online/internal/server" ) func main() { configPath := flag.String("config", "config.yaml", "Path to configuration file") devMode := flag.Bool("dev", false, "Enable development mode") logLevel := flag.String("log-level", "info", "Log level (debug, info, warn, error)") flag.Parse() // Initialize logger if *devMode { logger.Init("debug", true) } else { logger.Init(*logLevel, false) } // Load configuration cfg, err := config.LoadConfig(*configPath) if err != nil { logger.Fatalf("Failed to load config: %v", err) } if err := cfg.Validate(); err != nil { logger.Fatalf("Invalid config: %v", err) } if *devMode { logger.Info("Development mode enabled") } // Initialize caches pageCache := cache.NewCache() feedCache := cache.NewFeedCache() // Initialize media cache mediaCache, err := cache.NewMediaCache("cache/media") if err != nil { logger.Fatalf("Failed to initialize media cache: %v", err) } // Initialize Nostr client profileRelays := cfg.GetProfilesRelays() contactRelays := cfg.GetContactFormRelays() nostrClient := nostr.NewClient(cfg.Relays.Feeds, profileRelays, contactRelays) ctx := context.Background() if err := nostrClient.Connect(ctx); err != nil { logger.Warnf("Failed to connect to relays: %v", err) } // Initialize services // Use standard Nostr kind constants articleKinds := nostr.SupportedArticleKinds() // Use first profile relay for wiki service (fallback) wikiRelay := cfg.Relays.Feeds if len(profileRelays) > 0 { wikiRelay = profileRelays[0] } wikiService := nostr.NewWikiService(nostrClient, articleKinds, nostr.KindWiki, wikiRelay, nostr.KindIndex, nostr.KindBlog, nostr.KindLongform) feedService := nostr.NewFeedService(nostrClient, nostr.KindNote) issueService := nostr.NewIssueService(nostrClient, nostr.KindIssue, nostr.KindRepoAnnouncement) ebooksService := nostr.NewEBooksService(nostrClient, nostr.KindIndex, cfg.Relays.Feeds) // Initialize HTML generator htmlGenerator, err := generator.NewHTMLGenerator( "templates", cfg.LinkBaseURL, cfg.SEO.SiteName, cfg.SEO.SiteURL, cfg.SEO.DefaultImage, nostrClient, ) if err != nil { logger.Fatalf("Failed to initialize HTML generator: %v", err) } // Initialize cache rewarming rewarmer := cache.NewRewarmer( pageCache, feedCache, wikiService, feedService, ebooksService, htmlGenerator, cfg.WikiIndex, cfg.BlogIndex, cfg.Feed.Relay, cfg.Feed.MaxEvents, time.Duration(cfg.Cache.RefreshIntervalMinutes)*time.Minute, time.Duration(cfg.Feed.PollIntervalMinutes)*time.Minute, ) // Generate minimal landing page immediately so server can start serving logger.Info("Generating initial landing page...") initialLandingHTML, err := htmlGenerator.GenerateLandingPage( []generator.WikiPageInfo{}, nil, // newestBlogItem nil, // newestArticleItem []generator.ArticleItemInfo{}, // allArticleItems []generator.EBookInfo{}, // allEBooks ) if err == nil { if err := pageCache.Set("/", initialLandingHTML); err != nil { logger.Warnf("Failed to cache initial landing page: %v", err) } else { logger.Info("Initial landing page cached") } } else { logger.Warnf("Failed to generate initial landing page: %v", err) } // Start cache rewarming (runs in background) rewarmer.Start(ctx) // Initialize HTTP server httpServer := server.NewServer(cfg.Server.Port, pageCache, feedCache, mediaCache, issueService, cfg.RepoAnnouncement, htmlGenerator, nostrClient) // Start server in goroutine go func() { if err := httpServer.Start(); err != nil { logger.Fatalf("Server failed: %v", err) } }() logger.Infof("Server started on port %d", cfg.Server.Port) logger.Info("Cache rewarming in progress... (pages will update as they become available)") // Wait for shutdown signal httpServer.WaitForShutdown() // Close Nostr client nostrClient.Close() }