netplan.go 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. package netplan
  2. import (
  3. "bytes"
  4. "fmt"
  5. "os"
  6. "path/filepath"
  7. "strings"
  8. "networktool/internal/model"
  9. "gopkg.in/yaml.v3"
  10. )
  11. type Service struct{}
  12. type fileConfig struct {
  13. Network map[string]any `yaml:"network"`
  14. }
  15. func New() *Service { return &Service{} }
  16. func (s *Service) FindSingleFile() (string, error) {
  17. files, err := filepath.Glob("/etc/netplan/*.yaml")
  18. if err != nil {
  19. return "", err
  20. }
  21. if len(files) == 0 {
  22. return "", fmt.Errorf("未找到 netplan 配置文件")
  23. }
  24. if len(files) > 1 {
  25. return "", fmt.Errorf("检测到多个 netplan 配置文件,首版暂不支持自动处理")
  26. }
  27. return files[0], nil
  28. }
  29. func (s *Service) Backup(path string) (string, error) {
  30. backupPath := path + ".networktool.bak"
  31. data, err := os.ReadFile(path)
  32. if err != nil {
  33. return "", err
  34. }
  35. if err := os.WriteFile(backupPath, data, 0600); err != nil {
  36. return "", err
  37. }
  38. return backupPath, nil
  39. }
  40. func (s *Service) Restore(path string, backupPath string) error {
  41. data, err := os.ReadFile(backupPath)
  42. if err != nil {
  43. return err
  44. }
  45. if err := os.WriteFile(path, data, 0600); err != nil {
  46. return err
  47. }
  48. _ = os.Remove(backupPath)
  49. return nil
  50. }
  51. func (s *Service) Write(path string, targetInterface string, input model.InterfaceConfig, managementInterface string, maintenanceCIDR string) error {
  52. data, err := os.ReadFile(path)
  53. if err != nil {
  54. return err
  55. }
  56. var cfg fileConfig
  57. if err := yaml.Unmarshal(data, &cfg); err != nil {
  58. return err
  59. }
  60. if cfg.Network == nil {
  61. cfg.Network = make(map[string]any)
  62. }
  63. if _, ok := cfg.Network["version"]; !ok {
  64. cfg.Network["version"] = 2
  65. }
  66. ethernets := ensureMap(cfg.Network, "ethernets")
  67. target := ensureMap(ethernets, targetInterface)
  68. if input.Dhcp4 {
  69. target["dhcp4"] = true
  70. delete(target, "addresses")
  71. delete(target, "gateway4")
  72. delete(target, "routes")
  73. delete(target, "nameservers")
  74. output, err := marshalYAML(&cfg)
  75. if err != nil {
  76. return err
  77. }
  78. return os.WriteFile(path, output, 0600)
  79. }
  80. addresses := normalizedAddresses(input)
  81. routes := normalizedRoutes(input)
  82. target["dhcp4"] = false
  83. addressItems := make([]string, 0, len(addresses))
  84. for _, address := range addresses {
  85. addressItems = append(addressItems, fmt.Sprintf("%s/%d", strings.TrimSpace(address.IP), address.Prefix))
  86. }
  87. target["addresses"] = addressItems
  88. delete(target, "gateway4")
  89. if len(routes) > 0 {
  90. routeItems := make([]map[string]string, 0, len(routes))
  91. for _, route := range routes {
  92. routeItems = append(routeItems, map[string]string{"to": strings.TrimSpace(route.To), "via": strings.TrimSpace(route.Via)})
  93. }
  94. target["routes"] = routeItems
  95. } else {
  96. delete(target, "routes")
  97. }
  98. if len(input.DNS) > 0 {
  99. nameservers := ensureMap(target, "nameservers")
  100. nameservers["addresses"] = input.DNS
  101. } else {
  102. delete(target, "nameservers")
  103. }
  104. output, err := marshalYAML(&cfg)
  105. if err != nil {
  106. return err
  107. }
  108. return os.WriteFile(path, output, 0600)
  109. }
  110. func normalizedAddresses(input model.InterfaceConfig) []model.InterfaceAddressConfig {
  111. if len(input.Addresses) > 0 {
  112. return input.Addresses
  113. }
  114. if strings.TrimSpace(input.IP) == "" {
  115. return nil
  116. }
  117. return []model.InterfaceAddressConfig{{IP: strings.TrimSpace(input.IP), Prefix: input.Prefix}}
  118. }
  119. func normalizedRoutes(input model.InterfaceConfig) []model.InterfaceRouteConfig {
  120. if len(input.Routes) > 0 {
  121. return input.Routes
  122. }
  123. if strings.TrimSpace(input.Gateway) == "" {
  124. return nil
  125. }
  126. return []model.InterfaceRouteConfig{{To: "default", Via: strings.TrimSpace(input.Gateway)}}
  127. }
  128. func marshalYAML(value any) ([]byte, error) {
  129. var output bytes.Buffer
  130. encoder := yaml.NewEncoder(&output)
  131. encoder.SetIndent(2)
  132. if err := encoder.Encode(value); err != nil {
  133. _ = encoder.Close()
  134. return nil, err
  135. }
  136. if err := encoder.Close(); err != nil {
  137. return nil, err
  138. }
  139. return output.Bytes(), nil
  140. }
  141. func ensureMap(parent map[string]any, key string) map[string]any {
  142. if existing, ok := parent[key].(map[string]any); ok {
  143. return existing
  144. }
  145. child := make(map[string]any)
  146. parent[key] = child
  147. return child
  148. }
  149. func anyToStringSlice(value any) []string {
  150. switch typed := value.(type) {
  151. case []string:
  152. return append([]string(nil), typed...)
  153. case []any:
  154. result := make([]string, 0, len(typed))
  155. for _, item := range typed {
  156. text, ok := item.(string)
  157. if ok {
  158. result = append(result, text)
  159. }
  160. }
  161. return result
  162. default:
  163. return nil
  164. }
  165. }
  166. func contains(items []string, target string) bool {
  167. for _, item := range items {
  168. if item == target {
  169. return true
  170. }
  171. }
  172. return false
  173. }
  174. func uniqueNonEmpty(items []string) []string {
  175. seen := make(map[string]struct{})
  176. result := make([]string, 0, len(items))
  177. for _, item := range items {
  178. if item == "" {
  179. continue
  180. }
  181. if _, ok := seen[item]; ok {
  182. continue
  183. }
  184. seen[item] = struct{}{}
  185. result = append(result, item)
  186. }
  187. return result
  188. }