package main import ( "encoding/json" "errors" "fmt" "os" "strconv" "strings" "github.com/BurntSushi/toml" ) type Config struct { ListenAddr string `json:"listenAddr"` ListenPort string `json:"listenPort"` FrpcPath string `json:"frpcPath"` InstancePath string `json:"instancePath"` Debug bool `json:"debug"` } type InstanceInfo struct { Name string `json:"name"` ServerAddr string `json:"serverAddr"` ServerPort string `json:"serverPort"` AuthMethod string `json:"auth_method"` RunUser string `json:"runUser"` Additional map[string]interface{} `json:"additionalProperties"` } type FrpcProxyInfo struct { Name string `json:"name"` Type string `json:"type"` LocalIP string `json:"local_ip"` LocalPort string `json:"local_port"` RemotePort string `json:"remote_port"` } type CreateInstanceRequest struct { InstanceInfo InstanceInfo `json:"instanceInfo"` BootAtStart bool `json:"bootAtStart"` RunUser string `json:"runUser"` Additional map[string]interface{} `json:"additionalProperties"` } type CreateProxyRequest struct { InstanceID string `json:"instanceID"` ProxyInfo FrpcProxyInfo `json:"proxyInfo"` } type FrpcConfig struct { Global map[string]interface{} `toml:"-"` Proxies []map[string]interface{} `toml:"proxies"` } var globalConfig *Config func LoadConfig(configPath string) (*Config, error) { data, err := os.ReadFile(configPath) if err != nil { return nil, fmt.Errorf("failed to read config file: %w", err) } var config Config if err := json.Unmarshal(data, &config); err != nil { return nil, fmt.Errorf("failed to parse config file: %w", err) } if config.ListenAddr == "" { config.ListenAddr = "0.0.0.0" } if config.ListenPort == "" { config.ListenPort = "8080" } if config.FrpcPath == "" { config.FrpcPath = "/usr/bin/frpc" } if config.InstancePath == "" { config.InstancePath = "./configs" } if err := os.MkdirAll(config.InstancePath, 0755); err != nil { return nil, fmt.Errorf("failed to create config directory: %w", err) } globalConfig = &config return &config, nil } func GetConfig() (*Config, error) { if globalConfig == nil { return nil, errors.New("config not loaded") } return globalConfig, nil } func SaveConfig(configPath string, config *Config) error { data, err := json.MarshalIndent(config, "", " ") if err != nil { return fmt.Errorf("failed to marshal config: %w", err) } if err := os.WriteFile(configPath, data, 0644); err != nil { return fmt.Errorf("failed to write config file: %w", err) } globalConfig = config return nil } func decodeFrpcConfig(configContent string) (FrpcConfig, error) { var config FrpcConfig var rawMap map[string]interface{} meta, err := toml.Decode(configContent, &rawMap) if err != nil { return config, fmt.Errorf("failed to parse config: %w", err) } config.Global = make(map[string]interface{}) config.Proxies = make([]map[string]interface{}, 0) getNestedValue := func(m map[string]interface{}, key string) (interface{}, bool) { parts := strings.Split(key, ".") current := m for i, part := range parts { if i == len(parts)-1 { val, ok := current[part] return val, ok } next, ok := current[part].(map[string]interface{}) if !ok { return nil, false } current = next } return nil, false } for _, key := range meta.Keys() { keyStr := key.String() if keyStr == "proxies" { if proxies, ok := rawMap["proxies"].([]map[string]interface{}); ok { config.Proxies = proxies } else if proxies, ok := rawMap["proxies"].([]interface{}); ok { for _, p := range proxies { if pm, ok := p.(map[string]interface{}); ok { config.Proxies = append(config.Proxies, pm) } } } } else if !strings.HasPrefix(keyStr, "proxies.") { if val, ok := getNestedValue(rawMap, keyStr); ok { config.Global[keyStr] = val } } } return config, nil } func encodeFrpcConfig(config FrpcConfig) (string, error) { var buf strings.Builder if len(config.Global) > 0 { for key, value := range config.Global { switch v := value.(type) { case string: buf.WriteString(fmt.Sprintf("%s = %q\n", key, v)) case int: buf.WriteString(fmt.Sprintf("%s = %d\n", key, v)) case int64: buf.WriteString(fmt.Sprintf("%s = %d\n", key, v)) case float64: buf.WriteString(fmt.Sprintf("%s = %v\n", key, v)) case bool: buf.WriteString(fmt.Sprintf("%s = %v\n", key, v)) default: data, err := json.Marshal(v) if err != nil { return "", fmt.Errorf("failed to marshal value for key %s: %w", key, err) } buf.WriteString(fmt.Sprintf("%s = %s\n", key, string(data))) } } buf.WriteString("\n") } if len(config.Proxies) > 0 { for _, proxy := range config.Proxies { buf.WriteString("[[proxies]]\n") for key, value := range proxy { switch v := value.(type) { case string: buf.WriteString(fmt.Sprintf("%s = %q\n", key, v)) case int: buf.WriteString(fmt.Sprintf("%s = %d\n", key, v)) case int64: buf.WriteString(fmt.Sprintf("%s = %d\n", key, v)) case float64: buf.WriteString(fmt.Sprintf("%s = %v\n", key, v)) case bool: buf.WriteString(fmt.Sprintf("%s = %v\n", key, v)) default: data, err := json.Marshal(v) if err != nil { return "", fmt.Errorf("failed to marshal value for key %s: %w", key, err) } buf.WriteString(fmt.Sprintf("%s = %s\n", key, string(data))) } } buf.WriteString("\n") } } return strings.TrimSuffix(buf.String(), "\n"), nil } func generateFrpcConfig(info InstanceInfo) string { config := FrpcConfig{ Global: make(map[string]interface{}), } config.Global["serverAddr"] = info.ServerAddr config.Global["serverPort"] = info.ServerPort config.Global["auth.method"] = info.AuthMethod for key, value := range info.Additional { config.Global[key] = value } result, err := encodeFrpcConfig(config) if err != nil { return "" } return result } func addFrpcProxy(configContent string, info FrpcProxyInfo) (string, error) { config, err := decodeFrpcConfig(configContent) if err != nil { return "", fmt.Errorf("failed to parse config: %w", err) } proxy := map[string]interface{}{ "name": info.Name, "type": info.Type, "localIP": info.LocalIP, "localPort": info.LocalPort, "remotePort": info.RemotePort, } config.Proxies = append(config.Proxies, proxy) result, err := encodeFrpcConfig(config) if err != nil { return "", fmt.Errorf("failed to write config: %w", err) } return result, nil } func removeFrpcProxy(configContent string, proxyName string) (string, error) { config, err := decodeFrpcConfig(configContent) if err != nil { return "", fmt.Errorf("failed to parse config: %w", err) } var found bool var newProxies []map[string]interface{} for _, proxy := range config.Proxies { if name, ok := proxy["name"].(string); ok && name == proxyName { found = true continue } newProxies = append(newProxies, proxy) } if !found { return "", fmt.Errorf("proxy %s not found", proxyName) } config.Proxies = newProxies result, err := encodeFrpcConfig(config) if err != nil { return "", fmt.Errorf("failed to write config: %w", err) } return result, nil } func modifyFrpcProxy(configContent string, info FrpcProxyInfo) (string, error) { config, err := decodeFrpcConfig(configContent) if err != nil { return "", fmt.Errorf("failed to parse config: %w", err) } var found bool for i, proxy := range config.Proxies { if name, ok := proxy["name"].(string); ok && name == info.Name { config.Proxies[i] = map[string]interface{}{ "name": info.Name, "type": info.Type, "localIP": info.LocalIP, "localPort": info.LocalPort, "remotePort": info.RemotePort, } found = true break } } if !found { return "", fmt.Errorf("proxy %s not found", info.Name) } result, err := encodeFrpcConfig(config) if err != nil { return "", fmt.Errorf("failed to write config: %w", err) } return result, nil } func getKeyText(configPath, key, section string) (value string, err error) { configContent, err := os.ReadFile(configPath) if err != nil { return "", fmt.Errorf("failed to read config file: %w", err) } config, err := decodeFrpcConfig(string(configContent)) if err != nil { return "", fmt.Errorf("failed to parse config: %w", err) } if config.Global == nil { return "", fmt.Errorf("config file has no global fields") } if v, ok := config.Global[key]; ok { value = fmt.Sprintf("%v", v) } return value, nil } func setKeyText(configPath, key, section string, value string) error { configContent, err := os.ReadFile(configPath) if err != nil { return fmt.Errorf("failed to read config file: %w", err) } config, err := decodeFrpcConfig(string(configContent)) if err != nil { return fmt.Errorf("failed to parse config: %w", err) } if config.Global == nil { config.Global = make(map[string]interface{}) } if intVal, err := strconv.Atoi(value); err == nil { config.Global[key] = intVal } else { config.Global[key] = value } result, err := encodeFrpcConfig(config) if err != nil { return fmt.Errorf("failed to write config: %w", err) } if err := os.WriteFile(configPath, []byte(result), 0644); err != nil { return fmt.Errorf("failed to write config file: %w", err) } return nil }