package wgconf import ( "bufio" "fmt" "os" "strings" ) // Config represents a parsed WireGuard configuration file. type Config struct { PrivateKey string Address string DNS string Peers []Peer } // Peer represents a WireGuard peer. type Peer struct { PublicKey string Endpoint string AllowedIPs []string } // stripComment removes inline and block comments from a line, keeping spaces intact otherwise. func stripComment(line string) string { line = strings.TrimSpace(line) if line == "" || strings.HasPrefix(line, "#") || strings.HasPrefix(line, ";") { return "" } // Find the first occurrence of '#' or ';' preceded by a whitespace character. // This protects characters like '#' if they are part of a key/value with no leading whitespace. for i := 1; i < len(line); i++ { if (line[i] == '#' || line[i] == ';') && (line[i-1] == ' ' || line[i-1] == '\t') { return strings.TrimSpace(line[:i]) } } return line } // Parse reads a WireGuard .conf file and returns a Config struct. func Parse(path string) (*Config, error) { file, err := os.Open(path) if err != nil { return nil, fmt.Errorf("failed to open config file: %w", err) } defer func() { if err := file.Close(); err != nil { // We use a simple print here because we are in a defer fmt.Printf("warning: failed to close config file %s: %v\n", path, err) } }() cfg := &Config{} var currentPeer *Peer scanner := bufio.NewScanner(file) for scanner.Scan() { line := stripComment(scanner.Text()) if line == "" { continue } if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") { section := strings.ToLower(strings.Trim(line, "[] \t")) if section == "peer" { if currentPeer != nil { cfg.Peers = append(cfg.Peers, *currentPeer) } currentPeer = &Peer{} } else { if currentPeer != nil { cfg.Peers = append(cfg.Peers, *currentPeer) currentPeer = nil } } continue } parts := strings.SplitN(line, "=", 2) if len(parts) != 2 { return nil, fmt.Errorf("invalid line format: %s", line) } key := strings.ToLower(strings.TrimSpace(parts[0])) val := strings.TrimSpace(parts[1]) if currentPeer != nil { switch key { case "publickey": currentPeer.PublicKey = val case "endpoint": currentPeer.Endpoint = val case "allowedips": var ips []string for _, ip := range strings.Split(val, ",") { trimmed := strings.TrimSpace(ip) if trimmed != "" { ips = append(ips, trimmed) } } currentPeer.AllowedIPs = ips } } else { switch key { case "privatekey": cfg.PrivateKey = val case "address": cfg.Address = val case "dns": cfg.DNS = val } } } if currentPeer != nil { cfg.Peers = append(cfg.Peers, *currentPeer) } if err := scanner.Err(); err != nil { return nil, fmt.Errorf("error reading config file: %w", err) } return cfg, nil }