package netplan import ( "fmt" "os" "path/filepath" "quickip/internal/model" "gopkg.in/yaml.v3" ) type Service struct{} type fileConfig struct { Network map[string]any `yaml:"network"` } 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" 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 == nil { cfg.Network = make(map[string]any) } if _, ok := cfg.Network["version"]; !ok { cfg.Network["version"] = 2 } ethernets := ensureMap(cfg.Network, "ethernets") target := ensureMap(ethernets, targetInterface) if input.Dhcp4 { target["dhcp4"] = true delete(target, "addresses") delete(target, "gateway4") delete(target, "routes") delete(target, "nameservers") output, err := yaml.Marshal(&cfg) if err != nil { return err } return os.WriteFile(path, output, 0600) } target["dhcp4"] = false target["addresses"] = []string{fmt.Sprintf("%s/%d", input.IP, input.Prefix)} delete(target, "gateway4") if input.Gateway != "" { target["routes"] = []map[string]string{{"to": "default", "via": input.Gateway}} } else { delete(target, "routes") } if len(input.DNS) > 0 { nameservers := ensureMap(target, "nameservers") nameservers["addresses"] = input.DNS } else { delete(target, "nameservers") } output, err := yaml.Marshal(&cfg) if err != nil { return err } return os.WriteFile(path, output, 0600) } func ensureMap(parent map[string]any, key string) map[string]any { if existing, ok := parent[key].(map[string]any); ok { return existing } child := make(map[string]any) parent[key] = child return child } func anyToStringSlice(value any) []string { switch typed := value.(type) { case []string: return append([]string(nil), typed...) case []any: result := make([]string, 0, len(typed)) for _, item := range typed { text, ok := item.(string) if ok { result = append(result, text) } } return result default: return nil } } 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 }