#!/usr/bin/env ruby # 2018 Luke Shumaker require 'json' require 'net/http' require 'optparse' require 'set' devnull = open("/dev/null") begin OptionParser.new do |parser| parser.on("-h", "--help", "Display this help") do puts parser exit end end.parse! rescue OptionParser::ParseError => e STDERR.puts "#{$0}: #{e}" STDERR.puts "Try '#{$0} --help' for more information." exit 2 end unless ARGV.empty? STDERR.puts "#{$0}: extra argument '#{ARGV[0]}'" STDERR.puts "Try '#{$0} --help' for more information." exit 2 end # Get a listing of hostnames that have HTTP Arch Linux ARM mirrors data = JSON::parse(Net::HTTP.post( URI("https://archlinuxarm.org/data/mirrors/list"), "", { 'Referer' => 'https://archlinuxarm.org/about/mirrors', 'X-Requested-With' => 'XMLHttpRequest', }).body) hosts = data["data"].map{|m|m[2]} # Get a listing of rsync URLs for each host modulelist_procs = {} hosts.each do |host| modulelist_procs[host] ||= IO.popen(["timeout", "2s", "rsync", "--password-file", "/dev/null", "#{host}::"], :err => devnull) end rsync_urls = Set.new() modulelist_procs.each do |host,modulelist_proc| modules = modulelist_proc.read.lines.map{|line| line.chomp.sub(/\s.*/, '') } modulelist_proc.close rsync_urls.merge(modules.map{|m| "rsync://#{host}/#{m}/"}) end # Get a listing of the top-level files in each listing_procs = {} rsync_urls.each do |url| listing_procs[url] ||= IO.popen(["rsync", "--password-file", "/dev/null", "--no-motd", "--list-only", "--", url], :err => devnull) end listings = {} listing_procs.map.each do |url,listing_proc| listings[url] = listing_proc.read.lines.map{|line|line.chomp.sub(/.*\s/,'')} listing_proc.close end # Filter for listings that look like Arch Linux ARM mirrors requires = ['aarch64', 'arm', 'armv6h', 'armv7h', 'os'] rsync_urls = rsync_urls.select{|url| requires.all?{|file|listings[url].include?(file)} } # Print the results rsync_urls.sort.each do |url| puts url end