~bigbes/shroud

ref: ca7efb0f8ff6e45da70363dcde1608ac92cd46d4 shroud/internal/config/config.go -rw-r--r-- 5.0 KiB
ca7efb0f — Eugene Blikh Initial commit: Shadowsocks + AmneziaWG VPN server 2 months ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
package config

import (
	"fmt"
	"os"
	"path/filepath"

	"gopkg.in/yaml.v3"
)

type Config struct {
	// Server identification and display.
	Server ServerConfig `yaml:"server"`
	// REST API settings.
	API APIConfig `yaml:"api"`
	// Prometheus metrics endpoint settings.
	Metrics MetricsConfig `yaml:"metrics"`
	// Shadowsocks proxy settings.
	Shadowsocks ShadowsocksConfig `yaml:"shadowsocks"`
	// AmneziaWG VPN settings.
	AmneziaWG AmneziaWGConfig `yaml:"amneziawg"`
	// Path to persistent state file.
	// Defaults to "state.yaml" in the same directory as the config file.
	State string `yaml:"state_file"`

	// configDir is the directory containing the config file, used for resolving relative paths.
	configDir string
}

type ServerConfig struct {
	// Human-readable server name.
	Name string `yaml:"name"`
	// Unique server identifier (auto-generated if empty).
	ID string `yaml:"id"`
	// Hostname or IP used in access key URLs.
	Hostname string `yaml:"hostname"`
}

type APIConfig struct {
	// Listen address for the management API (e.g., ":8081").
	ListenAddr string `yaml:"listen_addr"`
	// Secret prefix for URL-based authentication (e.g., "SecretPath").
	// API will be served at /<secret>/...
	Secret string `yaml:"secret"`
	// Path to TLS certificate file (optional, used for certSha256 in access.yaml).
	CertFile string `yaml:"cert_file"`
}

type MetricsConfig struct {
	// Listen address for the Prometheus /metrics and /healthz endpoint.
	// Defaults to "127.0.0.1:8081".
	ListenAddr string `yaml:"listen_addr"`
	// Node exporter collectors to enable (e.g., [cpu, meminfo, diskstats]).
	// Empty means no node_exporter collectors.
	NodeExporterCollectors []string `yaml:"node_exporter_collectors"`
}

type AmneziaWGConfig struct {
	// Enable AmneziaWG protocol.
	Enabled bool `yaml:"enabled"`
	// UDP listen port for AWG + HTTP/3 mux.
	ListenPort int `yaml:"listen_port"`
	// TUN device name.
	TUNName string `yaml:"tun_name"`
	// Server subnet in CIDR notation (e.g., "10.14.0.0/24").
	// Server takes the first usable IP (.1), peers are allocated from .2 onwards.
	Address string `yaml:"address"`
	// TUN MTU.
	MTU int `yaml:"mtu"`
	// Server interface private key (base64). Auto-generated if empty.
	PrivateKey string `yaml:"private_key"`
	// DNS server for client configs.
	DNS string `yaml:"dns"`

	// HTTP/3 cover for DPI resistance.
	// Domain for Let's Encrypt certificate. If empty, HTTP/3 server is disabled.
	Domain string `yaml:"domain"`
	// Directory for cached TLS certificates.
	CertCache string `yaml:"cert_cache"`
	// HTTP port for ACME HTTP-01 challenges.
	ACMEHTTPPort int `yaml:"acme_http_port"`

	// Obfuscation parameters.
	Jc   int `yaml:"jc"`
	Jmin int `yaml:"jmin"`
	Jmax int `yaml:"jmax"`
	S1   int `yaml:"s1"`
	S2   int `yaml:"s2"`
	S3   int `yaml:"s3"`
	S4   int `yaml:"s4"`
	H1   string `yaml:"h1"` // "min-max" or single value
	H2   string `yaml:"h2"`
	H3   string `yaml:"h3"`
	H4   string `yaml:"h4"`
}

type ShadowsocksConfig struct {
	// Default port for new access keys. 0 means pick a random unused port on first start.
	DefaultPort int `yaml:"default_port"`
	// Default cipher for new access keys.
	DefaultCipher string `yaml:"default_cipher"`
	// UDP NAT timeout as a duration string (e.g., "5m").
	NATTimeout string `yaml:"nat_timeout"`
	// Replay protection history size (0 = disabled).
	ReplayHistory int `yaml:"replay_history"`
	// Path to IP-to-country MaxMind MMDB file.
	IPCountryDB string `yaml:"ip_country_db"`
	// Path to IP-to-ASN MaxMind MMDB file.
	IPASNDB string `yaml:"ip_asn_db"`
}

func Load(filename string) (*Config, error) {
	absPath, err := filepath.Abs(filename)
	if err != nil {
		return nil, fmt.Errorf("resolving config path: %w", err)
	}

	data, err := os.ReadFile(absPath)
	if err != nil {
		return nil, fmt.Errorf("reading config file: %w", err)
	}
	cfg := &Config{
		configDir: filepath.Dir(absPath),
	}
	if err := yaml.Unmarshal(data, cfg); err != nil {
		return nil, fmt.Errorf("parsing config: %w", err)
	}
	cfg.setDefaults()
	return cfg, nil
}

func (c *Config) setDefaults() {
	if c.API.ListenAddr == "" {
		c.API.ListenAddr = ":8081"
	}
	if c.Metrics.ListenAddr == "" {
		c.Metrics.ListenAddr = "127.0.0.1:8081"
	}
	if c.Shadowsocks.DefaultCipher == "" {
		c.Shadowsocks.DefaultCipher = "chacha20-ietf-poly1305"
	}
	if c.Shadowsocks.NATTimeout == "" {
		c.Shadowsocks.NATTimeout = "5m"
	}
	if c.Server.Name == "" {
		c.Server.Name = "Outline Server"
	}
	if c.State == "" {
		c.State = "state.yaml"
	}
	// AmneziaWG defaults.
	if c.AmneziaWG.Enabled {
		if c.AmneziaWG.ListenPort == 0 {
			c.AmneziaWG.ListenPort = 443
		}
		if c.AmneziaWG.TUNName == "" {
			c.AmneziaWG.TUNName = "awg0"
		}
		if c.AmneziaWG.MTU == 0 {
			c.AmneziaWG.MTU = 1420
		}
		if c.AmneziaWG.CertCache == "" {
			c.AmneziaWG.CertCache = "/var/lib/outline-distro/certs"
		}
		if c.AmneziaWG.ACMEHTTPPort == 0 {
			c.AmneziaWG.ACMEHTTPPort = 80
		}
	}
}

// StateFile returns the absolute path to the state file.
func (c *Config) StateFile() string {
	if filepath.IsAbs(c.State) {
		return c.State
	}
	return filepath.Join(c.configDir, c.State)
}