feat(admin): lapse sweep and the four renewal notices

Hourly sweep marks expired Free instances lapsed and sends at most one
notice per instance per pass, most urgent first, recorded on the document
so a restart cannot re-send.

Deletion warnings are suppressed when the reaper is off. Promising a
deletion that will never happen is a lie, and a scarier one than silence.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
mrhid6
2026-07-26 13:20:00 +01:00
co-authored by Claude Opus 5
parent 909ddb884e
commit 372a8c5ddf
4 changed files with 228 additions and 0 deletions
+14
View File
@@ -10,6 +10,7 @@ import (
"net/url"
"os"
"strings"
"time"
)
type Config struct {
@@ -26,6 +27,7 @@ type Config struct {
AllowedOrigins []string
TrustProxy bool
Addr string
ReapAfter time.Duration
SMTPHost string
SMTPPort string
@@ -108,6 +110,18 @@ func Load() (Config, error) {
c.AllowedOrigins = append(c.AllowedOrigins, o)
}
}
// Mirrors the control plane's FREE_INSTANCE_REAP_AFTER so notice emails can
// name the real deletion date. An unparseable value is refused rather than
// silently treated as "off": a typo here would quietly stop every deletion
// warning while the control plane still deletes.
if v := os.Getenv("FREE_INSTANCE_REAP_AFTER"); v != "" {
d, err := time.ParseDuration(v)
if err != nil {
return Config{}, fmt.Errorf("FREE_INSTANCE_REAP_AFTER %q: %w", v, err)
}
c.ReapAfter = d
}
return c, nil
}