2025-01-10 23:43:16 +08:00
|
|
|
package models
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
|
|
|
|
"github.com/go-acme/lego/v4/log"
|
|
|
|
)
|
|
|
|
|
|
|
|
// RawRecordConfig stores the raw user-input from dnsconfig.js for a DNS Record
|
|
|
|
// (A, MX, SRV, etc). This is later processed to become a RecordConfig. NOTE:
|
|
|
|
// Only newer rtypes are processed this way. Eventually the legacy types will
|
|
|
|
// be converted and removed.
|
|
|
|
type RawRecordConfig struct {
|
|
|
|
Type string `json:"type"`
|
|
|
|
Args []string `json:"args,omitempty"`
|
|
|
|
Metadata []map[string]any `json:"metas,omitempty"`
|
|
|
|
TTL uint32 `json:"ttl,omitempty"`
|
|
|
|
SubDomain string `json:"subdomain,omitempty"`
|
|
|
|
|
|
|
|
// Override NO_PURGE and delete this record
|
|
|
|
EnsureAbsent bool `json:"ensure_absent,omitempty"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// FromRaw converts the RawRecordConfig into a RecordConfig by calling the
|
|
|
|
// conversion function provided when the rtype was registered.
|
|
|
|
func FromRaw(rc *RecordConfig, origin string, typeName string, args []string, meta map[string]string) error {
|
|
|
|
|
|
|
|
rt, ok := rtypeDB[typeName]
|
|
|
|
if !ok {
|
2025-01-15 08:39:25 +08:00
|
|
|
return fmt.Errorf("unknown (FromRaw) rtype %q", typeName)
|
2025-01-10 23:43:16 +08:00
|
|
|
}
|
|
|
|
|
2025-02-19 21:42:34 +08:00
|
|
|
return rt.PopulateFromRaw(rc, args, meta, effectiveOrigin(rc.SubDomain, origin))
|
2025-01-10 23:43:16 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// CheckAndFixImport checks the records for any that were created with a
|
|
|
|
// provider that has not yet been upgraded. In theory leaving providers in the
|
|
|
|
// legacy state should not cause any issues, but it is a good idea to fix them
|
|
|
|
// as soon as possible.
|
|
|
|
func CheckAndFixImport(recs []*RecordConfig, origin string) bool {
|
|
|
|
found := false
|
|
|
|
for _, rec := range recs {
|
|
|
|
//fmt.Printf("DEBUG: Found record %s %s %v\n", rec.Type, rec.Name, rec)
|
|
|
|
// Was this created wrong?
|
|
|
|
if IsTypeUpgraded(rec.Type) && rec.Fields == nil {
|
|
|
|
found = true
|
|
|
|
log.Warnf("LEGACY PROVIDER needs fixing! Created invalid record: %s %s %v\n", rec.Type, rec.Name, rec)
|
2025-01-15 08:39:25 +08:00
|
|
|
if err := rec.ImportFromLegacy(origin); err != nil {
|
|
|
|
log.Warnf("Error fixing record: %s %s %v: %v\n", rec.Type, rec.Name, rec, err)
|
|
|
|
}
|
2025-01-10 23:43:16 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return found
|
|
|
|
}
|
|
|
|
|
2025-01-15 08:39:25 +08:00
|
|
|
// MustImportFromLegacy is like ImportFromLegacy but panics on error. Use only in tests and init() functions.
|
|
|
|
func (rc *RecordConfig) MustImportFromLegacy(origin string) {
|
|
|
|
if err := rc.ImportFromLegacy(origin); err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-01-10 23:43:16 +08:00
|
|
|
// TransformRawRecords converts the RawRecordConfigs from dnsconfig.js into RecordConfig.
|
|
|
|
func TransformRawRecords(domains []*DomainConfig) error {
|
|
|
|
|
|
|
|
for _, dc := range domains {
|
|
|
|
|
|
|
|
for _, rawRec := range dc.RawRecords {
|
|
|
|
|
|
|
|
if rawRec.TTL == 0 {
|
|
|
|
rawRec.TTL = dc.DefaultTTL
|
|
|
|
}
|
|
|
|
|
|
|
|
rec := &RecordConfig{
|
|
|
|
Type: rawRec.Type,
|
|
|
|
TTL: rawRec.TTL,
|
|
|
|
SubDomain: rawRec.SubDomain,
|
|
|
|
Metadata: map[string]string{},
|
|
|
|
}
|
|
|
|
|
|
|
|
// Copy the metadata (convert values to string)
|
|
|
|
for _, m := range rawRec.Metadata {
|
|
|
|
for mk, mv := range m {
|
|
|
|
if v, ok := mv.(string); ok {
|
|
|
|
rec.Metadata[mk] = v // Already a string
|
|
|
|
} else {
|
|
|
|
rec.Metadata[mk] = fmt.Sprintf("%v", mv)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
rt, ok := rtypeDB[rawRec.Type]
|
|
|
|
if !ok {
|
2025-01-15 08:39:25 +08:00
|
|
|
return fmt.Errorf("unknown (TRR) rtype %q", rawRec.Type)
|
2025-01-10 23:43:16 +08:00
|
|
|
}
|
|
|
|
|
2025-02-19 21:42:34 +08:00
|
|
|
err := rt.PopulateFromRaw(rec, rawRec.Args, rec.Metadata, effectiveOrigin(rec.SubDomain, dc.Name))
|
2025-01-10 23:43:16 +08:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("%s (%q, dom=%q) record error: %w",
|
|
|
|
rawRec.Type,
|
|
|
|
rec.Name,
|
|
|
|
dc.Name,
|
|
|
|
err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Free memeory:
|
|
|
|
clear(rawRec.Args)
|
|
|
|
rawRec.Args = nil
|
|
|
|
|
|
|
|
if rawRec.EnsureAbsent {
|
|
|
|
dc.EnsureAbsent = append(dc.EnsureAbsent, rec)
|
|
|
|
} else {
|
|
|
|
dc.Records = append(dc.Records, rec)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
dc.RawRecords = nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
2025-02-19 21:42:34 +08:00
|
|
|
|
|
|
|
// effectiveOrigin returns the effective origin given a "subdomain" and an
|
|
|
|
// "origin". The concept of a subdomain is only relevant in dnsconfig.js and
|
|
|
|
// RawRecordConfig. In the RecordConfig, the "Name" field is the full name
|
|
|
|
// (minor the dc.Name) and any .Target or other fields are FQDNs or relative to
|
|
|
|
// the effective origin.
|
|
|
|
func effectiveOrigin(sub, origin string) string {
|
|
|
|
if sub == "" {
|
|
|
|
return origin
|
|
|
|
}
|
|
|
|
return sub + "." + origin
|
|
|
|
}
|