| index 124ce77a6..df32d490c 100644 |
| --- a/internal/registry/registry.go |
| +++ b/internal/registry/registry.go |
| @@ -346,6 +346,19 @@ CREATE TABLE IF NOT EXISTS route_aliases ( |
| dns_checked_at INTEGER NOT NULL DEFAULT 0, |
| cert_expires_at INTEGER NOT NULL DEFAULT 0, |
| created_at INTEGER NOT NULL DEFAULT 0 |
| +);`); err != nil { |
| + return err |
| + } |
| + // wildcard_domains is a derived table (a later reconcile pass owns every |
| + // row, materializing domains.Claim records whose Wildcard capability is |
| + // set into it) — a plain CREATE TABLE IF NOT EXISTS, same shape as |
| + // route_aliases above: additive, so existing DBs migrate on open. |
| + if _, err := r.sql.Exec(` |
| +CREATE TABLE IF NOT EXISTS wildcard_domains ( |
| + host TEXT PRIMARY KEY, |
| + account TEXT NOT NULL DEFAULT '', |
| + app TEXT NOT NULL DEFAULT '', |
| + created_at INTEGER NOT NULL DEFAULT 0 |
| );`); err != nil { |
| return err |
| } |
| @@ -1261,6 +1274,53 @@ func (r *Registry) SetAliasCertExpiresAt(alias string, at int64) error { |
| return err |
| } |
| |
| +// WildcardDomain is one derived row of the wildcard_domains table: a |
| +// customer domain whose claim carries wildcard:true and whose primary |
| +// route lives on this box — i.e. a delegated wildcard-cert parent the |
| +// edge should obtain *.host for. Wholly pass-owned, like route_aliases: |
| +// every row traces to a claim, so removal needs no ownership flag. |
| +type WildcardDomain struct { |
| + Host string |
| + Account string |
| + App string |
| + CreatedAt int64 |
| +} |
| + |
| +// ListWildcardDomains returns every wildcard domain, ordered by host. |
| +func (r *Registry) ListWildcardDomains() ([]WildcardDomain, error) { |
| + rows, err := r.sql.Query(`SELECT host, account, app, created_at FROM wildcard_domains ORDER BY host`) |
| + if err != nil { |
| + return nil, err |
| + } |
| + defer rows.Close() |
| + var out []WildcardDomain |
| + for rows.Next() { |
| + var w WildcardDomain |
| + if err := rows.Scan(&w.Host, &w.Account, &w.App, &w.CreatedAt); err != nil { |
| + return nil, err |
| + } |
| + out = append(out, w) |
| + } |
| + return out, rows.Err() |
| +} |
| + |
| +// UpsertWildcardDomain inserts or updates a row by host. |
| +func (r *Registry) UpsertWildcardDomain(w WildcardDomain) error { |
| + _, err := r.sql.Exec(` |
| +INSERT INTO wildcard_domains (host, account, app, created_at) |
| +VALUES (?, ?, ?, ?) |
| +ON CONFLICT(host) DO UPDATE SET account=excluded.account, app=excluded.app, created_at=excluded.created_at`, |
| + w.Host, w.Account, w.App, w.CreatedAt) |
| + return err |
| +} |
| + |
| +// DeleteWildcardDomain removes host's row. Idempotent: a host already gone |
| +// is a success, same contract as DeleteAlias. |
| +func (r *Registry) DeleteWildcardDomain(host string) error { |
| + _, err := r.sql.Exec(`DELETE FROM wildcard_domains WHERE host = ?`, host) |
| + return err |
| +} |
| + |
| // Fingerprint summarizes the routes, fleets, fleet_boxes and route_aliases |
| // tables over a fresh connection, for the edge's cheap change poll. It is |
| // content-accurate: it hashes the actual VALUES of the reload-relevant |