tg-ws-proxy-go/internal/config/config.go

163 lines
3.6 KiB
Go
Raw Permalink Normal View History

2026-03-22 19:39:24 +03:00
// Package config provides configuration management.
package config
import (
"encoding/json"
"net"
"os"
"path/filepath"
"runtime"
"strconv"
"strings"
)
// Config holds the proxy configuration.
type Config struct {
Port int `json:"port"`
Host string `json:"host"`
DCIP []string `json:"dc_ip"`
Verbose bool `json:"verbose"`
AutoStart bool `json:"autostart"`
LogMaxMB float64 `json:"log_max_mb"`
BufKB int `json:"buf_kb"`
PoolSize int `json:"pool_size"`
Auth string `json:"auth"` // username:password
UpstreamProxy string `json:"upstream_proxy"`
2026-03-22 19:39:24 +03:00
}
// DefaultConfig returns the default configuration.
func DefaultConfig() *Config {
return &Config{
Port: 1080,
Host: "127.0.0.1",
DCIP: []string{"2:149.154.167.220", "4:149.154.167.220"},
Verbose: false,
AutoStart: false,
LogMaxMB: 5,
BufKB: 256,
PoolSize: 4,
}
}
// GetConfigDir returns the configuration directory for the current OS.
func GetConfigDir() (string, error) {
switch runtime.GOOS {
case "windows":
appData := os.Getenv("APPDATA")
if appData == "" {
home, err := os.UserHomeDir()
if err != nil {
return "", err
}
appData = home
}
return filepath.Join(appData, "TgWsProxy"), nil
case "darwin":
home, err := os.UserHomeDir()
if err != nil {
return "", err
}
return filepath.Join(home, "Library", "Application Support", "TgWsProxy"), nil
default: // Linux and others
xdgConfig := os.Getenv("XDG_CONFIG_HOME")
if xdgConfig != "" {
return filepath.Join(xdgConfig, "TgWsProxy"), nil
}
home, err := os.UserHomeDir()
if err != nil {
return "", err
}
return filepath.Join(home, ".config", "TgWsProxy"), nil
}
}
// Load loads configuration from file.
func Load() (*Config, error) {
dir, err := GetConfigDir()
if err != nil {
return DefaultConfig(), nil
}
configPath := filepath.Join(dir, "config.json")
data, err := os.ReadFile(configPath)
if err != nil {
if os.IsNotExist(err) {
return DefaultConfig(), nil
}
return DefaultConfig(), nil
}
cfg := DefaultConfig()
if err := json.Unmarshal(data, cfg); err != nil {
return DefaultConfig(), nil
}
// Ensure defaults for missing fields
if cfg.Port == 0 {
cfg.Port = 1080
}
if cfg.Host == "" {
cfg.Host = "127.0.0.1"
}
if len(cfg.DCIP) == 0 {
cfg.DCIP = []string{"2:149.154.167.220", "4:149.154.167.220"}
}
return cfg, nil
}
// Save saves configuration to file.
func (c *Config) Save() error {
dir, err := GetConfigDir()
if err != nil {
return err
}
if err := os.MkdirAll(dir, 0755); err != nil {
return err
}
configPath := filepath.Join(dir, "config.json")
data, err := json.MarshalIndent(c, "", " ")
if err != nil {
return err
}
return os.WriteFile(configPath, data, 0644)
}
// ParseDCIPList parses a list of "DC:IP" strings into a map.
func ParseDCIPList(dcIPList []string) (map[int]string, error) {
result := make(map[int]string)
for _, entry := range dcIPList {
if !strings.Contains(entry, ":") {
return nil, ErrInvalidDCIPFormat{Entry: entry}
}
parts := strings.SplitN(entry, ":", 2)
dcStr, ipStr := parts[0], parts[1]
dc, err := strconv.Atoi(dcStr)
if err != nil {
return nil, ErrInvalidDCIPFormat{Entry: entry}
}
if net.ParseIP(ipStr) == nil {
return nil, ErrInvalidDCIPFormat{Entry: entry}
}
result[dc] = ipStr
}
return result, nil
}
// ErrInvalidDCIPFormat is returned when DC:IP format is invalid.
type ErrInvalidDCIPFormat struct {
Entry string
}
func (e ErrInvalidDCIPFormat) Error() string {
return "invalid --dc-ip format " + strconv.Quote(e.Entry) + ", expected DC:IP"
}