All checks were successful
CI / changes (push) Successful in 19s
CI / test (push) Successful in 31s
CI / build-ai-gateway (push) Successful in 1m8s
CI / build-ha-gateway (push) Successful in 1m19s
CI / build-discord-bot (push) Successful in 59s
CI / build-alexa-bridge (push) Successful in 1m8s
CI / build-alert-bridge (push) Successful in 1m17s
CI / build-tts-gateway (push) Successful in 1m10s
CI / build-tts-sidecar (push) Has been skipped
CI / build-tts-model (push) Has been skipped
New standalone service that receives authenticated HTTP POSTs (e.g. from a cronjob elsewhere on the home network) and posts them to a Discord channel via an Incoming Webhook. Fire-and-forget, no dependency on discord-bot's bot session at all - mirrors alexa-bridge's precedent of a small bridge service with its own HTTP listener and auth rather than adding an inbound edge to an existing internal service. Wires alert-bridge into go.work, CI (changes filter, test job, new build-alert-bridge job), and the other five Dockerfiles' COPY lines. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
94 lines
2.5 KiB
Go
94 lines
2.5 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"log/slog"
|
|
"net/http"
|
|
"os"
|
|
"os/signal"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/joho/godotenv"
|
|
|
|
httpadapter "gitea.nik4nao.com/nik/home-services/alert-bridge/internal/adapters/primary/http"
|
|
"gitea.nik4nao.com/nik/home-services/alert-bridge/internal/adapters/secondary/discordwebhook"
|
|
"gitea.nik4nao.com/nik/home-services/alert-bridge/internal/app"
|
|
"gitea.nik4nao.com/nik/home-services/alert-bridge/internal/config"
|
|
"gitea.nik4nao.com/nik/home-services/alert-bridge/internal/logger"
|
|
"gitea.nik4nao.com/nik/home-services/alert-bridge/internal/telemetry"
|
|
)
|
|
|
|
var version = "dev"
|
|
|
|
func main() {
|
|
_ = godotenv.Load()
|
|
|
|
cfg, err := config.Load()
|
|
if err != nil {
|
|
os.Stderr.WriteString("config error: " + err.Error() + "\n")
|
|
os.Exit(1)
|
|
}
|
|
|
|
log := logger.New(cfg.LogFormat, cfg.LogLevel)
|
|
slog.SetDefault(log)
|
|
log.Info("starting alert-bridge",
|
|
"version", version,
|
|
"http_port", cfg.HTTPPort,
|
|
"log_level", cfg.LogLevel,
|
|
"log_format", cfg.LogFormat,
|
|
)
|
|
|
|
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
|
|
defer stop()
|
|
ctx = logger.WithLogger(ctx, log)
|
|
|
|
shutdown, err := telemetry.Setup(ctx, "alert-bridge", version, cfg)
|
|
if err != nil {
|
|
log.Error("telemetry setup failed", "err", err)
|
|
os.Exit(1)
|
|
}
|
|
if cfg.OTELEndpoint != "" {
|
|
log.Info("telemetry enabled", "endpoint", cfg.OTELEndpoint)
|
|
} else {
|
|
log.Debug("telemetry disabled")
|
|
}
|
|
|
|
notifier := discordwebhook.New(cfg.DiscordWebhookURL, cfg.MentionUserID)
|
|
alertApp := app.NewAlertApp(notifier)
|
|
alertHandler := httpadapter.NewHandler(alertApp, cfg.APIKey)
|
|
|
|
mux := http.NewServeMux()
|
|
mux.Handle("POST /alerts", alertHandler)
|
|
mux.HandleFunc("GET /healthz", func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusOK)
|
|
})
|
|
|
|
srv := &http.Server{
|
|
Addr: ":" + cfg.HTTPPort,
|
|
Handler: mux,
|
|
ReadHeaderTimeout: 5 * time.Second,
|
|
}
|
|
|
|
go func() {
|
|
log.Info("alert-bridge listening", "addr", srv.Addr)
|
|
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
|
log.Error("serve failed", "err", err)
|
|
}
|
|
}()
|
|
|
|
<-ctx.Done()
|
|
log.Info("shutdown signal received, draining")
|
|
|
|
shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
defer cancel()
|
|
if err := srv.Shutdown(shutdownCtx); err != nil {
|
|
log.Error("http server shutdown failed", "err", err)
|
|
}
|
|
log.Info("shutdown complete")
|
|
|
|
if err := shutdown(context.Background()); err != nil {
|
|
log.Error("telemetry shutdown error", "err", err)
|
|
}
|
|
}
|