package server import ( "context" "fmt" "strings" ) // DNSProvider manages the TXT records ACME DNS-01 challenges need. type DNSProvider interface { // CreateTXT adds a TXT record with the given value at name. CreateTXT(ctx context.Context, name, value string) error // DeleteTXT removes every TXT record at name (best effort cleanup). DeleteTXT(ctx context.Context, name string) error } // challengeRecordName is the TXT record name a hostname's DNS-01 challenge // lives at; the wildcard of a hostname shares the same record. func challengeRecordName(hostname string) string { return "_acme-challenge." + hostname } // relativeRecordName strips the zone suffix from a record name, as DNS // provider APIs expect names relative to the zone. func relativeRecordName(name, zone string) (string, error) { suffix := "." + zone if !strings.HasSuffix(name, suffix) { return "", fmt.Errorf("record %q is not under zone %q", name, zone) } return strings.TrimSuffix(name, suffix), nil } // dnsZone guesses the registrable zone that hosts the tunnel hostnames: // the domain minus its first label when it has three or more (tunnel.tzgyn.dev // is served by the tzgyn.dev zone). Override with DNS_ZONE when wrong. func DNSZone(domain string) string { labels := strings.Split(domain, ".") if len(labels) >= 3 { return strings.Join(labels[1:], ".") } return domain }