Files
2026-09-16 06:26:16 +02:00

101 lines
3.2 KiB
Go

package main
import (
"context"
"flag"
"log"
"net/http"
"os"
"os/signal"
"path/filepath"
"sync"
"syscall"
"time"
"github.com/example/notify-gateway/internal/auth"
"github.com/example/notify-gateway/internal/config"
"github.com/example/notify-gateway/internal/divera"
"github.com/example/notify-gateway/internal/gateway"
"github.com/example/notify-gateway/internal/httpserver"
"github.com/example/notify-gateway/internal/mailingress"
"github.com/example/notify-gateway/internal/outbox"
)
func main() {
configPath := flag.String("config", "./data/config.json", "path to persistent config")
outboxPath := flag.String("outbox", "", "SQLite outbox path (default: outbox.db next to config)")
flag.Parse()
logger := log.New(os.Stdout, "notify-gateway ", log.LstdFlags|log.LUTC)
store, err := config.Open(*configPath)
if err != nil {
logger.Fatal(err)
}
cfg := store.Get()
if err := config.Validate(cfg); err != nil {
logger.Fatal(err)
}
if cfg.Server.AdminPasswordHash == "" {
password := os.Getenv("GATEWAY_ADMIN_PASSWORD")
generated := false
if password == "" {
password = randomPassword()
generated = true
}
hash, err := auth.HashPassword(password)
if err != nil {
logger.Fatal(err)
}
if err := store.Update(func(c *config.Config) error { c.Server.AdminPasswordHash = hash; return nil }); err != nil {
logger.Fatal(err)
}
if generated {
logger.Printf("INITIAL ADMIN PASSWORD (shown once): %s", password)
} else {
logger.Printf("initialized admin password from GATEWAY_ADMIN_PASSWORD")
}
}
client := divera.New(func() config.DiveraConfig { return store.Get().Divera })
dispatcher := gateway.New(store, client)
server := httpserver.New(store, dispatcher, client, logger)
if *outboxPath == "" {
*outboxPath = filepath.Join(filepath.Dir(*configPath), "outbox.db")
}
db, err := outbox.Open(*outboxPath)
if err != nil {
logger.Fatal(err)
}
defer db.Close()
queue := &gateway.Queue{Store: db, Dispatcher: dispatcher, Logger: logger}
poller := &mailingress.Poller{Config: store, Queue: queue}
server.UseQueue(queue, poller)
workerCtx, cancelWorkers := context.WithCancel(context.Background())
defer cancelWorkers()
var workers sync.WaitGroup
workers.Add(2)
go func() { defer workers.Done(); queue.Run(workerCtx) }()
go func() { defer workers.Done(); poller.Run(workerCtx) }()
cfg = store.Get()
srv := &http.Server{Addr: cfg.Server.Listen, Handler: server.Handler(), ReadHeaderTimeout: 10 * time.Second, ReadTimeout: 20 * time.Second, WriteTimeout: 30 * time.Second, IdleTimeout: 60 * time.Second}
logger.Printf("listening on %s (dry_run=%v)", cfg.Server.Listen, cfg.Divera.DryRun)
stop, stopSignals := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stopSignals()
serverErr := make(chan error, 1)
go func() { serverErr <- srv.ListenAndServe() }()
select {
case <-stop.Done():
case err := <-serverErr:
if err != nil && err != http.ErrServerClosed {
logger.Printf("HTTP server stopped: %v", err)
}
}
shutdown, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := srv.Shutdown(shutdown); err != nil {
_ = srv.Close()
}
cancelWorkers()
workers.Wait()
}
func randomPassword() string { return auth.RandomSecret() }