package app import ( "fmt" "sort" "strings" "time" ) func snapshotNftSetToFile(setName, dst string) (int, error) { elems, err := readNftSetElements(setName) if err != nil { return 0, err } if err := writeLinesFile(dst, elems); err != nil { return 0, err } return len(elems), nil } func readNftSetElements(setName string) ([]string, error) { out, stderr, code, err := runCommandTimeout( 8*time.Second, "nft", "list", "set", "inet", "agvpn", setName, ) if err != nil || code != 0 { msg := strings.ToLower(strings.TrimSpace(out + " " + stderr)) if strings.Contains(msg, "no such file") || strings.Contains(msg, "not found") || strings.Contains(msg, "does not exist") { return nil, nil } if err != nil { return nil, fmt.Errorf("nft list set %s failed: %w", setName, err) } return nil, fmt.Errorf("nft list set %s failed: %s", setName, strings.TrimSpace(stderr)) } return parseNftSetElementsText(out), nil } func parseNftSetElementsText(raw string) []string { idx := strings.Index(raw, "elements =") if idx < 0 { return nil } chunk := raw[idx:] open := strings.Index(chunk, "{") if open < 0 { return nil } body := chunk[open+1:] closeIdx := strings.Index(body, "}") if closeIdx >= 0 { body = body[:closeIdx] } body = strings.ReplaceAll(body, "\r", " ") body = strings.ReplaceAll(body, "\n", " ") seen := map[string]struct{}{} out := make([]string, 0, 1024) for _, tok := range strings.Split(body, ",") { val := strings.TrimSpace(tok) if val == "" { continue } if _, ok := seen[val]; ok { continue } seen[val] = struct{}{} out = append(out, val) } sort.Strings(out) return out }