b114251f45
Users get a notification_channels table (replacing users.ntfy_topic); pages fan
out to every enabled channel through a notify.Provider registry, with ntfy as
the first provider. Fallbacks are typed {type, address}. Schema edited in place:
existing databases must be recreated.
Claude-Session: https://claude.ai/code/session_016mBLURvJoMuUEr9cB2RpUN
103 lines
2.9 KiB
Go
103 lines
2.9 KiB
Go
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{}
|
|
}
|