mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-09-28 05:46:36 +08:00
aa4faddade
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}
168 lines
4.6 KiB
Go
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)
|
|
}
|