Files
vantage/admin/cmd/main.go
T
mrhid6andClaude Opus 5 372a8c5ddf 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>
2026-07-26 13:20:00 +01:00

105 lines
2.8 KiB
Go

package main
import (
"context"
"errors"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"github.com/joho/godotenv"
"github.com/mrhid6/vantage/admin/internal/api"
"github.com/mrhid6/vantage/admin/internal/auth"
"github.com/mrhid6/vantage/admin/internal/config"
"github.com/mrhid6/vantage/admin/internal/db"
"github.com/mrhid6/vantage/admin/internal/inject"
"github.com/mrhid6/vantage/admin/internal/licensing"
"github.com/mrhid6/vantage/admin/internal/lifecycle"
"github.com/mrhid6/vantage/admin/internal/mail"
"github.com/mrhid6/vantage/admin/internal/models"
)
func main() {
godotenv.Load()
cfg, err := config.Load()
if err != nil {
log.Fatalf("configuration error: %v", err)
}
licensing.SetSigningKey(cfg.SigningKey)
api.SetAppLoginURL(cfg.AppLoginURL)
mail.Init(mail.Config{
Host: cfg.SMTPHost, Port: cfg.SMTPPort, From: cfg.SMTPFrom,
Username: cfg.SMTPUsername, Password: cfg.SMTPPassword,
PublicURL: cfg.PublicURL,
})
if !mail.Enabled() {
log.Println("warning: SMTP not configured; verification and licence emails will fail")
}
auth.InitRedis(cfg.RedisAddr, cfg.RedisUsername, cfg.RedisPassword)
pingCtx, pingCancel := context.WithTimeout(context.Background(), 10*time.Second)
if err := auth.Ping(pingCtx); err != nil {
pingCancel()
log.Fatalf("redis: %v", err)
}
pingCancel()
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
if err := db.Connect(ctx, cfg); err != nil {
cancel()
log.Fatalf("database: %v", err)
}
cancel()
log.Printf("connected: admin=%s control=%s", cfg.AdminDBName, cfg.ControlDBName)
idxCtx, idxCancel := context.WithTimeout(context.Background(), 30*time.Second)
if err := db.EnsureIndexes(idxCtx); err != nil {
idxCancel()
log.Fatalf("indexes: %v", err)
}
if err := models.SeedPlans(idxCtx); err != nil {
idxCancel()
log.Fatalf("plan seed: %v", err)
}
idxCancel()
reconcileCtx, stopReconcile := context.WithCancel(context.Background())
defer stopReconcile()
inject.StartReconciler(reconcileCtx)
lifecycle.SetPortalURL(cfg.PublicURL)
lifecycle.Start(reconcileCtx, cfg.ReapAfter)
srv := &http.Server{
Addr: cfg.Addr,
Handler: api.Routes(cfg),
ReadHeaderTimeout: 10 * time.Second,
ReadTimeout: 20 * time.Second,
WriteTimeout: 30 * time.Second,
IdleTimeout: 60 * time.Second,
}
go func() {
log.Printf("admin listening on %s", cfg.Addr)
if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
log.Fatalf("server error: %v", err)
}
}()
stopCtx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer stop()
<-stopCtx.Done()
shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 15*time.Second)
defer shutdownCancel()
_ = srv.Shutdown(shutdownCtx)
log.Println("admin stopped")
os.Exit(0)
}