3x-ui/node/config/config.go

156 lines
3.7 KiB
Go

// Package config provides node configuration management, including API key persistence.
package config
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"sync"
)
// NodeConfig represents the node's configuration stored on disk.
type NodeConfig struct {
ApiKey string `json:"apiKey"` // API key for authentication with panel
PanelURL string `json:"panelUrl"` // Panel URL (optional, can be set via env var)
NodeAddress string `json:"nodeAddress"` // Node's own address (optional)
}
var (
config *NodeConfig
configMu sync.RWMutex
configPath string
)
// InitConfig initializes the configuration system and loads existing config if available.
// configDir is the directory where config file will be stored (e.g., "bin", "/app/bin").
func InitConfig(configDir string) error {
configMu.Lock()
defer configMu.Unlock()
// Determine config file path
if configDir == "" {
// Try common paths
possibleDirs := []string{"bin", "config", ".", "/app/bin", "/app/config"}
for _, dir := range possibleDirs {
if _, err := os.Stat(dir); err == nil {
configDir = dir
break
}
}
if configDir == "" {
configDir = "." // Fallback to current directory
}
}
configPath = filepath.Join(configDir, "node-config.json")
// Try to load existing config
if data, err := os.ReadFile(configPath); err == nil {
var loadedConfig NodeConfig
if err := json.Unmarshal(data, &loadedConfig); err == nil {
config = &loadedConfig
return nil
}
// If file exists but is invalid, we'll create a new one
}
// Create empty config if file doesn't exist
config = &NodeConfig{}
return nil
}
// GetConfig returns the current node configuration.
func GetConfig() *NodeConfig {
configMu.RLock()
defer configMu.RUnlock()
if config == nil {
return &NodeConfig{}
}
// Return a copy to prevent external modifications
return &NodeConfig{
ApiKey: config.ApiKey,
PanelURL: config.PanelURL,
NodeAddress: config.NodeAddress,
}
}
// SetApiKey sets the API key and saves it to disk.
// If an API key already exists, it will not be overwritten unless force is true.
func SetApiKey(apiKey string, force bool) error {
configMu.Lock()
defer configMu.Unlock()
if config == nil {
config = &NodeConfig{}
}
// Check if API key already exists
if config.ApiKey != "" && !force {
return fmt.Errorf("API key already exists. Use force=true to overwrite")
}
config.ApiKey = apiKey
return saveConfig()
}
// SetPanelURL sets the panel URL and saves it to disk.
func SetPanelURL(url string) error {
configMu.Lock()
defer configMu.Unlock()
if config == nil {
config = &NodeConfig{}
}
config.PanelURL = url
return saveConfig()
}
// SetNodeAddress sets the node address and saves it to disk.
func SetNodeAddress(address string) error {
configMu.Lock()
defer configMu.Unlock()
if config == nil {
config = &NodeConfig{}
}
config.NodeAddress = address
return saveConfig()
}
// saveConfig saves the current configuration to disk.
func saveConfig() error {
if configPath == "" {
return fmt.Errorf("config path not initialized, call InitConfig first")
}
// Ensure directory exists
dir := filepath.Dir(configPath)
if err := os.MkdirAll(dir, 0750); err != nil {
return fmt.Errorf("failed to create config directory: %w", err)
}
// Marshal config to JSON
data, err := json.MarshalIndent(config, "", " ")
if err != nil {
return fmt.Errorf("failed to marshal config: %w", err)
}
// Write to file with proper permissions (readable/writable by owner only)
if err := os.WriteFile(configPath, data, 0600); err != nil {
return fmt.Errorf("failed to write config file: %w", err)
}
return nil
}
// GetConfigPath returns the path to the config file.
func GetConfigPath() string {
configMu.RLock()
defer configMu.RUnlock()
return configPath
}