package main import ( "context" "log/slog" "os" "syscall" "time" "github.com/go-playground/validator/v10" evbus "github.com/vardius/message-bus" "gorm.io/gorm/schema" "github.com/fedor-git/wg-portal-2/internal" "github.com/fedor-git/wg-portal-2/internal/adapters" "github.com/fedor-git/wg-portal-2/internal/app" "github.com/fedor-git/wg-portal-2/internal/app/api/core" backendV0 "github.com/fedor-git/wg-portal-2/internal/app/api/v0/backend" handlersV0 "github.com/fedor-git/wg-portal-2/internal/app/api/v0/handlers" backendV1 "github.com/fedor-git/wg-portal-2/internal/app/api/v1/backend" handlersV1 "github.com/fedor-git/wg-portal-2/internal/app/api/v1/handlers" "github.com/fedor-git/wg-portal-2/internal/app/audit" "github.com/fedor-git/wg-portal-2/internal/app/auth" "github.com/fedor-git/wg-portal-2/internal/app/configfile" "github.com/fedor-git/wg-portal-2/internal/app/fanout" "github.com/fedor-git/wg-portal-2/internal/app/mail" "github.com/fedor-git/wg-portal-2/internal/app/route" "github.com/fedor-git/wg-portal-2/internal/app/users" "github.com/fedor-git/wg-portal-2/internal/app/webhooks" "github.com/fedor-git/wg-portal-2/internal/app/wireguard" "github.com/fedor-git/wg-portal-2/internal/config" "github.com/fedor-git/wg-portal-2/internal/domain" ) // main entry point for WireGuard Portal func main() { ctx := internal.SignalAwareContext(context.Background(), syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM) slog.Info("Starting WireGuard Portal V2...", "version", internal.Version) cfg, err := config.GetConfig() internal.AssertNoError(err) internal.SetupLogging(cfg.Advanced.LogLevel, cfg.Advanced.LogPretty, cfg.Advanced.LogJson) cfg.LogStartupValues() dbEncryptedSerializer := app.NewGormEncryptedStringSerializer(cfg.Database.EncryptionPassphrase) schema.RegisterSerializer("encstr", dbEncryptedSerializer) rawDb, err := adapters.NewDatabase(cfg.Database) internal.AssertNoError(err) database, err := adapters.NewSqlRepository(rawDb) internal.AssertNoError(err) wireGuard, err := wireguard.NewControllerManager(cfg) internal.AssertNoError(err) wgQuick := adapters.NewWgQuickRepo() mailer := adapters.NewSmtpMailRepo(cfg.Mail) metricsServer := adapters.NewMetricsServer(cfg) cfgFileSystem, err := adapters.NewFileSystemRepository(cfg.Advanced.ConfigStoragePath) internal.AssertNoError(err) shouldExit, err := app.HandleProgramArgs(rawDb) switch { case shouldExit && err == nil: return case shouldExit: slog.Error("Failed to process program args", "error", err) os.Exit(1) default: internal.AssertNoError(err) } queueSize := 100 eventBus := evbus.New(queueSize) fanout.Start(ctx, eventBus, cfg.Core.Fanout) // fanout.Start(ctx, eventBus, fanout.Config{ // Enabled: cfg.Core.Fanout.Enabled, // Peers: cfg.Core.Fanout.Peers, // AuthHeader: cfg.Core.Fanout.AuthHeader, // AuthValue: cfg.Core.Fanout.AuthValue, // Timeout: cfg.Core.Fanout.Timeout, // Debounce: cfg.Core.Fanout.Debounce, // SelfURL: cfg.Core.Fanout.SelfURL, // }) auditManager := audit.NewManager(database) auditRecorder, err := audit.NewAuditRecorder(cfg, eventBus, database) internal.AssertNoError(err) auditRecorder.StartBackgroundJobs(ctx) userManager, err := users.NewUserManager(cfg, eventBus, database, database) internal.AssertNoError(err) userManager.StartBackgroundJobs(ctx) authenticator, err := auth.NewAuthenticator(&cfg.Auth, cfg.Web.ExternalUrl, eventBus, userManager) internal.AssertNoError(err) authenticator.StartBackgroundJobs(ctx) webAuthn, err := auth.NewWebAuthnAuthenticator(cfg, eventBus, userManager) internal.AssertNoError(err) wireGuardManager, err := wireguard.NewWireGuardManager(cfg, eventBus, wireGuard, wgQuick, database) internal.AssertNoError(err) wireGuardManager.StartBackgroundJobs(ctx) statisticsCollector, err := wireguard.NewStatisticsCollector(cfg, eventBus, database, wireGuard, metricsServer) internal.AssertNoError(err) statisticsCollector.StartBackgroundJobs(ctx) cfgFileManager, err := configfile.NewConfigFileManager(cfg, eventBus, database, database, cfgFileSystem) internal.AssertNoError(err) cfgFileManager.StartBackgroundJobs(ctx) mailManager, err := mail.NewMailManager(cfg, mailer, cfgFileManager, database, database) internal.AssertNoError(err) routeManager, err := route.NewRouteManager(cfg, eventBus, database) internal.AssertNoError(err) routeManager.StartBackgroundJobs(ctx) webhookManager, err := webhooks.NewManager(cfg, eventBus) internal.AssertNoError(err) webhookManager.StartBackgroundJobs(ctx) err = app.Initialize(cfg, wireGuardManager, userManager) internal.AssertNoError(err) if cfg.Core.SyncOnStartup { syncCtx := domain.SetUserInfo(ctx, domain.SystemAdminContextUserInfo()) if err := wireGuardManager.RestoreInterfaceState(syncCtx, true /*updateDbOnError*/); err != nil { slog.Error("initial interface restore failed", "err", err) } if n, err := wireGuardManager.SyncAllPeersFromDB(syncCtx); err != nil { slog.Error("initial peer sync failed", "err", err) } else { slog.Info("initial peer sync done", "applied", n) } } validatorManager := validator.New() // region API v0 (SPA frontend) apiV0Session := handlersV0.NewSessionWrapper(cfg) apiV0Auth := handlersV0.NewAuthenticationHandler(authenticator, apiV0Session) apiV0BackendUsers := backendV0.NewUserService(cfg, userManager, wireGuardManager) apiV0BackendInterfaces := backendV0.NewInterfaceService(cfg, wireGuardManager, cfgFileManager) apiV0BackendPeers := backendV0.NewPeerService(cfg, wireGuardManager, cfgFileManager, mailManager) // apiV0BackendPeersWithEvents := handlersV0.NewEventingPeerService(apiV0BackendPeers, eventBus) // apiV0BackendPeersWithEvents := handlersV0.NewEventingPeerService(apiV0BackendPeers, eventBus) apiV0EndpointAuth := handlersV0.NewAuthEndpoint(cfg, apiV0Auth, apiV0Session, validatorManager, authenticator, webAuthn) apiV0EndpointAudit := handlersV0.NewAuditEndpoint(cfg, apiV0Auth, auditManager) apiV0EndpointUsers := handlersV0.NewUserEndpoint(cfg, apiV0Auth, validatorManager, apiV0BackendUsers) apiV0EndpointInterfaces := handlersV0.NewInterfaceEndpoint(cfg, apiV0Auth, validatorManager, apiV0BackendInterfaces) apiV0EndpointPeers := handlersV0.NewPeerEndpoint(cfg, apiV0Auth, validatorManager, apiV0BackendPeers) // apiV0EndpointPeers := handlersV0.NewPeerEndpoint(cfg, apiV0Auth, validatorManager, apiV0BackendPeersWithEvents) apiV0EndpointPeers.SetEventBus(eventBus) apiV0EndpointConfig := handlersV0.NewConfigEndpoint(cfg, apiV0Auth, wireGuard) apiV0EndpointTest := handlersV0.NewTestEndpoint(apiV0Auth) apiFrontend := handlersV0.NewRestApi(apiV0Session, apiV0EndpointAuth, apiV0EndpointAudit, apiV0EndpointUsers, apiV0EndpointInterfaces, apiV0EndpointPeers, apiV0EndpointConfig, apiV0EndpointTest, ) // endregion API v0 (SPA frontend) // region API v1 (User REST API) apiV1Auth := handlersV1.NewAuthenticationHandler(userManager) apiV1BackendUsers := backendV1.NewUserService(cfg, userManager) apiV1BackendPeers := backendV1.NewPeerService(cfg, wireGuardManager, userManager) apiV1BackendInterfaces := backendV1.NewInterfaceService(cfg, wireGuardManager) apiV1BackendProvisioning := backendV1.NewProvisioningService(cfg, userManager, wireGuardManager, cfgFileManager) apiV1BackendMetrics := backendV1.NewMetricsService(cfg, database, userManager, wireGuardManager) apiV1EndpointUsers := handlersV1.NewUserEndpoint(apiV1Auth, validatorManager, apiV1BackendUsers) apiV1EndpointPeers := handlersV1.NewPeerEndpoint(apiV1Auth, validatorManager, apiV1BackendPeers) apiV1EndpointPeers.SetEventBus(eventBus) apiV1EndpointInterfaces := handlersV1.NewInterfaceEndpoint(apiV1Auth, validatorManager, apiV1BackendInterfaces) apiV1EndpointProvisioning := handlersV1.NewProvisioningEndpoint(apiV1Auth, validatorManager, apiV1BackendProvisioning) apiV1EndpointMetrics := handlersV1.NewMetricsEndpoint(apiV1Auth, validatorManager, apiV1BackendMetrics) apiV1 := handlersV1.NewRestApi( apiV1EndpointUsers, apiV1EndpointPeers, apiV1EndpointInterfaces, apiV1EndpointProvisioning, apiV1EndpointMetrics, ) // endregion API v1 (User REST API) webSrv, err := core.NewServer(cfg, apiFrontend, apiV1) internal.AssertNoError(err) go metricsServer.Run(ctx) go webSrv.Run(ctx, cfg.Web.ListeningAddress) slog.Info("Application startup complete") // wait until context gets cancelled <-ctx.Done() slog.Info("Stopping WireGuard Portal") time.Sleep(5 * time.Second) // wait for (most) goroutines to finish gracefully slog.Info("Stopped WireGuard Portal") }