2026-08-23 20:39:22 +03:00
|
|
|
package db
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
2026-08-26 19:45:48 +03:00
|
|
|
"encoding/json"
|
2026-08-23 20:39:22 +03:00
|
|
|
"fmt"
|
|
|
|
|
|
|
|
|
|
"cloudipvalidator/internal/config"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// BootstrapFromConfig ensures the database's baseline state matches the
|
|
|
|
|
// YAML config on a fresh install, then gets out of the way.
|
|
|
|
|
//
|
|
|
|
|
// - ip_addresses -> SeedQueue: unchanged, always additive. This is a
|
|
|
|
|
// separate concept from the runtime admin queue API (SubmitIPs) — every
|
|
|
|
|
// control-api startup re-adds any address from cfg.IPAddresses that
|
|
|
|
|
// isn't already in the queue.
|
|
|
|
|
// - validators / sites / target_groups / check_types: applied from YAML
|
|
|
|
|
// only if the corresponding table is currently empty. Once any row
|
|
|
|
|
// exists (via this bootstrap or the admin API), YAML for that section
|
|
|
|
|
// is ignored on every subsequent startup — the database is the source
|
|
|
|
|
// of truth from that point on. This is what makes admin API changes to
|
|
|
|
|
// these four entities survive a restart.
|
|
|
|
|
func (d *DB) BootstrapFromConfig(ctx context.Context, cfg *config.ControlAPI) error {
|
|
|
|
|
if err := d.bootstrapValidators(ctx, cfg.Validators); err != nil {
|
|
|
|
|
return fmt.Errorf("bootstrap validators: %w", err)
|
|
|
|
|
}
|
|
|
|
|
if err := d.bootstrapSites(ctx, cfg.Sites); err != nil {
|
|
|
|
|
return fmt.Errorf("bootstrap sites: %w", err)
|
|
|
|
|
}
|
|
|
|
|
if err := d.bootstrapTargetGroups(ctx, cfg.Targets); err != nil {
|
|
|
|
|
return fmt.Errorf("bootstrap target groups: %w", err)
|
|
|
|
|
}
|
|
|
|
|
if err := d.bootstrapCheckTypes(ctx, cfg.CheckTypes); err != nil {
|
|
|
|
|
return fmt.Errorf("bootstrap check types: %w", err)
|
|
|
|
|
}
|
2026-08-24 10:29:08 +03:00
|
|
|
if err := d.bootstrapSettings(ctx, cfg.Orchestrator.FIPSettleSeconds); err != nil {
|
|
|
|
|
return fmt.Errorf("bootstrap settings: %w", err)
|
|
|
|
|
}
|
2026-08-26 19:45:48 +03:00
|
|
|
if err := d.bootstrapInboundChecks(ctx, cfg.Inbound.Ports, cfg.Inbound.ICMP); err != nil {
|
|
|
|
|
return fmt.Errorf("bootstrap inbound checks: %w", err)
|
|
|
|
|
}
|
2026-08-23 20:39:22 +03:00
|
|
|
if err := d.SeedQueue(ctx, cfg.IPAddresses); err != nil {
|
|
|
|
|
return fmt.Errorf("seed ip queue: %w", err)
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (d *DB) bootstrapValidators(ctx context.Context, validators []config.ValidatorConfig) error {
|
|
|
|
|
var count int
|
|
|
|
|
if err := d.QueryRowContext(ctx, `SELECT COUNT(*) FROM validators`).Scan(&count); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if count > 0 {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
for _, v := range validators {
|
|
|
|
|
if err := d.RegisterValidator(ctx, v.ValidatorID, "", v.OSPortID, ""); err != nil {
|
|
|
|
|
return fmt.Errorf("seed validator %s: %w", v.ValidatorID, err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (d *DB) bootstrapSites(ctx context.Context, sites []config.SiteConfig) error {
|
|
|
|
|
var count int
|
|
|
|
|
if err := d.QueryRowContext(ctx, `SELECT COUNT(*) FROM sites`).Scan(&count); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if count > 0 {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
for _, s := range sites {
|
|
|
|
|
if err := d.UpsertSite(ctx, s.Index, s.SiteID); err != nil {
|
|
|
|
|
return fmt.Errorf("seed site %s: %w", s.SiteID, err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (d *DB) bootstrapTargetGroups(ctx context.Context, targets map[string][]string) error {
|
|
|
|
|
var count int
|
|
|
|
|
if err := d.QueryRowContext(ctx, `SELECT COUNT(*) FROM target_groups`).Scan(&count); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if count > 0 {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
for name, addrs := range targets {
|
|
|
|
|
if err := d.UpsertTargetGroup(ctx, name, addrs); err != nil {
|
|
|
|
|
return fmt.Errorf("seed target group %s: %w", name, err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (d *DB) bootstrapCheckTypes(ctx context.Context, checkTypes []config.CheckTypeConfig) error {
|
|
|
|
|
var count int
|
|
|
|
|
if err := d.QueryRowContext(ctx, `SELECT COUNT(*) FROM check_types`).Scan(&count); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if count > 0 {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
for _, ct := range checkTypes {
|
|
|
|
|
if err := d.UpsertCheckType(ctx, ct.Name, ct.Enabled, ct.Targets); err != nil {
|
|
|
|
|
return fmt.Errorf("seed check type %s: %w", ct.Name, err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
2026-08-24 10:29:08 +03:00
|
|
|
|
|
|
|
|
func (d *DB) bootstrapSettings(ctx context.Context, fipSettleSeconds int) error {
|
|
|
|
|
var count int
|
|
|
|
|
if err := d.QueryRowContext(ctx, `SELECT COUNT(*) FROM settings`).Scan(&count); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if count > 0 {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
now := timeToDB(Now())
|
|
|
|
|
_, err := d.ExecContext(ctx, `
|
|
|
|
|
INSERT INTO settings (id, fip_settle_seconds, created_at, updated_at) VALUES (1, ?, ?, ?)
|
|
|
|
|
`, fipSettleSeconds, now, now)
|
|
|
|
|
return err
|
|
|
|
|
}
|
2026-08-26 19:45:48 +03:00
|
|
|
|
|
|
|
|
func (d *DB) bootstrapInboundChecks(ctx context.Context, ports []int, icmp bool) error {
|
|
|
|
|
var count int
|
|
|
|
|
if err := d.QueryRowContext(ctx, `SELECT COUNT(*) FROM inbound_checks_settings`).Scan(&count); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if count > 0 {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
if ports == nil {
|
|
|
|
|
ports = []int{}
|
|
|
|
|
}
|
|
|
|
|
payload, err := json.Marshal(ports)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
now := timeToDB(Now())
|
|
|
|
|
_, err = d.ExecContext(ctx, `
|
|
|
|
|
INSERT INTO inbound_checks_settings (id, ports, icmp, created_at, updated_at) VALUES (1, ?, ?, ?, ?)
|
|
|
|
|
`, string(payload), icmp, now, now)
|
|
|
|
|
return err
|
|
|
|
|
}
|