package netplan import ( "fmt" "os" "path/filepath" "quickip/internal/model" "gopkg.in/yaml.v3" ) type Service struct{} type fileConfig struct { Network networkConfig `yaml:"network"` } type networkConfig struct { Version int `yaml:"version,omitempty"` Ethernets map[string]*ethernetEntry `yaml:"ethernets,omitempty"` } type ethernetEntry struct { DHCP4 *bool `yaml:"dhcp4,omitempty"` Addresses []string `yaml:"addresses,omitempty"` Gateway4 string `yaml:"gateway4,omitempty"` Nameservers *nameserverConfig `yaml:"nameservers,omitempty"` Optional *bool `yaml:"optional,omitempty"` MTU int `yaml:"mtu,omitempty"` WakeOnLAN *bool `yaml:"wakeonlan,omitempty"` } type nameserverConfig struct { Addresses []string `yaml:"addresses,omitempty"` } func New() *Service { return &Service{} } func (s *Service) FindSingleFile() (string, error) { files, err := filepath.Glob("/etc/netplan/*.yaml") if err != nil { return "", err } if len(files) == 0 { return "", fmt.Errorf("未找到 netplan 配置文件") } if len(files) > 1 { return "", fmt.Errorf("检测到多个 netplan 配置文件,首版暂不支持自动处理") } return files[0], nil } func (s *Service) Backup(path string) (string, error) { backupPath := path + ".quickip.bak" if _, err := os.Stat(backupPath); err == nil { return backupPath, nil } data, err := os.ReadFile(path) if err != nil { return "", err } if err := os.WriteFile(backupPath, data, 0600); err != nil { return "", err } return backupPath, nil } func (s *Service) Restore(path string, backupPath string) error { data, err := os.ReadFile(backupPath) if err != nil { return err } if err := os.WriteFile(path, data, 0600); err != nil { return err } _ = os.Remove(backupPath) return nil } func (s *Service) Write(path string, targetInterface string, input model.InterfaceConfig, managementInterface string, maintenanceCIDR string) error { data, err := os.ReadFile(path) if err != nil { return err } var cfg fileConfig if err := yaml.Unmarshal(data, &cfg); err != nil { return err } if cfg.Network.Version == 0 { cfg.Network.Version = 2 } if cfg.Network.Ethernets == nil { cfg.Network.Ethernets = make(map[string]*ethernetEntry) } target := cfg.Network.Ethernets[targetInterface] if target == nil { target = ðernetEntry{} cfg.Network.Ethernets[targetInterface] = target } dhcpFalse := false target.DHCP4 = &dhcpFalse target.Addresses = []string{fmt.Sprintf("%s/%d", input.IP, input.Prefix)} if input.Gateway != "" { target.Gateway4 = input.Gateway } else { target.Gateway4 = "" } if len(input.DNS) > 0 { target.Nameservers = &nameserverConfig{Addresses: input.DNS} } else { target.Nameservers = nil } mgmt := cfg.Network.Ethernets[managementInterface] if mgmt == nil { mgmt = ðernetEntry{} cfg.Network.Ethernets[managementInterface] = mgmt } dhcpTrue := true mgmt.DHCP4 = &dhcpTrue if !contains(mgmt.Addresses, maintenanceCIDR) { mgmt.Addresses = append(uniqueNonEmpty(mgmt.Addresses), maintenanceCIDR) } output, err := yaml.Marshal(&cfg) if err != nil { return err } return os.WriteFile(path, output, 0600) } func contains(items []string, target string) bool { for _, item := range items { if item == target { return true } } return false } func uniqueNonEmpty(items []string) []string { seen := make(map[string]struct{}) result := make([]string, 0, len(items)) for _, item := range items { if item == "" { continue } if _, ok := seen[item]; ok { continue } seen[item] = struct{}{} result = append(result, item) } return result }