#!/usr/bin/env python3 """Finish isolated Classic prerequisites. Never starts a realm or edits live data.""" import argparse import hashlib import json import pathlib import shutil import subprocess import time parser = argparse.ArgumentParser() parser.add_argument('--root', required=True) args = parser.parse_args() root = pathlib.Path(args.root).resolve() def status(stage, **extra): value = {'at': time.time(), 'stage': stage, 'cutover': False, **extra} temporary = root / 'prerequisites-status.tmp' temporary.write_text(json.dumps(value, indent=2)) temporary.replace(root / 'prerequisites-status.json') print(stage, flush=True) def run(*command): return subprocess.check_output(command, text=True).strip() try: status('waiting for Classic compilation and client transfer') deadline = time.monotonic() + 6 * 3600 while True: state = json.loads(run('docker', 'inspect', 'azerothai-classic-build'))[0]['State'] if not state['Running']: if state['ExitCode'] != 0: raise RuntimeError('Classic build failed; inspect docker logs azerothai-classic-build') break if time.monotonic() > deadline: raise RuntimeError('Classic build exceeded six-hour observation window') time.sleep(30) manifest = json.loads((root / 'client-manifest.json').read_text()) while not all((root / 'client' / f).exists() and (root / 'client' / f).stat().st_size == v['size'] for f, v in manifest.items()): if time.monotonic() > deadline: raise RuntimeError('Client transfer incomplete after six hours') time.sleep(30) status('verifying transferred client archives') for filename, expected in manifest.items(): with (root / 'client' / filename).open('rb') as stream: actual = hashlib.file_digest(stream, 'sha256').hexdigest() if actual != expected['sha256']: raise RuntimeError('Client archive checksum mismatch: ' + filename) if shutil.disk_usage(root).free < 8 * 1024**3: raise RuntimeError('Less than 8 GiB free for extracted Vanilla data') output = root / 'extracted'; output.mkdir(exist_ok=True) status('extracting Vanilla maps and navigation data; realm not switched') with (root / 'data-extraction.log').open('w') as log: subprocess.run([ 'docker', 'run', '--name', 'azerothai-classic-extract', '--cpus', '2', '--memory', '4g', '--user', '1000:1000', '-i', '-v', str(root) + ':/work', '-w', '/work/run/bin/tools', 'azerothai-classic-builder:local', 'bash', './ExtractResources.sh', '/work/client', '/work/extracted' ], input='y\n2\n\nn\nn\n\n', text=True, stdout=log, stderr=subprocess.STDOUT, check=True) counts = {name: sum(1 for f in (output / name).rglob('*') if f.is_file()) for name in ('dbc', 'maps', 'vmaps', 'mmaps')} if not all(counts.values()): raise RuntimeError('An extracted data directory is empty: ' + str(counts)) status('prerequisites extracted; database, connector, roster and gameplay validation remain', files=counts) except Exception as error: status('blocked', error=str(error)) raise