package configs import ( "errors" "os" "path/filepath" "github.com/iwind/TeaGo/Tea" "gopkg.in/yaml.v3" ) const ConfigFileName = "api_httpdns.yaml" const oldConfigFileName = "api.yaml" var sharedAPIConfig *APIConfig type APIConfig struct { OldRPC struct { Endpoints []string `yaml:"endpoints"` DisableUpdate bool `yaml:"disableUpdate"` } `yaml:"rpc,omitempty"` RPCEndpoints []string `yaml:"rpc.endpoints,flow" json:"rpc.endpoints"` RPCDisableUpdate bool `yaml:"rpc.disableUpdate" json:"rpc.disableUpdate"` NodeId string `yaml:"nodeId"` Secret string `yaml:"secret"` ListenAddr string `yaml:"listenAddr"` HTTPSListenAddr string `yaml:"https.listenAddr" json:"https.listenAddr"` HTTPSCert string `yaml:"https.cert" json:"https.cert"` HTTPSKey string `yaml:"https.key" json:"https.key"` LogDir string `yaml:"logDir" json:"logDir"` } func SharedAPIConfig() (*APIConfig, error) { if sharedAPIConfig != nil { return sharedAPIConfig, nil } config, err := LoadAPIConfig() if err != nil { return nil, err } sharedAPIConfig = config return config, nil } func LoadAPIConfig() (*APIConfig, error) { for _, filename := range []string{ConfigFileName, oldConfigFileName} { var loadedData []byte var loadedPath string for _, candidate := range findConfigCandidates(filename) { data, err := os.ReadFile(candidate) if err != nil { if os.IsNotExist(err) { continue } return nil, err } loadedData = data loadedPath = candidate break } if len(loadedData) == 0 { if filename == oldConfigFileName { continue } return nil, errors.New("no config file '" + ConfigFileName + "' found") } config := &APIConfig{} err := yaml.Unmarshal(loadedData, config) if err != nil { return nil, err } err = config.Init() if err != nil { return nil, err } if filename == oldConfigFileName { config.OldRPC.Endpoints = nil // 优先写回当前旧配置所在目录,避免依赖启动时工作目录 _ = config.WriteFile(filepath.Join(filepath.Dir(loadedPath), ConfigFileName)) } return config, nil } return nil, errors.New("no config file '" + ConfigFileName + "' found") } func (c *APIConfig) Init() error { if len(c.RPCEndpoints) == 0 && len(c.OldRPC.Endpoints) > 0 { c.RPCEndpoints = c.OldRPC.Endpoints c.RPCDisableUpdate = c.OldRPC.DisableUpdate } if len(c.RPCEndpoints) == 0 { return errors.New("no valid 'rpc.endpoints'") } if len(c.NodeId) == 0 { return errors.New("'nodeId' required") } if len(c.Secret) == 0 { return errors.New("'secret' required") } // 兼容旧配置中的 listenAddr if len(c.HTTPSListenAddr) == 0 { if len(c.ListenAddr) > 0 { c.HTTPSListenAddr = c.ListenAddr } else { c.HTTPSListenAddr = ":8443" } } if len(c.HTTPSCert) == 0 { // return errors.New("'https.cert' required") } if len(c.HTTPSKey) == 0 { // return errors.New("'https.key' required") } return nil } func (c *APIConfig) WriteFile(path string) error { data, err := yaml.Marshal(c) if err != nil { return err } return os.WriteFile(path, data, 0666) } func findConfigCandidates(filename string) []string { candidates := []string{ Tea.ConfigFile(filename), Tea.ConfigFile(filepath.Join("configs", filename)), filename, filepath.Join("configs", filename), } if exePath, err := os.Executable(); err == nil { exeDir := filepath.Dir(exePath) candidates = append(candidates, filepath.Join(exeDir, filename), filepath.Join(exeDir, "configs", filename), filepath.Join(exeDir, "..", "configs", filename), ) } uniq := map[string]struct{}{} result := make([]string, 0, len(candidates)) for _, candidate := range candidates { candidate = filepath.Clean(candidate) if len(candidate) == 0 { continue } if _, ok := uniq[candidate]; ok { continue } uniq[candidate] = struct{}{} result = append(result, candidate) } return result }