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
273 lines
7.0 KiB
Go
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)
|
|
}
|