package main import ( "fmt" "os" "strings" "time" ) // CountryData holds the resolved CIDR blocks for a single country. type CountryData struct { Code string // ISO 3166-1 alpha-2 country code Name string // Display name for RouterOS comments CIDRs []string // IPv4 CIDR blocks } // GenerateRSC produces a RouterOS .rsc script file that populates an address list. // The filename includes today's date, e.g. "CountryIPBlocks-2026-05-01.rsc". // Returns the generated filename. func GenerateRSC(countries []CountryData, listName string) (string, error) { now := time.Now() datedName := fmt.Sprintf("%s-%s", listName, now.Format("2006-01-02")) filename := fmt.Sprintf("%s.rsc", datedName) var b strings.Builder // Header with metadata b.WriteString(fmt.Sprintf("# RouterOS GeoIP Address List: %s\n", datedName)) b.WriteString(fmt.Sprintf("# Generated: %s\n", now.UTC().Format(time.RFC3339))) b.WriteString(fmt.Sprintf("# Countries: %d\n", len(countries))) totalCIDRs := 0 for _, c := range countries { totalCIDRs += len(c.CIDRs) } b.WriteString(fmt.Sprintf("# Total entries: %d\n", totalCIDRs)) b.WriteString("#\n") // Each run creates a uniquely-named list (e.g. CountryIPBlocks-2026-05-01). // Workflow: import this file, then atomically swap your firewall rules to // reference the new list, then remove the old dated list. b.WriteString("/ip firewall address-list\n") for _, country := range countries { for _, cidr := range country.CIDRs { b.WriteString(fmt.Sprintf("add address=%s comment=\"%s\" list=%s\n", cidr, country.Name, datedName)) } } if err := os.WriteFile(filename, []byte(b.String()), os.ModePerm); err != nil { return "", fmt.Errorf("failed to write file %q: %v", filename, err) } return filename, nil }