Files
Reticulum-Go/internal/config/config.go
Sudo-Ivan 1d3a969742
Some checks failed
Bearer / scan (push) Successful in 9s
Go Build Multi-Platform / build (amd64, linux) (push) Successful in 42s
Go Build Multi-Platform / build (amd64, darwin) (push) Successful in 44s
Go Build Multi-Platform / build (arm, freebsd) (push) Successful in 41s
Go Build Multi-Platform / build (arm, windows) (push) Successful in 39s
Go Build Multi-Platform / build (arm64, windows) (push) Successful in 1m8s
Go Build Multi-Platform / build (wasm, js) (push) Successful in 1m6s
TinyGo Build / tinygo-build (tinygo-wasm, tinygo-wasm, reticulum-go.wasm, wasm) (pull_request) Failing after 1m2s
TinyGo Build / tinygo-build (tinygo-build, tinygo-default, reticulum-go-tinygo, ) (pull_request) Failing after 1m4s
Go Revive Lint / lint (push) Successful in 1m4s
Go Test Multi-Platform / Test (ubuntu-latest, arm64) (push) Successful in 1m24s
Run Gosec / tests (push) Successful in 1m29s
Go Test Multi-Platform / Test (ubuntu-latest, amd64) (push) Successful in 2m31s
Go Build Multi-Platform / build (amd64, freebsd) (push) Successful in 9m28s
Go Build Multi-Platform / build (arm, linux) (push) Successful in 9m28s
Go Build Multi-Platform / build (amd64, windows) (push) Successful in 9m30s
Go Build Multi-Platform / build (arm64, darwin) (push) Successful in 9m27s
Go Build Multi-Platform / build (arm64, linux) (push) Successful in 9m26s
Go Build Multi-Platform / build (arm64, freebsd) (push) Successful in 9m29s
Go Build Multi-Platform / Create Release (push) Has been skipped
chore: add SPDX license identifier and copyright notice
2025-12-31 20:44:58 -06:00

273 lines
7.0 KiB
Go

// SPDX-License-Identifier: 0BSD
// Copyright (c) 2024-2026 Sudo-Ivan / Quad4.io
package config
import (
"bufio"
"fmt"
"os"
"path/filepath"
"strconv"
"strings"
"git.quad4.io/Networks/Reticulum-Go/pkg/common"
)
const (
DefaultSharedInstancePort = 37428
DefaultInstanceControlPort = 37429
DefaultLogLevel = 4
)
func DefaultConfig() *common.ReticulumConfig {
return &common.ReticulumConfig{
EnableTransport: true,
ShareInstance: true,
SharedInstancePort: DefaultSharedInstancePort,
InstanceControlPort: DefaultInstanceControlPort,
PanicOnInterfaceErr: false,
LogLevel: DefaultLogLevel,
Interfaces: make(map[string]*common.InterfaceConfig),
}
}
func GetConfigPath() (string, error) {
homeDir, err := os.UserHomeDir()
if err != nil {
return "", err
}
return filepath.Join(homeDir, ".reticulum-go", "config"), nil
}
func EnsureConfigDir() error {
homeDir, err := os.UserHomeDir()
if err != nil {
return err
}
configDir := filepath.Join(homeDir, ".reticulum-go")
return os.MkdirAll(configDir, 0700) // #nosec G301
}
// parseValue parses string values into appropriate types
func parseValue(value string) interface{} {
value = strings.TrimSpace(value)
// Try bool
if value == "true" {
return true
}
if value == "false" {
return false
}
// Try int
if i, err := strconv.Atoi(value); err == nil {
return i
}
// Return as string
return value
}
// LoadConfig loads the configuration from the specified path
func LoadConfig(path string) (*common.ReticulumConfig, error) {
// bearer:disable go_gosec_filesystem_filereadtaint
file, err := os.Open(path) // #nosec G304
if err != nil {
return nil, err
}
defer file.Close()
cfg := DefaultConfig()
cfg.ConfigPath = path
scanner := bufio.NewScanner(file)
var currentInterface *common.InterfaceConfig
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
// Skip comments and empty lines
if line == "" || strings.HasPrefix(line, "#") {
continue
}
// Handle interface sections
if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") {
name := strings.Trim(line, "[]")
currentInterface = &common.InterfaceConfig{Name: name}
cfg.Interfaces[name] = currentInterface
continue
}
parts := strings.SplitN(line, "=", 2)
if len(parts) != 2 {
continue
}
key := strings.TrimSpace(parts[0])
value := strings.TrimSpace(parts[1])
if currentInterface != nil {
// Parse interface config
switch key {
case "type":
currentInterface.Type = value
case "enabled":
currentInterface.Enabled = value == "true"
case "address":
currentInterface.Address = value
case "port":
currentInterface.Port, _ = strconv.Atoi(value)
case "target_host":
currentInterface.TargetHost = value
case "target_port":
currentInterface.TargetPort, _ = strconv.Atoi(value)
case "discovery_port":
currentInterface.DiscoveryPort, _ = strconv.Atoi(value)
case "data_port":
currentInterface.DataPort, _ = strconv.Atoi(value)
case "discovery_scope":
currentInterface.DiscoveryScope = value
case "group_id":
currentInterface.GroupID = value
}
} else {
// Parse global config
switch key {
case "enable_transport":
cfg.EnableTransport = value == "true"
case "share_instance":
cfg.ShareInstance = value == "true"
case "shared_instance_port":
cfg.SharedInstancePort, _ = strconv.Atoi(value)
case "instance_control_port":
cfg.InstanceControlPort, _ = strconv.Atoi(value)
case "panic_on_interface_error":
cfg.PanicOnInterfaceErr = value == "true"
case "loglevel":
cfg.LogLevel, _ = strconv.Atoi(value)
}
}
}
return cfg, nil
}
// SaveConfig saves the configuration to the specified path
func SaveConfig(cfg *common.ReticulumConfig) error {
if cfg.ConfigPath == "" {
return fmt.Errorf("config path not set")
}
var builder strings.Builder
// Write global config
builder.WriteString("# Reticulum Configuration\n")
builder.WriteString(fmt.Sprintf("enable_transport = %v\n", cfg.EnableTransport))
builder.WriteString(fmt.Sprintf("share_instance = %v\n", cfg.ShareInstance))
builder.WriteString(fmt.Sprintf("shared_instance_port = %d\n", cfg.SharedInstancePort))
builder.WriteString(fmt.Sprintf("instance_control_port = %d\n", cfg.InstanceControlPort))
builder.WriteString(fmt.Sprintf("panic_on_interface_error = %v\n", cfg.PanicOnInterfaceErr))
builder.WriteString(fmt.Sprintf("loglevel = %d\n\n", cfg.LogLevel))
// Write interface configs
for name, iface := range cfg.Interfaces {
builder.WriteString(fmt.Sprintf("[%s]\n", name))
builder.WriteString(fmt.Sprintf("type = %s\n", iface.Type))
builder.WriteString(fmt.Sprintf("enabled = %v\n", iface.Enabled))
if iface.Address != "" {
builder.WriteString(fmt.Sprintf("address = %s\n", iface.Address))
}
if iface.Port != 0 {
builder.WriteString(fmt.Sprintf("port = %d\n", iface.Port))
}
if iface.TargetHost != "" {
builder.WriteString(fmt.Sprintf("target_host = %s\n", iface.TargetHost))
}
if iface.TargetPort != 0 {
builder.WriteString(fmt.Sprintf("target_port = %d\n", iface.TargetPort))
}
if iface.DiscoveryPort != 0 {
builder.WriteString(fmt.Sprintf("discovery_port = %d\n", iface.DiscoveryPort))
}
if iface.DataPort != 0 {
builder.WriteString(fmt.Sprintf("data_port = %d\n", iface.DataPort))
}
if iface.DiscoveryScope != "" {
builder.WriteString(fmt.Sprintf("discovery_scope = %s\n", iface.DiscoveryScope))
}
if iface.GroupID != "" {
builder.WriteString(fmt.Sprintf("group_id = %s\n", iface.GroupID))
}
builder.WriteString("\n")
}
return os.WriteFile(cfg.ConfigPath, []byte(builder.String()), 0600) // #nosec G306
}
// CreateDefaultConfig creates a default configuration file
func CreateDefaultConfig(path string) error {
cfg := DefaultConfig()
cfg.ConfigPath = path
cfg.Interfaces["Auto Discovery"] = &common.InterfaceConfig{
Type: "AutoInterface",
Enabled: true,
GroupID: "reticulum",
DiscoveryScope: "link",
DiscoveryPort: 29716,
DataPort: 42671,
}
cfg.Interfaces["Go-RNS-Testnet"] = &common.InterfaceConfig{
Type: "TCPClientInterface",
Enabled: true,
TargetHost: "127.0.0.1",
TargetPort: 4242,
Name: "Go-RNS-Testnet",
}
cfg.Interfaces["Quad4 TCP"] = &common.InterfaceConfig{
Type: "TCPClientInterface",
Enabled: true,
TargetHost: "rns.quad4.io",
TargetPort: 4242,
Name: "Quad4 TCP",
}
cfg.Interfaces["Local UDP"] = &common.InterfaceConfig{
Type: "UDPInterface",
Enabled: false,
Address: "0.0.0.0",
Port: 37696,
}
if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil { // #nosec G301
return err
}
return SaveConfig(cfg)
}
// InitConfig initializes the configuration system
func InitConfig() (*common.ReticulumConfig, error) {
configPath, err := GetConfigPath()
if err != nil {
return nil, err
}
// Check if config file exists
if _, err := os.Stat(configPath); os.IsNotExist(err) {
// Create default config
if err := CreateDefaultConfig(configPath); err != nil {
return nil, err
}
}
// Load config
return LoadConfig(configPath)
}