Files
OpenFlare/internal/apps/relay/config/config.go
T
ryan aa4faddade 补齐 131 个 .go 文件的 SPDX license 头(repo 自带 make license 约定,早于约定新增的文件含 2 个生产文件;纯注释插入零行为影响),make license-check 转绿。go mod tidy -diff 确认干净。
Result: {"status":"keep","total_issues":8,"golint_canonicalheader":0,"golint_errname":0,"golint_errorlint":1,"golint_exhaustive":0,"golint_forcetypeassert":0,"golint_gosec":0,"golint_intrange":0,"golint_modernize":3,"golint_nilnil":3,"golint_perfsprint":0,"golint_prealloc":0,"golint_recvcheck":1,"golint_usestdlibvars":0,"golint_wastedassign":0,"golint_total":8,"golint_test_testifylint":0,"golint_test_thelper":0,"golint_test_usetesting":0,"golint_test_total":0,"golint_vetx_total":0,"eslint_problems":0,"eslint_errors":0,"eslint_warnings":0,"tsc_errors":0,"vitest_failed":0,"vitest_total":116,"measure_s":81}
2026-08-16 20:09:24 +08:00

168 lines
4.6 KiB
Go

// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
// Package config loads and persists relay daemon configuration.
package config
import (
"encoding/json"
"errors"
"os"
"path/filepath"
"strings"
"time"
edgeconfig "github.com/Rain-kl/Wavelet/internal/apps/edge/config"
"github.com/Rain-kl/Wavelet/internal/apps/edge/nodeip"
)
const (
defaultHeartbeatInterval = 10 * time.Second
defaultRequestTimeout = 10 * time.Second
configFilePerm = 0o600
)
// MillisecondDuration is a JSON-friendly duration type shared with edge config.
type MillisecondDuration = edgeconfig.MillisecondDuration
// Config holds relay daemon settings loaded from file and environment.
type Config struct {
ServerURL string `json:"server_url"`
AgentToken string `json:"agent_token"`
DiscoveryToken string `json:"discovery_token"`
NodeName string `json:"node_name"`
NodeIP string `json:"node_ip"`
FrpsPath string `json:"frps_path"`
DataDir string `json:"data_dir"`
StatePath string `json:"state_path"`
HeartbeatInterval MillisecondDuration `json:"heartbeat_interval"`
RequestTimeout MillisecondDuration `json:"request_timeout"`
configPath string
NodeIPConfigured bool
}
// Load reads configuration from path, applying environment overrides and defaults.
func Load(path string) (*Config, error) {
data, err := os.ReadFile(path) //nolint:gosec // path is the relay config file from startup
if err != nil && !os.IsNotExist(err) {
return nil, err
}
cfg := &Config{}
if err == nil {
if err = json.Unmarshal(data, cfg); err != nil {
return nil, err
}
}
if err != nil && !hasEnvConfig() {
return nil, err
}
cfg.configPath = path
applyEnvOverrides(cfg)
cfg.NodeIPConfigured = cfg.NodeIP != ""
applyDefaults(cfg, filepath.Dir(path))
if err = validate(cfg); err != nil {
return nil, err
}
return cfg, nil
}
func hasEnvConfig() bool {
for _, key := range []string{
"OPENFLARE_SERVER_URL",
"OPENFLARE_AGENT_TOKEN",
"OPENFLARE_DISCOVERY_TOKEN",
"OPENFLARE_NODE_NAME",
"OPENFLARE_NODE_IP",
"OPENFLARE_DATA_DIR",
"OPENFLARE_FRPS_PATH",
} {
if strings.TrimSpace(os.Getenv(key)) != "" {
return true
}
}
return false
}
func applyEnvOverrides(cfg *Config) {
if cfg == nil {
return
}
overrideString := func(key string, target *string) {
if value := strings.TrimSpace(os.Getenv(key)); value != "" {
*target = value
}
}
overrideString("OPENFLARE_SERVER_URL", &cfg.ServerURL)
overrideString("OPENFLARE_AGENT_TOKEN", &cfg.AgentToken)
overrideString("OPENFLARE_DISCOVERY_TOKEN", &cfg.DiscoveryToken)
overrideString("OPENFLARE_NODE_NAME", &cfg.NodeName)
overrideString("OPENFLARE_NODE_IP", &cfg.NodeIP)
overrideString("OPENFLARE_DATA_DIR", &cfg.DataDir)
overrideString("OPENFLARE_FRPS_PATH", &cfg.FrpsPath)
}
func applyDefaults(cfg *Config, baseDir string) {
baseDir = filepath.Clean(baseDir)
if cfg.FrpsPath == "" {
cfg.FrpsPath = "frps" // rely on PATH
}
if cfg.DataDir == "" {
cfg.DataDir = filepath.Join(baseDir, "data")
}
if cfg.NodeName == "" {
host, _ := os.Hostname()
cfg.NodeName = strings.TrimSpace(host)
}
if cfg.NodeIP == "" {
cfg.NodeIP = nodeip.Detect()
}
if cfg.StatePath == "" {
cfg.StatePath = filepath.Join(cfg.DataDir, "relay-state.json")
}
if cfg.HeartbeatInterval <= 0 {
cfg.HeartbeatInterval = MillisecondDuration(defaultHeartbeatInterval)
}
if cfg.RequestTimeout <= 0 {
cfg.RequestTimeout = MillisecondDuration(defaultRequestTimeout)
}
}
func validate(cfg *Config) error {
if cfg.ServerURL == "" {
return errors.New("server_url 不能为空")
}
if strings.TrimSpace(cfg.AgentToken) == "" && strings.TrimSpace(cfg.DiscoveryToken) == "" {
return errors.New("agent_token 和 discovery_token 不能同时为空")
}
if cfg.NodeName == "" {
return errors.New("node_name 不能为空")
}
return nil
}
// InitialAuthToken returns the agent or discovery token used for authentication.
func (cfg *Config) InitialAuthToken() string {
if cfg == nil {
return ""
}
if token := strings.TrimSpace(cfg.AgentToken); token != "" {
return token
}
return strings.TrimSpace(cfg.DiscoveryToken)
}
// Save writes the current configuration back to the loaded config path.
func (cfg *Config) Save() error {
if cfg == nil {
return errors.New("config 不能为空")
}
if cfg.configPath == "" {
return errors.New("config path 未初始化")
}
data, err := json.MarshalIndent(cfg, "", " ")
if err != nil {
return err
}
return os.WriteFile(cfg.configPath, data, configFilePerm)
}