|
20 | 20 | import hashlib |
21 | 21 | import struct |
22 | 22 | import collections |
| 23 | +import ssl |
23 | 24 |
|
24 | 25 | # Python 2/3 compatibility for urllib and input |
25 | 26 | try: |
@@ -63,6 +64,41 @@ def urlretrieve(url, filename, reporthook=None): |
63 | 64 |
|
64 | 65 | IS_WINDOWS = (os.name == 'nt') |
65 | 66 |
|
| 67 | +# Handle SSL certificate verification on Windows (especially Arm64/minimal installs). |
| 68 | +if IS_WINDOWS: |
| 69 | + try: |
| 70 | + # Try to use Windows Certificate Store directly to avoid dependency on certifi. |
| 71 | + def _create_windows_context(): |
| 72 | + ctx = ssl.create_default_context() |
| 73 | + try: |
| 74 | + # ROOT and CA stores contain the trusted anchors on Windows. |
| 75 | + for storename in ["ROOT", "CA"]: |
| 76 | + for cert, encoding, trust in ssl.enum_certificates(storename): |
| 77 | + if encoding == "x509_asn": |
| 78 | + try: |
| 79 | + # Convert DER to PEM and load into context. |
| 80 | + ctx.load_verify_locations(cdata=ssl.DER_cert_to_PEM_cert(cert)) |
| 81 | + except Exception: |
| 82 | + pass |
| 83 | + except Exception: |
| 84 | + pass |
| 85 | + return ctx |
| 86 | + |
| 87 | + # Test if it works, then set as default context creator for urllib. |
| 88 | + _test_ctx = _create_windows_context() |
| 89 | + ssl._create_default_https_context = _create_windows_context |
| 90 | + except Exception: |
| 91 | + # Fallback to certifi if available, then to unverified as a last resort. |
| 92 | + try: |
| 93 | + import certifi |
| 94 | + os.environ['SSL_CERT_FILE'] = certifi.where() |
| 95 | + except ImportError: |
| 96 | + try: |
| 97 | + if hasattr(ssl, '_create_unverified_context'): |
| 98 | + ssl._create_default_https_context = ssl._create_unverified_context |
| 99 | + except Exception: |
| 100 | + pass |
| 101 | + |
66 | 102 | try: |
67 | 103 | DEVNULL = subprocess.DEVNULL # Python 3.3+ |
68 | 104 | except AttributeError: |
@@ -3522,10 +3558,37 @@ def find_image_link(releases, target_zst, target_xz): |
3522 | 3558 |
|
3523 | 3559 | log("Extracting " + ova_file) |
3524 | 3560 | extract_start_time = time.time() |
| 3561 | + |
| 3562 | + def cmd_exists(cmd): |
| 3563 | + test_cmd = [cmd, '--version'] |
| 3564 | + try: |
| 3565 | + startupinfo = None |
| 3566 | + if IS_WINDOWS: |
| 3567 | + startupinfo = subprocess.STARTUPINFO() |
| 3568 | + startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW |
| 3569 | + subprocess.call(test_cmd, stdout=DEVNULL, stderr=DEVNULL, startupinfo=startupinfo) |
| 3570 | + return True |
| 3571 | + except: |
| 3572 | + return False |
| 3573 | + |
3525 | 3574 | if ova_file.endswith('.zst'): |
| 3575 | + if not cmd_exists('zstd'): |
| 3576 | + msg = "Error: 'zstd' command not found. This is required to extract the image.\n" |
| 3577 | + if IS_WINDOWS: |
| 3578 | + msg += "Please install it via winget: winget install facebook.zstd\n" |
| 3579 | + else: |
| 3580 | + msg += "Please install it via your package manager (e.g. apt install zstd, brew install zstd)\n" |
| 3581 | + fatal(msg) |
3526 | 3582 | if subprocess.call(['zstd', '-d', ova_file, '-o', qcow_name]) != 0: |
3527 | 3583 | fatal("zstd extraction failed") |
3528 | 3584 | elif ova_file.endswith('.xz'): |
| 3585 | + if not cmd_exists('xz'): |
| 3586 | + msg = "Error: 'xz' command not found. This is required to extract the image.\n" |
| 3587 | + if IS_WINDOWS: |
| 3588 | + msg += "Please install it via winget: winget install Tukaani.XZ\n" |
| 3589 | + else: |
| 3590 | + msg += "Please install it via your package manager (e.g. apt install xz-utils, brew install xz)\n" |
| 3591 | + fatal(msg) |
3529 | 3592 | with open(qcow_name, 'wb') as f: |
3530 | 3593 | if subprocess.call(['xz', '-d', '-c', ova_file], stdout=f) != 0: |
3531 | 3594 | fatal("xz extraction failed") |
|
0 commit comments