#!/usr/bin/env python3 """Download a pinned official release, verify GitHub's asset SHA256, and unpack it.""" import argparse import gzip import hashlib import json import os import platform import tempfile import urllib.request from pathlib import Path parser = argparse.ArgumentParser() parser.add_argument('--version', default='v1.19.32') parser.add_argument('--output', default='bin/mihomo') args = parser.parse_args() machine = platform.machine() arch = {'x86_64': 'amd64-compatible', 'aarch64': 'arm64'}.get(machine) if arch is None: raise SystemExit(f'Unsupported architecture: {machine}') if not args.version.startswith('v') or any(c not in 'v0123456789.' for c in args.version): raise SystemExit('Use an explicit stable version such as v1.19.32') request = urllib.request.Request(f'https://api.github.com/repos/MetaCubeX/mihomo/releases/tags/{args.version}', headers={'User-Agent': 'ClashWeb-build'}) with urllib.request.urlopen(request, timeout=30) as response: release = json.load(response) name = f'mihomo-linux-{arch}-{args.version}.gz' asset = next((a for a in release['assets'] if a['name'] == name), None) if not asset or not asset.get('digest', '').startswith('sha256:'): raise SystemExit('Release asset or authoritative SHA256 digest is missing; download aborted.') print(f'Downloading {name}', flush=True) with urllib.request.urlopen(asset['browser_download_url'], timeout=120) as response: compressed = response.read(100 * 1024 * 1024 + 1) if len(compressed) > 100 * 1024 * 1024: raise SystemExit('Asset exceeds download limit') if hashlib.sha256(compressed).hexdigest() != asset['digest'].split(':', 1)[1]: raise SystemExit('SHA256 mismatch; download aborted') output = Path(args.output) output.parent.mkdir(parents=True, exist_ok=True) fd, temporary = tempfile.mkstemp(dir=output.parent, prefix='.mihomo-') try: with os.fdopen(fd, 'wb') as stream: stream.write(gzip.decompress(compressed)) os.chmod(temporary, 0o755) os.replace(temporary, output) finally: if os.path.exists(temporary): os.unlink(temporary) print(f'Verified {asset["digest"]}; installed to {output}')