first commit
This commit is contained in:
229
config/config.go
Normal file
229
config/config.go
Normal file
@@ -0,0 +1,229 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user