- Move instance-related structs and functions to config.go - Remove serverAddr, serverPort, and authMethod from database schema - Implement new config parsing and encoding with nested key support - Update service management to use instanceID instead of username/name - Add GetServiceNameByInstanceID helper function - Update API documentation for auth.method field change
351 lines
8.5 KiB
Go
351 lines
8.5 KiB
Go
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 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
|
|
}
|