package dnscfg import ( "encoding/json" "os" "path/filepath" "strings" ) func NormalizeResolverMode(mode string, viaSmartDNS bool, directMode string, smartDNSMode string, hybridWildcardMode string) string { switch strings.ToLower(strings.TrimSpace(mode)) { case strings.ToLower(strings.TrimSpace(directMode)): return directMode case strings.ToLower(strings.TrimSpace(smartDNSMode)): return hybridWildcardMode case strings.ToLower(strings.TrimSpace(hybridWildcardMode)), "hybrid": return hybridWildcardMode default: if viaSmartDNS { return hybridWildcardMode } return directMode } } func SmartDNSForced(envRaw string) bool { switch strings.TrimSpace(strings.ToLower(envRaw)) { case "1", "true", "yes", "on": return true default: return false } } type ModeState struct { ViaSmartDNS bool `json:"via_smartdns"` SmartDNSAddr string `json:"smartdns_addr"` Mode string `json:"mode"` } type ModeConfig struct { Path string DirectMode string DefaultSmartDNSAddr string NormalizeResolverMode func(mode string, viaSmartDNS bool) string NormalizeSmartDNSAddr func(raw string) string } func LoadMode(cfg ModeConfig) (ModeState, bool) { mode := ModeState{ ViaSmartDNS: false, SmartDNSAddr: strings.TrimSpace(cfg.DefaultSmartDNSAddr), Mode: strings.TrimSpace(cfg.DirectMode), } needPersist := false data, err := os.ReadFile(strings.TrimSpace(cfg.Path)) switch { case err == nil: var stored ModeState if err := json.Unmarshal(data, &stored); err == nil { normalized, changed := normalizeModeState(stored, cfg) mode = normalized if strings.TrimSpace(stored.Mode) == "" || stored.ViaSmartDNS != normalized.ViaSmartDNS || changed { needPersist = true } } else { needPersist = true } case os.IsNotExist(err): needPersist = true } normalized, changed := normalizeModeState(mode, cfg) mode = normalized if changed { needPersist = true } return mode, needPersist } func SaveMode(cfg ModeConfig, mode ModeState) error { normalized, _ := normalizeModeState(mode, cfg) path := strings.TrimSpace(cfg.Path) if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { return err } tmp := path + ".tmp" b, err := json.MarshalIndent(normalized, "", " ") if err != nil { return err } if err := os.WriteFile(tmp, b, 0o644); err != nil { return err } return os.Rename(tmp, path) } func normalizeModeState(mode ModeState, cfg ModeConfig) (ModeState, bool) { changed := false prevMode := mode.Mode mode.Mode = normalizeResolverMode(cfg, mode.Mode, mode.ViaSmartDNS) if mode.Mode != prevMode { changed = true } viaSmartDNS := mode.Mode != strings.TrimSpace(cfg.DirectMode) if mode.ViaSmartDNS != viaSmartDNS { mode.ViaSmartDNS = viaSmartDNS changed = true } prevAddr := mode.SmartDNSAddr mode.SmartDNSAddr = normalizeSmartDNSAddr(cfg, mode.SmartDNSAddr) if mode.SmartDNSAddr == "" { mode.SmartDNSAddr = strings.TrimSpace(cfg.DefaultSmartDNSAddr) } if mode.SmartDNSAddr != prevAddr { changed = true } return mode, changed } func normalizeResolverMode(cfg ModeConfig, mode string, viaSmartDNS bool) string { if cfg.NormalizeResolverMode == nil { return strings.TrimSpace(mode) } return strings.TrimSpace(cfg.NormalizeResolverMode(mode, viaSmartDNS)) } func normalizeSmartDNSAddr(cfg ModeConfig, raw string) string { if cfg.NormalizeSmartDNSAddr == nil { return strings.TrimSpace(raw) } return strings.TrimSpace(cfg.NormalizeSmartDNSAddr(raw)) }