├── .github └── workflows │ └── mirrorz.yml ├── README.md └── oh-my-mirrorz.py /.github/workflows/mirrorz.yml: -------------------------------------------------------------------------------- 1 | name: Trigger MirrorZ deploy 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | 7 | jobs: 8 | trigger_mirrorz: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Trigger mirrorz deploy 12 | uses: peter-evans/repository-dispatch@v1 13 | with: 14 | token: ${{ secrets.MIRRORZ_DEP_TOKEN }} 15 | repository: mirrorz-org/mirrorz 16 | event-type: dependency 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # oh-my-mirrorz 2 | 3 | A script for speed testing for each mirrors providing mirrorz service. 4 | 5 | ## How to use 6 | 7 | Make sure you have `curl` installed. Also at least Python 3.5 is required. 8 | 9 | Use 10 | 11 | ``` 12 | curl https://mirrorz.org/oh-my-mirrorz.py | python3 13 | ``` 14 | 15 | or 16 | 17 | ``` 18 | curl -O https://mirrorz.org/oh-my-mirrorz.py 19 | chmod +x ./oh-my-mirrorz.py 20 | ./oh-my-mirrorz.py 21 | ``` 22 | 23 | If you get stuck when loading meta data, you may issue `Ctrl+C` to skip one mirrors. 24 | 25 | ## Proxy 26 | 27 | This script sends two kinds of http request: python `urllib.request` (built-in) to get metadata and `curl` to do actual speed test. 28 | 29 | Some sites provide their meta data (`mirrorz.json`) in a place that is hard to access, which you may use a proxy. In this case you may set `HTTP_PROXY` or `HTTPS_PROXY`, which is used by python `urllib.request`. 30 | 31 | In some settings you may use a proxy to do actual downloading, in this case you may set `http_proxy` or `https_proxy`. Note that these lowercase envs are also read by python `urllib.request`. 32 | -------------------------------------------------------------------------------- /oh-my-mirrorz.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # vim: expandtab ts=4 sts=4 sw=4 4 | 5 | import subprocess 6 | import urllib.request 7 | import json 8 | import os 9 | import argparse 10 | 11 | VERSION = '' 12 | CURL_VERSION = '' 13 | UA_URL = '' 14 | 15 | big = { 16 | 'centos': '/7/isos/x86_64/CentOS-7-x86_64-Everything-2009.iso', 17 | 'centos-vault': '/6.0/isos/x86_64/CentOS-6.0-x86_64-LiveDVD.iso', 18 | 'opensuse': '/distribution/leap/15.5/iso/openSUSE-Leap-15.5-DVD-x86_64-Media.iso', 19 | 'ubuntu-releases': '/22.04/ubuntu-22.04.3-desktop-amd64.iso', 20 | 'debian-cd': '/current/amd64/iso-bd/debian-edu-12.1.0-amd64-BD-1.iso', 21 | 'kali-images': '/kali-2023.2/kali-linux-2023.2-live-amd64.iso', 22 | 'CTAN': '/systems/texlive/Images/texlive.iso', 23 | 'blackarch': '/iso/blackarch-linux-full-2023.04.01-x86_64.iso', 24 | 'archlinux': '/iso/latest/archlinux-x86_64.iso', 25 | 'ubuntu': '/indices/md5sums.gz', 26 | 'debian': '/ls-lR.gz', 27 | } 28 | 29 | # filled by CI 30 | mirrors = [] 31 | 32 | map = {} 33 | res = {} 34 | 35 | def check_curl(): 36 | global CURL_VERSION 37 | try: 38 | res = subprocess.run(['curl', '--version'], stdout=subprocess.PIPE) 39 | out = res.stdout.decode('utf-8') 40 | CURL_VERSION = out.split()[1] 41 | print(out) 42 | return 0 43 | except: 44 | print("No curl found!") 45 | return -1 46 | 47 | def site_info(url): 48 | user_agent = 'oh-my-mirrorz/%s (+https://github.com/mirrorz-org/oh-my-mirrorz) %s %s' % (VERSION, UA_URL, "urllib/" + urllib.request.__version__) 49 | headers = { 50 | 'User-Agent': user_agent 51 | } 52 | 53 | request = urllib.request.Request(url, headers=headers) 54 | with urllib.request.urlopen(request, timeout=10) as response: 55 | return json.loads(response.read().decode('utf-8')) 56 | 57 | 58 | def speed_test(url, args): 59 | opt = '-qs' 60 | if args.ipv4: 61 | opt += '4' 62 | elif args.ipv6: 63 | opt += '6' 64 | res = subprocess.run(['curl', opt, '-o', os.devnull, '-w', '%{http_code} %{speed_download}', 65 | '-m'+str(args.time), '-A', 'oh-my-mirrorz/%s (+https://github.com/mirrorz-org/oh-my-mirrorz) %s curl/%s' % (VERSION, UA_URL, CURL_VERSION), url], stdout=subprocess.PIPE) 66 | code, speed = res.stdout.decode('utf-8').split() 67 | return int(code), float(speed) 68 | 69 | def human_readable_speed(speed): 70 | scale = ['B/s', 'KiB/s', 'MiB/s', 'GiB/s', 'TiB/s'] 71 | i = 0 72 | while (speed > 1024.0): 73 | i += 1 74 | speed /= 1024.0 75 | return f'{speed:.2f} {scale[i]}' 76 | 77 | def main(): 78 | parser = argparse.ArgumentParser() 79 | group = parser.add_mutually_exclusive_group() 80 | group.add_argument("-4", "--ipv4", help="IPv4 only when speed testing", action="store_true") 81 | group.add_argument("-6", "--ipv6", help="IPv6 only when speed testing", action="store_true") 82 | parser.add_argument("-t", "--time", type=int, default=5, choices=[3, 5, 10, 30, 60], help="Duration of a speed test for one mirror (default: %(default)d)") 83 | args = parser.parse_args() 84 | 85 | if check_curl() != 0: 86 | exit(-1) 87 | for url in mirrors: 88 | try: 89 | map[url] = site_info(url) 90 | print('Loaded', map[url]['site']['abbr'], ':', map[url]['site']['url']) 91 | except: 92 | print('! Failed to load', url) 93 | pass 94 | 95 | print() # one empty line to separate metadata and speedtest 96 | 97 | for _, v in map.items(): 98 | uri_list = [] 99 | if 'big' in v['site']: 100 | uri_list.append(v['site']['big']) 101 | for r, u in big.items(): 102 | for m in v['mirrors']: 103 | if m['cname'] == r: 104 | uri_list.append(m['url'] + u) 105 | if len(uri_list) == 0: 106 | print('! No big file found for', v['site']['abbr'], v['site']['url']) 107 | continue 108 | 109 | for uri in uri_list: 110 | res[v['site']['abbr']] = 0 111 | print('Speed testing', v['site']['abbr'], uri if uri.startswith("http") else v['site']['url'] + uri, '... ', end='', flush=True) 112 | code, speed = speed_test(v['site']['url'] + uri, args) 113 | if code != 200: 114 | print('HTTP Code', code, 'Speed', human_readable_speed(speed)) 115 | else: 116 | print(human_readable_speed(speed)) 117 | res[v['site']['abbr']] = speed 118 | break 119 | 120 | print() # one empty line to separate speedtest and result 121 | 122 | print('RANK', 'ABBR', 'SPEED', sep='\t\t') 123 | for i, (k, v) in enumerate(sorted(res.items(), key = lambda x: x[1], reverse=True)): 124 | print(f'{i:02d}:', k, human_readable_speed(v), sep='\t\t') 125 | 126 | if __name__ == '__main__': 127 | main() 128 | --------------------------------------------------------------------------------