Add 80% limit warning, stop repeat notifications, fix review findings

Notifications:
- Warn once when a counter reaches warn_percent of its limit, through
  separate warn_*_url endpoints.
- Write ntf/ markers so a lost Redis reply cannot re-send a breach.
- Retry only when the request never left or the web app answered 429/5xx.

Fixes:
- Retry transactions Redis rejected; treat pool timeouts as definite.
- Install the signal handler before startup work.
- Stop logging the login suffix; redact notify URL credentials.
- Reject corrupt or empty CDBs, truncated hash tables included.
- Require a Sentinel password, refuse example placeholders, and default
  admin_addr to localhost.
- Normalise /counters domains, count on the arrival day, log rejections,
  hide internals in errors and exit 1 on a failed drain.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Dimitrije Dimitrijevic
2026-11-09 09:01:56 +01:00
co-authored by Claude Opus 5.5
parent c43ff17627
commit 54f9626121
29 changed files with 1673 additions and 245 deletions
+47
View File
@@ -415,3 +415,50 @@ func TestRun_SignalLoop(t *testing.T) {
t.Errorf("log:\n%s", e.logs())
}
}
// A SIGHUP while the daemon starts must not kill it, and the reload it asked
// for runs once the daemon is ready.
func TestRun_SIGHUPDuringStartupIsQueued(t *testing.T) {
e := newEnv(t, "")
beforeStartup = func() { syscall.Kill(os.Getpid(), syscall.SIGHUP) }
t.Cleanup(func() { beforeStartup = nil })
done := make(chan int, 1)
var out, errb bytes.Buffer
go func() { done <- run([]string{"-config", e.cfgPath}, &out, &errb, nil) }()
deadline := time.Now().Add(5 * time.Second)
for !strings.Contains(e.logs(), "domains db reloaded") {
if time.Now().After(deadline) {
t.Fatalf("the early SIGHUP was not honoured; log:\n%s", e.logs())
}
time.Sleep(20 * time.Millisecond)
}
syscall.Kill(os.Getpid(), syscall.SIGTERM)
select {
case code := <-done:
if code != 0 {
t.Errorf("exit code %d, stderr %q", code, errb.String())
}
case <-time.After(15 * time.Second):
t.Fatal("run did not return after SIGTERM")
}
}
func TestNewApp_EmptyCDBIsFatal(t *testing.T) {
e := newEnv(t, "")
w, err := cdb.Create(e.cdbPath)
if err != nil {
t.Fatal(err)
}
if err := w.Close(); err != nil {
t.Fatal(err)
}
cfg, err := config.Load(e.cfgPath)
if err != nil {
t.Fatal(err)
}
if a, err := newApp(cfg); err == nil {
a.Shutdown(context.Background())
t.Fatal("an empty domains db must be fatal at startup")
}
}