persistence.go 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. package network
  2. import (
  3. "fmt"
  4. "os"
  5. "path/filepath"
  6. "strings"
  7. "time"
  8. )
  9. const (
  10. DebianInterfacesPath = "/etc/network/interfaces"
  11. DebianVlansCfgPath = "/etc/network/interfaces.d/vlans.cfg"
  12. DebianIfacesCfgPath = "/etc/network/interfaces.d/interfaces.cfg"
  13. )
  14. // PersistenceManager handles writing Debian network configuration files.
  15. type PersistenceManager struct {
  16. netMgr *Manager
  17. }
  18. func NewPersistenceManager(netMgr *Manager) *PersistenceManager {
  19. return &PersistenceManager{netMgr: netMgr}
  20. }
  21. // GenerateDebianConfig creates the contents of the /etc/network/interfaces.d/vlans.cfg file.
  22. func (p *PersistenceManager) GenerateDebianConfig() (string, error) {
  23. vlans, err := p.netMgr.ListVLANs()
  24. if err != nil {
  25. return "", err
  26. }
  27. var sb strings.Builder
  28. sb.WriteString(fmt.Sprintf("# ====================================================\n"))
  29. sb.WriteString(fmt.Sprintf("# Proxmox Router Appliance - VLANs Configuration\n"))
  30. sb.WriteString(fmt.Sprintf("# Generated: %s\n", time.Now().Format(time.RFC1123)))
  31. sb.WriteString(fmt.Sprintf("# DO NOT EDIT MANUALLY - Managed via Router Web UI\n"))
  32. sb.WriteString(fmt.Sprintf("# ====================================================\n\n"))
  33. if len(vlans) == 0 {
  34. sb.WriteString("# No VLAN sub-interfaces configured.\n")
  35. return sb.String(), nil
  36. }
  37. for _, v := range vlans {
  38. sb.WriteString(fmt.Sprintf("# --- VLAN ID %d (%s) ---\n", v.VlanID, v.Name))
  39. if v.Description != "" {
  40. sb.WriteString(fmt.Sprintf("# Description: %s\n", v.Description))
  41. }
  42. if v.Role != "" {
  43. sb.WriteString(fmt.Sprintf("# Role: %s\n", v.Role))
  44. }
  45. sb.WriteString(fmt.Sprintf("auto %s\n", v.Name))
  46. if len(v.IPv4) > 0 {
  47. sb.WriteString(fmt.Sprintf("iface %s inet static\n", v.Name))
  48. sb.WriteString(fmt.Sprintf(" address %s\n", v.IPv4[0]))
  49. if v.ParentName != "" {
  50. sb.WriteString(fmt.Sprintf(" vlan-raw-device %s\n", v.ParentName))
  51. }
  52. if v.MTU > 0 && v.MTU != 1500 {
  53. sb.WriteString(fmt.Sprintf(" mtu %d\n", v.MTU))
  54. }
  55. // additional secondary IPv4 addresses
  56. for i := 1; i < len(v.IPv4); i++ {
  57. sb.WriteString(fmt.Sprintf(" up ip addr add %s dev %s\n", v.IPv4[i], v.Name))
  58. sb.WriteString(fmt.Sprintf(" down ip addr del %s dev %s\n", v.IPv4[i], v.Name))
  59. }
  60. } else {
  61. sb.WriteString(fmt.Sprintf("iface %s inet manual\n", v.Name))
  62. if v.ParentName != "" {
  63. sb.WriteString(fmt.Sprintf(" vlan-raw-device %s\n", v.ParentName))
  64. }
  65. }
  66. sb.WriteString("\n")
  67. }
  68. return sb.String(), nil
  69. }
  70. // EnsureSourceInclude ensures that /etc/network/interfaces contains loopback and "source /etc/network/interfaces.d/*"
  71. // while removing duplicate interface stanzas that are managed inside interfaces.d/.
  72. func (p *PersistenceManager) EnsureSourceInclude() error {
  73. cleanContent := `# The loopback network interface
  74. auto lo
  75. iface lo inet loopback
  76. # Include extra interface configurations managed by DebTik / Proxmox Router
  77. source /etc/network/interfaces.d/*
  78. `
  79. return os.WriteFile(DebianInterfacesPath, []byte(cleanContent), 0644)
  80. }
  81. func isDynamicOrVirtualInterface(name string) bool {
  82. lower := strings.ToLower(name)
  83. return strings.HasPrefix(lower, "eoip-") ||
  84. strings.HasPrefix(lower, "wgx-") ||
  85. strings.HasPrefix(lower, "wg") ||
  86. strings.HasPrefix(lower, "aerotun-") ||
  87. strings.HasPrefix(lower, "ppp") ||
  88. strings.HasPrefix(lower, "gre")
  89. }
  90. // GenerateInterfacesConfig creates /etc/network/interfaces.d/interfaces.cfg for regular interfaces
  91. func (p *PersistenceManager) GenerateInterfacesConfig() (string, error) {
  92. ifacesWithIPs := p.netMgr.metadata.GetAllWithIPs()
  93. var sb strings.Builder
  94. sb.WriteString("# ====================================================\n")
  95. sb.WriteString("# Proxmox Router Appliance - Interface IP Configuration\n")
  96. sb.WriteString(fmt.Sprintf("# Generated: %s\n", time.Now().Format(time.RFC1123)))
  97. sb.WriteString("# DO NOT EDIT MANUALLY - Managed via Router Web UI\n")
  98. sb.WriteString("# ====================================================\n\n")
  99. if len(ifacesWithIPs) == 0 {
  100. sb.WriteString("# No persistent physical interface IPs configured.\n")
  101. return sb.String(), nil
  102. }
  103. for ifaceName, ips := range ifacesWithIPs {
  104. if len(ips) == 0 || strings.HasPrefix(ifaceName, "vlan") || strings.Contains(ifaceName, ".") || isDynamicOrVirtualInterface(ifaceName) {
  105. continue // Skip VLANs (handled in vlans.cfg) and daemon-managed virtual/tunnel interfaces
  106. }
  107. sb.WriteString(fmt.Sprintf("# --- Interface %s ---\n", ifaceName))
  108. sb.WriteString(fmt.Sprintf("auto %s\n", ifaceName))
  109. sb.WriteString(fmt.Sprintf("iface %s inet static\n", ifaceName))
  110. sb.WriteString(fmt.Sprintf(" address %s\n", ips[0]))
  111. for i := 1; i < len(ips); i++ {
  112. sb.WriteString(fmt.Sprintf(" up ip addr add %s dev %s || true\n", ips[i], ifaceName))
  113. sb.WriteString(fmt.Sprintf(" down ip addr del %s dev %s || true\n", ips[i], ifaceName))
  114. }
  115. sb.WriteString("\n")
  116. }
  117. return sb.String(), nil
  118. }
  119. // SaveDebianConfig writes the configuration to disk.
  120. func (p *PersistenceManager) SaveDebianConfig() (string, error) {
  121. cfg, err := p.GenerateDebianConfig()
  122. if err != nil {
  123. return "", err
  124. }
  125. dir := filepath.Dir(DebianVlansCfgPath)
  126. if err := os.MkdirAll(dir, 0755); err != nil {
  127. return "", fmt.Errorf("failed to create directory %s: %w", dir, err)
  128. }
  129. if err := os.WriteFile(DebianVlansCfgPath, []byte(cfg), 0644); err != nil {
  130. return "", fmt.Errorf("failed to write %s: %w", DebianVlansCfgPath, err)
  131. }
  132. ifaceCfg, err := p.GenerateInterfacesConfig()
  133. if err == nil {
  134. _ = os.WriteFile(DebianIfacesCfgPath, []byte(ifaceCfg), 0644)
  135. }
  136. _ = p.EnsureSourceInclude()
  137. return cfg, nil
  138. }