~bigbes/shroud

ref: 3804bcf465a4eb00d1f03720316d0a176b0b97fb shroud/internal/config/config.go -rw-r--r-- 8.4 KiB
3804bcf4 — Eugene Blikh feat(vless): add VLESS+REALITY transport support 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
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
package config

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

	"sourcecraft.dev/bigbes/shroud/internal/mmdb"

	"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"`
	// VLESS over REALITY transport settings.
	VLESS VLESSConfig `yaml:"vless"`
	// ACME (Let's Encrypt) certificate settings.
	ACME ACMEConfig `yaml:"acme"`
	// 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"`
	// Hostname for AWG client endpoint. If empty and muxer is enabled, falls back to server.hostname.
	Hostname string `yaml:"hostname"`

	// HTTP/3 + AWG muxer on the same UDP port.
	// When enabled, AWG and QUIC/HTTP3 share the listen port.
	// nil = auto (enabled when domain is set), true = force on, false = force off.
	MuxEnabled *bool `yaml:"mux_enabled"`

	// HTTP/3 cover for DPI resistance.
	// Domain for Let's Encrypt certificate. Defaults to server.hostname if empty.
	// If still empty after defaulting, HTTP/3 server is disabled.
	Domain string `yaml:"domain"`

	// 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 VLESSConfig struct {
	// Enable VLESS over REALITY transport.
	Enabled bool `yaml:"enabled"`
	// Listen address for the REALITY listener (e.g., ":443").
	ListenAddr string `yaml:"listen_addr"`
	// Accepted SNIs for REALITY handshake (e.g., ["www.microsoft.com"]).
	ServerNames []string `yaml:"server_names"`
	// Decoy forward target for unauthenticated probes (e.g., "www.microsoft.com:443").
	Dest string `yaml:"dest"`
	// Enable debug logging for REALITY handshakes.
	Show bool `yaml:"show"`
}

type ACMEConfig struct {
	// Directory for cached TLS certificates.
	CertCache string `yaml:"cert_cache"`
	// HTTP port for ACME HTTP-01 challenges.
	HTTPPort int `yaml:"http_port"`
}

type ShadowsocksConfig struct {
	// Enable Shadowsocks protocol. Defaults to true.
	Enabled *bool `yaml:"enabled"`
	// 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"`
	// URL or local path to IP-to-country MaxMind MMDB file.
	// Defaults to GeoLite2-Country.mmdb from github.com/P3TERX/GeoLite.mmdb.
	// Set to "none" to disable.
	IPCountryDB string `yaml:"ip_country_db"`
	// URL or local path to IP-to-ASN MaxMind MMDB file.
	// Defaults to GeoLite2-ASN.mmdb from github.com/P3TERX/GeoLite.mmdb.
	// Set to "none" to disable.
	IPASNDB string `yaml:"ip_asn_db"`
	// Directory to cache downloaded MMDB files.
	// Defaults to "/var/lib/shroud/mmdb".
	IPDBCacheDir string `yaml:"ip_db_cache_dir"`
	// Auto-update MMDB files daily from URLs. Defaults to true.
	IPDBAutoUpdate *bool `yaml:"ip_db_auto_update"`
}

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.Enabled == nil {
		t := true
		c.Shadowsocks.Enabled = &t
	}
	if c.Shadowsocks.DefaultCipher == "" {
		c.Shadowsocks.DefaultCipher = "chacha20-ietf-poly1305"
	}
	if c.Shadowsocks.NATTimeout == "" {
		c.Shadowsocks.NATTimeout = "5m"
	}
	if c.Shadowsocks.IPCountryDB == "" {
		c.Shadowsocks.IPCountryDB = mmdb.DefaultCountryURL
	}
	if c.Shadowsocks.IPASNDB == "" {
		c.Shadowsocks.IPASNDB = mmdb.DefaultASNURL
	}
	if c.Shadowsocks.IPDBAutoUpdate == nil {
		t := true
		c.Shadowsocks.IPDBAutoUpdate = &t
	}
	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
		}
	}
	// VLESS defaults.
	if c.VLESS.Enabled {
		if c.VLESS.ListenAddr == "" {
			c.VLESS.ListenAddr = ":443"
		}
	}
	// AmneziaWG domain defaults to server hostname.
	if c.AmneziaWG.Enabled && c.AmneziaWG.Domain == "" {
		c.AmneziaWG.Domain = c.Server.Hostname
	}
	// ACME defaults.
	if c.ACME.CertCache == "" {
		c.ACME.CertCache = "/var/lib/shroud/certs"
	}
	if c.ACME.HTTPPort == 0 {
		c.ACME.HTTPPort = 80
	}
}

// AWGMuxEnabled returns whether the AWG+HTTP/3 muxer is enabled.
// If MuxEnabled is explicitly set, that value is used.
// Otherwise, muxer is enabled when a domain is configured.
func (c *AmneziaWGConfig) AWGMuxEnabled() bool {
	if c.MuxEnabled != nil {
		return *c.MuxEnabled
	}
	return c.Domain != ""
}

// AWGHostname returns the hostname for AWG client endpoints.
// Uses awg.hostname if set; falls back to serverHostname when muxer is enabled.
func (c *AmneziaWGConfig) AWGHostname(serverHostname string) string {
	if c.Hostname != "" {
		return c.Hostname
	}
	if c.AWGMuxEnabled() {
		return serverHostname
	}
	return ""
}

// MMDBConfig returns the MMDB manager configuration derived from shadowsocks settings.
// Sources set to "none" are treated as disabled (empty).
func (c *ShadowsocksConfig) MMDBConfig() mmdb.Config {
	country := c.IPCountryDB
	if country == "none" {
		country = ""
	}
	asn := c.IPASNDB
	if asn == "none" {
		asn = ""
	}
	autoUpdate := true
	if c.IPDBAutoUpdate != nil {
		autoUpdate = *c.IPDBAutoUpdate
	}
	return mmdb.Config{
		CountryURL: country,
		ASNURL:     asn,
		CacheDir:   c.IPDBCacheDir,
		AutoUpdate: autoUpdate,
	}
}

// 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)
}