Files
alpine-router/config/config.go

229 lines
4.7 KiB
Go
Raw Normal View History

2026-04-13 09:46:02 +03:00
package config
import (
"fmt"
"os"
"path/filepath"
"sync"
"gopkg.in/yaml.v3"
)
type InterfaceConfig struct {
Auto bool `yaml:"auto"`
Mode string `yaml:"mode"`
Address string `yaml:"address,omitempty"`
Netmask string `yaml:"netmask,omitempty"`
Gateway string `yaml:"gateway,omitempty"`
DNS []string `yaml:"dns,omitempty"`
Extra map[string]string `yaml:"extra,omitempty"`
}
type DHCPPool struct {
Interface string `yaml:"interface"`
Enabled bool `yaml:"enabled"`
Subnet string `yaml:"subnet"`
Netmask string `yaml:"netmask"`
RangeStart string `yaml:"range_start"`
RangeEnd string `yaml:"range_end"`
Router string `yaml:"router"`
DNS []string `yaml:"dns"`
LeaseTime int `yaml:"lease_time"`
}
type DHCPConfig struct {
Enabled bool `yaml:"enabled"`
Pools []DHCPPool `yaml:"pools"`
}
type NATConfig struct {
Interfaces []string `yaml:"interfaces"`
}
type KnownDevice struct {
IP string `yaml:"ip"`
MAC string `yaml:"mac"`
Hostname string `yaml:"hostname"`
Blocked bool `yaml:"blocked,omitempty"`
StaticIP string `yaml:"static_ip,omitempty"`
}
type MihomoConfig struct {
Enabled bool `yaml:"enabled"`
}
type AppConfig struct {
Interfaces map[string]*InterfaceConfig `yaml:"interfaces"`
DHCP DHCPConfig `yaml:"dhcp"`
NAT NATConfig `yaml:"nat"`
KnownDevices []KnownDevice `yaml:"known_devices"`
Mihomo MihomoConfig `yaml:"mihomo"`
}
var (
mu sync.Mutex
filePath string
)
func init() {
dir := executableDir()
filePath = filepath.Join(dir, "config.yaml")
}
func executableDir() string {
exe, err := os.Executable()
if err != nil {
return "."
}
// Resolve symlinks — /proc/self/exe on Linux points to real path
if resolved, err := filepath.EvalSymlinks(exe); err == nil {
exe = resolved
}
return filepath.Dir(exe)
}
func SetPath(p string) {
mu.Lock()
defer mu.Unlock()
filePath = p
}
func GetPath() string {
mu.Lock()
defer mu.Unlock()
return filePath
}
func Load() (*AppConfig, error) {
mu.Lock()
defer mu.Unlock()
data, err := os.ReadFile(filePath)
if err != nil {
if os.IsNotExist(err) {
return defaultConfig(), nil
}
return nil, fmt.Errorf("read config: %w", err)
}
var cfg AppConfig
if err := yaml.Unmarshal(data, &cfg); err != nil {
return nil, fmt.Errorf("parse config: %w", err)
}
EnsureDefaults(&cfg)
return &cfg, nil
}
func Save(cfg *AppConfig) error {
mu.Lock()
defer mu.Unlock()
EnsureDefaults(cfg)
data, err := yaml.Marshal(cfg)
if err != nil {
return fmt.Errorf("marshal config: %w", err)
}
if err := os.MkdirAll(filepath.Dir(filePath), 0755); err != nil {
return fmt.Errorf("mkdir config dir: %w", err)
}
tmp := filePath + ".tmp"
if err := os.WriteFile(tmp, data, 0644); err != nil {
return fmt.Errorf("write config tmp: %w", err)
}
if err := os.Rename(tmp, filePath); err != nil {
return fmt.Errorf("rename config: %w", err)
}
return nil
}
func LoadInto(dst interface{}) error {
mu.Lock()
defer mu.Unlock()
data, err := os.ReadFile(filePath)
if err != nil {
if os.IsNotExist(err) {
return nil
}
return fmt.Errorf("read config: %w", err)
}
return yaml.Unmarshal(data, dst)
}
func defaultConfig() *AppConfig {
return &AppConfig{
Interfaces: map[string]*InterfaceConfig{},
DHCP: DHCPConfig{Pools: []DHCPPool{}},
NAT: NATConfig{Interfaces: []string{}},
KnownDevices: []KnownDevice{},
Mihomo: MihomoConfig{Enabled: false},
}
}
func EnsureDefaults(cfg *AppConfig) {
if cfg.Interfaces == nil {
cfg.Interfaces = map[string]*InterfaceConfig{}
}
if cfg.DHCP.Pools == nil {
cfg.DHCP.Pools = []DHCPPool{}
}
if cfg.NAT.Interfaces == nil {
cfg.NAT.Interfaces = []string{}
}
if cfg.KnownDevices == nil {
cfg.KnownDevices = []KnownDevice{}
}
}
func UpdateKnownDevices(seen []KnownDevice) error {
cfg, err := Load()
if err != nil {
return err
}
existing := make(map[string]KnownDevice)
for _, d := range cfg.KnownDevices {
key := d.MAC
if key == "" {
key = d.IP
}
existing[key] = d
}
for _, d := range seen {
key := d.MAC
if key == "" {
key = d.IP
}
if existingDev, ok := existing[key]; ok {
if d.Hostname != "" {
existingDev.Hostname = d.Hostname
}
if d.IP != "" {
existingDev.IP = d.IP
}
if d.MAC != "" {
existingDev.MAC = d.MAC
}
if d.StaticIP != "" {
existingDev.StaticIP = d.StaticIP
}
existing[key] = existingDev
} else {
existing[key] = d
}
}
cfg.KnownDevices = make([]KnownDevice, 0, len(existing))
for _, d := range existing {
cfg.KnownDevices = append(cfg.KnownDevices, d)
}
return Save(cfg)
}