package main import ( "context" "log" "net/http" "os/signal" "syscall" "time" "git.ryuvia.com/niklas/terdut-server/internal/api" "git.ryuvia.com/niklas/terdut-server/internal/config" "git.ryuvia.com/niklas/terdut-server/internal/db" "git.ryuvia.com/niklas/terdut-server/internal/notify" ) var version = "dev" func main() { cfg := config.Load() if err := cfg.Validate(); err != nil { log.Fatalf("config: %v", err) } database, err := db.Open(cfg.DSN) if err != nil { log.Fatalf("open db: %v", err) } defer database.Close() if err := db.Migrate(database); err != nil { log.Fatalf("migrate: %v", err) } notifyCfg := api.NotifyConfig{ Providers: notifyProviders(cfg), Fallback: notifyFallback(cfg), PublicURL: cfg.PublicURL, RepeatEvery: cfg.NotifyRepeat, } // The behaviour knobs move into the database on first start, after which an // administrator owns them and a redeploy leaves them alone. if err := api.SeedSettings(context.Background(), database, cfg); err != nil { log.Fatalf("seed settings: %v", err) } if err := api.SeedOperatorKey(context.Background(), database, cfg.OperatorKey); err != nil { log.Fatalf("%v", err) } router := api.NewRouter(database, notifyCfg, cfg, version) srv := &http.Server{ Addr: cfg.Addr, Handler: router, ReadTimeout: 15 * time.Second, WriteTimeout: 15 * time.Second, IdleTimeout: 60 * time.Second, } ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) defer stop() go api.StartArchiver(ctx, database, cfg.ArchiveAfter, cfg.StaleAfter, notifyCfg) go api.StartNotifier(ctx, database, notifyCfg) go func() { log.Printf("terdut-server %s listening on %s", version, cfg.Addr) if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Fatalf("listen: %v", err) } }() <-ctx.Done() log.Println("shutting down") shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() if err := srv.Shutdown(shutdownCtx); err != nil { log.Printf("shutdown: %v", err) } } // notifyProviders registers the notification providers this install has // configuration for. A provider with no configuration is simply absent, which is // what keeps it out of the web UI's list of channel types. func notifyProviders(cfg config.Config) *notify.Registry { var providers []notify.Provider if cfg.NtfyURL != "" { providers = append(providers, notify.NewNtfy(cfg.NtfyURL, cfg.NtfyToken)) } return notify.NewRegistry(providers...) } // notifyFallback is where an incident goes when nobody on call can be reached. // Only ntfy has a fallback setting today; a second provider adds its own. func notifyFallback(cfg config.Config) notify.Target { if cfg.NtfyURL != "" && cfg.NtfyFallbackTopic != "" { return notify.Target{Type: notify.TypeNtfy, Address: cfg.NtfyFallbackTopic} } return notify.Target{} }