package network import ( "fmt" "os" "path/filepath" "strings" "time" ) const ( DebianInterfacesPath = "/etc/network/interfaces" DebianVlansCfgPath = "/etc/network/interfaces.d/vlans.cfg" ) // PersistenceManager handles writing Debian network configuration files. type PersistenceManager struct { netMgr *Manager } func NewPersistenceManager(netMgr *Manager) *PersistenceManager { return &PersistenceManager{netMgr: netMgr} } // GenerateDebianConfig creates the contents of the /etc/network/interfaces.d/vlans.cfg file. func (p *PersistenceManager) GenerateDebianConfig() (string, error) { vlans, err := p.netMgr.ListVLANs() if err != nil { return "", err } var sb strings.Builder sb.WriteString(fmt.Sprintf("# ====================================================\n")) sb.WriteString(fmt.Sprintf("# Proxmox Router Appliance - VLANs Configuration\n")) sb.WriteString(fmt.Sprintf("# Generated: %s\n", time.Now().Format(time.RFC1123))) sb.WriteString(fmt.Sprintf("# DO NOT EDIT MANUALLY - Managed via Router Web UI\n")) sb.WriteString(fmt.Sprintf("# ====================================================\n\n")) if len(vlans) == 0 { sb.WriteString("# No VLAN sub-interfaces configured.\n") return sb.String(), nil } for _, v := range vlans { sb.WriteString(fmt.Sprintf("# --- VLAN ID %d (%s) ---\n", v.VlanID, v.Name)) if v.Description != "" { sb.WriteString(fmt.Sprintf("# Description: %s\n", v.Description)) } if v.Role != "" { sb.WriteString(fmt.Sprintf("# Role: %s\n", v.Role)) } sb.WriteString(fmt.Sprintf("auto %s\n", v.Name)) if len(v.IPv4) > 0 { sb.WriteString(fmt.Sprintf("iface %s inet static\n", v.Name)) sb.WriteString(fmt.Sprintf(" address %s\n", v.IPv4[0])) if v.ParentName != "" { sb.WriteString(fmt.Sprintf(" vlan-raw-device %s\n", v.ParentName)) } if v.MTU > 0 && v.MTU != 1500 { sb.WriteString(fmt.Sprintf(" mtu %d\n", v.MTU)) } // additional secondary IPv4 addresses for i := 1; i < len(v.IPv4); i++ { sb.WriteString(fmt.Sprintf(" up ip addr add %s dev %s\n", v.IPv4[i], v.Name)) sb.WriteString(fmt.Sprintf(" down ip addr del %s dev %s\n", v.IPv4[i], v.Name)) } } else { sb.WriteString(fmt.Sprintf("iface %s inet manual\n", v.Name)) if v.ParentName != "" { sb.WriteString(fmt.Sprintf(" vlan-raw-device %s\n", v.ParentName)) } } sb.WriteString("\n") } return sb.String(), nil } // EnsureSourceInclude ensures that /etc/network/interfaces contains "source /etc/network/interfaces.d/*". func (p *PersistenceManager) EnsureSourceInclude() error { content, err := os.ReadFile(DebianInterfacesPath) if err != nil { if os.IsNotExist(err) { return nil } return err } str := string(content) if strings.Contains(str, "source /etc/network/interfaces.d/*") || strings.Contains(str, "source-directory /etc/network/interfaces.d") { return nil } // Append source directive newContent := str + "\n# Include extra interface configurations\nsource /etc/network/interfaces.d/*\n" return os.WriteFile(DebianInterfacesPath, []byte(newContent), 0644) } // SaveDebianConfig writes the configuration to disk. func (p *PersistenceManager) SaveDebianConfig() (string, error) { cfg, err := p.GenerateDebianConfig() if err != nil { return "", err } dir := filepath.Dir(DebianVlansCfgPath) if err := os.MkdirAll(dir, 0755); err != nil { return "", fmt.Errorf("failed to create directory %s: %w", dir, err) } if err := os.WriteFile(DebianVlansCfgPath, []byte(cfg), 0644); err != nil { return "", fmt.Errorf("failed to write %s: %w", DebianVlansCfgPath, err) } _ = p.EnsureSourceInclude() return cfg, nil }