persistence.go 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  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. // GenerateInterfacesConfig creates /etc/network/interfaces.d/interfaces.cfg for regular interfaces
  82. func (p *PersistenceManager) GenerateInterfacesConfig() (string, error) {
  83. ifacesWithIPs := p.netMgr.metadata.GetAllWithIPs()
  84. var sb strings.Builder
  85. sb.WriteString("# ====================================================\n")
  86. sb.WriteString("# Proxmox Router Appliance - Interface IP Configuration\n")
  87. sb.WriteString(fmt.Sprintf("# Generated: %s\n", time.Now().Format(time.RFC1123)))
  88. sb.WriteString("# DO NOT EDIT MANUALLY - Managed via Router Web UI\n")
  89. sb.WriteString("# ====================================================\n\n")
  90. if len(ifacesWithIPs) == 0 {
  91. sb.WriteString("# No persistent physical interface IPs configured.\n")
  92. return sb.String(), nil
  93. }
  94. for ifaceName, ips := range ifacesWithIPs {
  95. if len(ips) == 0 || strings.HasPrefix(ifaceName, "vlan") || strings.Contains(ifaceName, ".") {
  96. continue // Skip VLANs (handled in vlans.cfg)
  97. }
  98. sb.WriteString(fmt.Sprintf("# --- Interface %s ---\n", ifaceName))
  99. sb.WriteString(fmt.Sprintf("auto %s\n", ifaceName))
  100. sb.WriteString(fmt.Sprintf("iface %s inet static\n", ifaceName))
  101. sb.WriteString(fmt.Sprintf(" address %s\n", ips[0]))
  102. for i := 1; i < len(ips); i++ {
  103. sb.WriteString(fmt.Sprintf(" up ip addr add %s dev %s || true\n", ips[i], ifaceName))
  104. sb.WriteString(fmt.Sprintf(" down ip addr del %s dev %s || true\n", ips[i], ifaceName))
  105. }
  106. sb.WriteString("\n")
  107. }
  108. return sb.String(), nil
  109. }
  110. // SaveDebianConfig writes the configuration to disk.
  111. func (p *PersistenceManager) SaveDebianConfig() (string, error) {
  112. cfg, err := p.GenerateDebianConfig()
  113. if err != nil {
  114. return "", err
  115. }
  116. dir := filepath.Dir(DebianVlansCfgPath)
  117. if err := os.MkdirAll(dir, 0755); err != nil {
  118. return "", fmt.Errorf("failed to create directory %s: %w", dir, err)
  119. }
  120. if err := os.WriteFile(DebianVlansCfgPath, []byte(cfg), 0644); err != nil {
  121. return "", fmt.Errorf("failed to write %s: %w", DebianVlansCfgPath, err)
  122. }
  123. ifaceCfg, err := p.GenerateInterfacesConfig()
  124. if err == nil {
  125. _ = os.WriteFile(DebianIfacesCfgPath, []byte(ifaceCfg), 0644)
  126. }
  127. _ = p.EnsureSourceInclude()
  128. return cfg, nil
  129. }