package wireguard import ( "bufio" "fmt" "os" "strconv" "strings" "gitea.perlbach24.de/scripte/wireguard-ui-multi/internal/server" ) // ParsedLegacyConfig is the parsed result of a legacy wg-quick style config file. type ParsedLegacyConfig struct { PrivateKey string Address string // e.g. "10.10.0.1/24" (used as AddressRange for the new Server) ListenPort int DNS string MTU int Peers []ParsedLegacyPeer } // ParsedLegacyPeer is a single [Peer] section from a legacy config. type ParsedLegacyPeer struct { Name string PublicKey string PresharedKey string AllowedIPs string Endpoint string PersistentKeepalive int } // ParseLegacyConfig reads and parses a wg-quick INI-style config file (e.g. /etc/wireguard/wg0.conf). func ParseLegacyConfig(path string) (*ParsedLegacyConfig, error) { f, err := os.Open(path) if err != nil { return nil, err } defer f.Close() cfg := &ParsedLegacyConfig{} var curSection string var curPeer *ParsedLegacyPeer // pendingName holds a comment found on the line(s) immediately before a // "[Peer]" header, e.g. "# client-laptop". wg-quick has no native peer // name field, so this is the only place a human-readable name can come // from; it's consumed (and reset) as soon as the next [Peer] section starts. var pendingName string scanner := bufio.NewScanner(f) for scanner.Scan() { line := strings.TrimSpace(scanner.Text()) if line == "" { continue } if strings.HasPrefix(line, "#") || strings.HasPrefix(line, ";") { pendingName = strings.TrimSpace(strings.TrimLeft(line, "#;")) continue } // Strip inline comments. if idx := strings.IndexAny(line, "#;"); idx >= 0 { line = strings.TrimSpace(line[:idx]) if line == "" { continue } } if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") { section := strings.ToLower(strings.TrimSpace(line[1 : len(line)-1])) switch section { case "interface": curSection = "interface" curPeer = nil case "peer": curSection = "peer" cfg.Peers = append(cfg.Peers, ParsedLegacyPeer{Name: pendingName}) curPeer = &cfg.Peers[len(cfg.Peers)-1] default: curSection = "" curPeer = nil } pendingName = "" continue } key, value, ok := splitKV(line) if !ok { continue } switch curSection { case "interface": switch { case strings.EqualFold(key, "PrivateKey"): cfg.PrivateKey = value case strings.EqualFold(key, "Address"): cfg.Address = value case strings.EqualFold(key, "ListenPort"): cfg.ListenPort, _ = strconv.Atoi(value) case strings.EqualFold(key, "DNS"): cfg.DNS = value case strings.EqualFold(key, "MTU"): cfg.MTU, _ = strconv.Atoi(value) } case "peer": if curPeer == nil { continue } switch { case strings.EqualFold(key, "PublicKey"): curPeer.PublicKey = value case strings.EqualFold(key, "PresharedKey"): curPeer.PresharedKey = value case strings.EqualFold(key, "AllowedIPs"): curPeer.AllowedIPs = value case strings.EqualFold(key, "Endpoint"): curPeer.Endpoint = value case strings.EqualFold(key, "PersistentKeepalive"): curPeer.PersistentKeepalive, _ = strconv.Atoi(value) } } } if err := scanner.Err(); err != nil { return nil, err } return cfg, nil } func splitKV(line string) (key, value string, ok bool) { idx := strings.Index(line, "=") if idx < 0 { return "", "", false } key = strings.TrimSpace(line[:idx]) value = strings.TrimSpace(line[idx+1:]) if key == "" { return "", "", false } return key, value, true } // ImportLegacyServer parses legacyConfPath and creates a corresponding Server + its Peers // in the given store, using serverName and interfaceName for the new Server record. // Returns the new server's ID. func ImportLegacyServer(store *server.Store, legacyConfPath, serverName, interfaceName string) (int64, error) { parsed, err := ParseLegacyConfig(legacyConfPath) if err != nil { return 0, fmt.Errorf("parse legacy config %q: %w", legacyConfPath, err) } pubKey, err := PublicFromPrivate(parsed.PrivateKey) if err != nil { return 0, fmt.Errorf("derive public key: %w", err) } mtu := parsed.MTU if mtu == 0 { mtu = 1420 } srv := &server.Server{ Name: serverName, InterfaceName: interfaceName, ListenPort: parsed.ListenPort, PrivateKey: parsed.PrivateKey, PublicKey: pubKey, AddressRange: parsed.Address, DNS: parsed.DNS, MTU: mtu, Enabled: true, } serverID, err := store.CreateServer(srv) if err != nil { return 0, fmt.Errorf("create server: %w", err) } for i, pp := range parsed.Peers { name := pp.Name if name == "" { name = fmt.Sprintf("peer-%d", i+1) } peer := &server.Peer{ ServerID: serverID, Name: name, PublicKey: pp.PublicKey, PresharedKey: pp.PresharedKey, AllowedIPs: pp.AllowedIPs, Endpoint: pp.Endpoint, PersistentKeepalive: pp.PersistentKeepalive, Enabled: true, } if _, err := store.CreatePeer(peer); err != nil { return serverID, fmt.Errorf("create peer %q (index %d): %w", name, i, err) } } return serverID, nil }