From 35a65888254597cfac831423dc17d6a6d45a61e4 Mon Sep 17 00:00:00 2001 From: Ruscher Date: Wed, 13 Sep 2023 18:28:23 -0300 Subject: [PATCH 001/369] Add BigLinux --- quickget | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/quickget b/quickget index 4ea03d1fb8..f09168ba1f 100755 --- a/quickget +++ b/quickget @@ -61,6 +61,7 @@ function pretty_name() { archlinux) PRETTY_NAME="Arch Linux";; archcraft) PRETTY_NAME="Archcraft";; arcolinux) PRETTY_NAME="Arco Linux";; + biglinux) PRETTY_NAME="BigLinux";; blendos) PRETTY_NAME="BlendOS";; cachyos) PRETTY_NAME="CachyOS";; centos-stream) PRETTY_NAME="CentOS Stream";; @@ -211,6 +212,7 @@ function os_support() { archcraft \ arcolinux \ batocera \ + biglinux \ blendos \ bodhi \ bunsenlabs \ @@ -418,6 +420,10 @@ function editions_arcolinux() { echo large small } +function releases_biglinux() { + echo kde +} + function releases_blendos() { # Pull the rss feed @@ -1270,6 +1276,15 @@ function get_bunsenlabs() { echo "${URL}/${ISO} ${HASH}" } +function get_biglinux() { + local HASH="" + local ISO="" + local URL="https://iso.biglinux.com.br/" + ISO=$(grep -Eo 'biglinux_[0-9]{4}(-[0-9]{2}){2}_k[0-9]{2,3}.iso' <(wget -q -O- ${URL}) | sort -u | tail -n2 | head -n1) + HASH=$(curl -s ${URL}${ISO}.md5 | grep -Eo '[[:alnum:]]{32}') + echo "${URL}${ISO} ${HASH}" +} + function get_blendos() { local HASH="" From 286ac1bcfe7e98690be44b28e36ca8f3384a6ad7 Mon Sep 17 00:00:00 2001 From: Doc Norberg Date: Sat, 18 Nov 2023 18:30:19 -0800 Subject: [PATCH 002/369] Add options to directly specify VM width and height on cmdline or config --- quickemu | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/quickemu b/quickemu index ee519f7d39..c00388c055 100755 --- a/quickemu +++ b/quickemu @@ -770,7 +770,11 @@ function vm_boot() { # Try and coerce the display resolution for Linux guests only. if [ "${guest_os}" == "linux" ]; then - VIDEO="${VIDEO},xres=${X_RES},yres=${Y_RES}" + if [ -n "${WIDTH}" ] && [ -n "${HEIGHT}" ]; then + VIDEO="${VIDEO},xres=${WIDTH},yres=${HEIGHT}" + else + VIDEO="${VIDEO},xres=${X_RES},yres=${Y_RES}" + fi fi # Allocate VRAM to VGA devices @@ -1354,6 +1358,8 @@ function usage() { echo " --snapshot info : Show disk/snapshot info." echo " --status-quo : Do not commit any changes to disk/snapshot." echo " --viewer : Choose an alternative viewer. @Options: 'spicy' (default), 'remote-viewer', 'none'" + echo " --width : Set VM screen width. Does nothing without --height" + echo " --height : Set VM screen height. Does nothing without --width" echo " --ssh-port : Set ssh-port manually" echo " --spice-port : Set spice-port manually" echo " --public-dir : Expose share directory. @Options: '' (default: xdg-user-dir PUBLICSHARE), '', 'none'" @@ -1491,6 +1497,8 @@ secureboot="off" tpm="off" usb_devices=() viewer="spicy" +width="" +height="" ssh_port="" spice_port="" public_dir="" @@ -1533,6 +1541,8 @@ VMDIR="" VMNAME="" VMPATH="" VIEWER="" +WIDTH="" +HEIGHT="" SSH_PORT="" SPICE_PORT="" MONITOR="" @@ -1651,6 +1661,14 @@ else VIEWER="${2}" shift shift;; + -width|--width) + WIDTH="${2}" + shift; + shift;; + -height|--height) + HEIGHT="${2}" + shift; + shift;; -ssh-port|--ssh-port) SSH_PORT="${2}" shift; @@ -1767,6 +1785,11 @@ if [ -n "${VM}" ] && [ -e "${VM}" ]; then fi viewer_param_check + if [ -z "${WIDTH}" ] || [ -z "${HEIGHT}" ] ; then + WIDTH="${width}" + HEIGHT="${height}" + fi + # Set the default 3D acceleration. if [ -z "${gl}" ]; then gl="on" From 59865c894e00778586540e3d5873ded351cc4516 Mon Sep 17 00:00:00 2001 From: Alex Genovese Date: Fri, 22 Dec 2023 11:04:00 +0100 Subject: [PATCH 003/369] add disk health check --- quickemu | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/quickemu b/quickemu index c00388c055..0f1cb5ea57 100755 --- a/quickemu +++ b/quickemu @@ -1749,6 +1749,14 @@ if [ -n "${VM}" ] && [ -e "${VM}" ]; then exit 1 fi + DiskChkMsg="$("$QEMU_IMG" check -q "${disk_img}" 2>&1)" + + if [[ $DiskChkMsg ]]; then + echo; echo "ERROR! disk.qcow2 has problems. Try qemu-img check --help." + echo; echo "$DiskChkMsg" ; echo + exit 1 + fi + VMDIR=$(dirname "${disk_img}") VMNAME=$(basename "${VM}" .conf) VMPATH=$(realpath "$(dirname "${VM}")") From d680eecbdc7c11c1e66defabbd79f058740659cf Mon Sep 17 00:00:00 2001 From: zen0bit Date: Tue, 9 Apr 2024 04:04:58 +0200 Subject: [PATCH 004/369] Revert "add disk health check" This reverts commit 0e4fc7e79aad55d9e335c3f61ec8ecd58d8d082a. --- quickemu | 8 -------- 1 file changed, 8 deletions(-) diff --git a/quickemu b/quickemu index 0f1cb5ea57..c00388c055 100755 --- a/quickemu +++ b/quickemu @@ -1749,14 +1749,6 @@ if [ -n "${VM}" ] && [ -e "${VM}" ]; then exit 1 fi - DiskChkMsg="$("$QEMU_IMG" check -q "${disk_img}" 2>&1)" - - if [[ $DiskChkMsg ]]; then - echo; echo "ERROR! disk.qcow2 has problems. Try qemu-img check --help." - echo; echo "$DiskChkMsg" ; echo - exit 1 - fi - VMDIR=$(dirname "${disk_img}") VMNAME=$(basename "${VM}" .conf) VMPATH=$(realpath "$(dirname "${VM}")") From 8ab2590e5b106d907fc4b98085179f84dd88b9eb Mon Sep 17 00:00:00 2001 From: Liam <33645555+lj3954@users.noreply.github.com> Date: Sun, 24 Dec 2023 01:01:53 -0600 Subject: [PATCH 005/369] inbuilt macOS downloading * Fix quickget show-iso-url and test-iso-url creating unnecessary directory * Beautify output, add show-iso-url and test-iso-url for Windows (fully) and macOS (sorta) * (NON-FUNCTIONAL) macrecovery shell script. * Semi-functional (although incomplete) macrecovery shell script Rough draft. To be completed, cleaned up and simplified (very much so) hoping to merge into quickemu & replace the python macrecovery dependency. * macrecovery shell script now successfully downloads the image. TODO: Verification * Merged macrecovery functions into quickget. Chunkcheck (C) to replace macrecovery's image verification Chunkcheck written by MCJack123: https://gist.github.com/MCJack123/943eaca762730ca4b7ae460b731b68e7 * Replace C chunkcheck binary with the Python equivalent. Re-add python to dependencies. * force macOS guests to usually boot with core counts which are powers of 2; fix #865 * Add support for macOS Sonoma * Fix issue where script would be unable to find chunkcheck if installed system-wide * Update README verbiage * Add headers to web_get function; macOS can now be downloaded via aria2; clean up code & output * Add support for macOS Sonoma * Fix use of wrong operator (>) which touches a file * Small correction to README * macOS switched from wget to default downloader (aria2/wget) * Replace wget with cURL for downloading macOS chunklist file * Fix variable naming in generate_id function --- README.md | 10 +- chunkcheck | 64 +++++++ macrecovery | 518 ---------------------------------------------------- quickemu | 11 +- quickget | 126 +++++++++---- 5 files changed, 174 insertions(+), 555 deletions(-) create mode 100755 chunkcheck delete mode 100755 macrecovery diff --git a/README.md b/README.md index 7c5bd7bd7b..bd999b8fe1 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,7 @@ QEMU](https://img.youtube.com/vi/AOTYWEgw0hI/0.jpg)](https://www.youtube.com/wat - [LSB](https://wiki.linuxfoundation.org/lsb/start) - [procps](https://gitlab.com/procps-ng/procps) - [python3](https://www.python.org/) -- [macrecovery](https://github.com/acidanthera/OpenCorePkg/tree/master/Utilities/macrecovery) +- [chunkcheck](https://gist.github.com/MCJack123/943eaca762730ca4b7ae460b731b68e7) - [mkisofs](http://cdrtools.sourceforge.net/private/cdrecord.html) - [usbutils](https://github.com/gregkh/usbutils) - [util-linux](https://github.com/karelzak/util-linux) @@ -373,8 +373,8 @@ quickget macos catalina quickemu --vm macos-catalina.conf ``` -macOS `high-sierra`, `mojave`, `catalina`, `big-sur`, `monterey` and -`ventura` are supported. +macOS `high-sierra`, `mojave`, `catalina`, `big-sur`, `monterey`, `ventura` and +`sonoma` are supported. - Use cursor keys and enter key to select the **macOS Base System** - From **macOS Utilities** @@ -384,7 +384,7 @@ macOS `high-sierra`, `mojave`, `catalina`, `big-sur`, `monterey` and click **Erase**. - Enter a `Name:` for the disk - If you are installing macOS Mojave or later (Catalina, Big - Sur, Monterey and Ventura), choose any of the APFS options + Sur, Monterey, Ventura and Sonoma), choose any of the APFS options as the filesystem. MacOS Extended may not work. - Click **Erase**. - Click **Done**. @@ -462,6 +462,7 @@ There are some considerations when running macOS via Quickemu. - Big Sur - Monterey - Ventura + - Sonoma (Not recommended) - `quickemu` will automatically download the required [OpenCore](https://github.com/acidanthera/OpenCorePkg) bootloader and OVMF firmware from [OSX-KVM](https://github.com/kholia/OSX-KVM). @@ -910,6 +911,7 @@ Useful reference that assisted the development of Quickemu. - - - + - - - - diff --git a/chunkcheck b/chunkcheck new file mode 100755 index 0000000000..af8c9ab98c --- /dev/null +++ b/chunkcheck @@ -0,0 +1,64 @@ +from pathlib import Path +import struct +import hashlib +import argparse +v1_prod_pubkey = 0xC3E748CAD9CD384329E10E25A91E43E1A762FF529ADE578C935BDDF9B13F2179D4855E6FC89E9E29CA12517D17DFA1EDCE0BEBF0EA7B461FFE61D94E2BDF72C196F89ACD3536B644064014DAE25A15DB6BB0852ECBD120916318D1CCDEA3C84C92ED743FC176D0BACA920D3FCF3158AFF731F88CE0623182A8ED67E650515F75745909F07D415F55FC15A35654D118C55A462D37A3ACDA08612F3F3F6571761EFCCBCC299AEE99B3A4FD6212CCFFF5EF37A2C334E871191F7E1C31960E010A54E86FA3F62E6D6905E1CD57732410A3EB0C6B4DEFDABE9F59BF1618758C751CD56CEF851D1C0EAA1C558E37AC108DA9089863D20E2E7E4BF475EC66FE6B3EFDCF +# v2_prod_pubkey = 0xCB45C5E53217D4499FB80B2D96AA4F964EB551F1DA4EBFA4F5E23F87BFE82FC113590E536757F329D6EAD1F267771EE342F5A5E61514DD3D3383187E663929D577D94648F262EBA1157E152DB5273D10AE3A6A058CB9CD64D01267DAC82ED3B7BC1631D078C911414129CDAAA0FFB0A8E2A7ADD6F32FB09A7E98D259BFF6ED10808D1BDA58CAF7355DFF1A085A18B11657D2617447BF657140D599364E5AC8E626276AC03BC2417831D9E61B25154AFE9F2D8271E9CE22D2783803083A5A7A575774688721097DC5E4B32D118CF6317A7083BA15BA608430A8C8C6B7DA2D932D81F571603A9363AC0197AB670242D9C9180D97A10900F11FE3D9246CF14F0883 +# v2_dev_pubkey = 0xB372CEC9E05E71FB3FAA08C34E3256FB312EA821638A243EF8A5DEA46FCDA33F00F88FC2933FB276D37B914F89BAD5B5D75771E342265B771995AE8F43B4DFF3F21A877FE777A8B419587C8718D36204FA1922A575AD5207D5D6B8C10F84DDCA661B731E7E7601D64D4A894F487FE1AA1DDC2A1697A3553B1DD85D5750DF2AA9D988E83C4C70BBBE4747219F9B92B199FECB16091896EBB441606DEC20F446249D5568BB51FC87BA7F85E6295FBE811B0A314408CD31921C360608A0FF7F87BD733560FE1C96E472834CAB6BE016C35727754273125089BE043FD3B26F0B2DE141E05990CE922F1702DA0A2F4E9F8760D0FA712DDB9928E0CDAC14501ED5E2C3 + +ChunkListHeader = struct.Struct('<4sIBBBxQQQ') +assert ChunkListHeader.size == 0x24 + +Chunk = struct.Struct(' 0 + assert chunk_offset == 0x24 + assert signature_offset == chunk_offset + Chunk.size * chunk_count + for i in range(chunk_count): + data = f.read(Chunk.size) + hash_ctx.update(data) + chunk_size, chunk_sha256 = Chunk.unpack(data) + yield chunk_size, chunk_sha256 + digest = hash_ctx.digest() + if signature_method == 1: + data = f.read(256) + assert len(data) == 256 + signature = int.from_bytes(data, 'little') + plaintext = 0x1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff003031300d0609608648016503040201050004200000000000000000000000000000000000000000000000000000000000000000 | int.from_bytes(digest, 'big') + assert pow(signature, 0x10001, v1_prod_pubkey) == plaintext + elif signature_method == 2: + data = f.read(32) + assert data == digest + else: + raise NotImplementedError + assert f.read(1) == b'' + +def check_chunklist(path, chunklist_path): + with open(path, 'rb') as f: + for chunk_size, chunk_sha256 in parse_chunklist(chunklist_path): + chunk = f.read(chunk_size) + assert len(chunk) == chunk_size + assert hashlib.sha256(chunk).digest() == chunk_sha256 + assert f.read(1) == b'' + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('vmdir', type=Path) + args = parser.parse_args() + vmdir = args.vmdir + check_chunklist(vmdir / 'RecoveryImage.dmg', vmdir / 'RecoveryImage.chunklist') + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/macrecovery b/macrecovery deleted file mode 100755 index e6799b2f94..0000000000 --- a/macrecovery +++ /dev/null @@ -1,518 +0,0 @@ -#!/usr/bin/env python3 - -""" -Gather recovery information for Macs. - -Copyright (c) 2019, vit9696 -""" - -from __future__ import print_function - -import argparse -import binascii -import datetime -import hashlib -import json -import linecache -import os -import random -import struct -import sys -import textwrap -import time - -try: - from urllib.request import Request,HTTPError,urlopen - from urllib.parse import urlencode,urlparse -except ImportError: - from urllib2 import Request,HTTPError,urlopen - from urllib import urlencode - from urlparse import urlparse - -SELF_DIR = os.path.dirname(os.path.realpath(__file__)) - -RECENT_MAC = 'Mac-7BA5B2D9E42DDD94' -MLB_ZERO = '00000000000000000' -MLB_VALID = 'C02749200YGJ803AX' -MLB_PRODUCT = '00000000000J80300' - -TYPE_SID = 16 -TYPE_K = 64 -TYPE_FG = 64 - -INFO_PRODUCT = 'AP' -INFO_IMAGE_LINK = 'AU' -INFO_IMAGE_HASH = 'AH' -INFO_IMAGE_SESS = 'AT' -INFO_SIGN_LINK = 'CU' -INFO_SIGN_HASH = 'CH' -INFO_SIGN_SESS = 'CT' -INFO_REQURED = [ INFO_PRODUCT, INFO_IMAGE_LINK, INFO_IMAGE_HASH, INFO_IMAGE_SESS, - INFO_SIGN_LINK, INFO_SIGN_HASH, INFO_SIGN_SESS ] - -def run_query(url, headers, post=None, raw=False): - if post is not None: - data = '\n'.join([entry + '=' + post[entry] for entry in post]) - if sys.version_info[0] >= 3: - data = data.encode('utf-8') - else: - data = None - - req = Request(url=url, headers=headers, data=data) - try: - response = urlopen(req) - if raw: return response - return dict(response.info()), response.read() - except HTTPError as e: - print('ERROR: "{}" when connecting to {}'.format(e, url)) - sys.exit(1) - -def generate_id(type, id=None): - valid_chars = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'] - if id is None: - return ''.join(random.choice(valid_chars) for i in range(type)) - return id - -def product_mlb(mlb): - return '00000000000' + mlb[11] + mlb[12] + mlb[13] + mlb[14] + '00' - -def mlb_from_eeee(eeee): - if len(eeee) != 4: - print('ERROR: Invalid EEEE code length!') - sys.exit(1) - - return '00000000000' + eeee + '00' - -def int_from_unsigned_bytes(bytes, byteorder): - if byteorder == 'little': bytes = bytes[::-1] - encoded = binascii.hexlify(bytes) - return int(encoded, 16) - -# zhangyoufu https://gist.github.com/MCJack123/943eaca762730ca4b7ae460b731b68e7#gistcomment-3061078 2021-10-08 -Apple_EFI_ROM_public_key_1 = 0xC3E748CAD9CD384329E10E25A91E43E1A762FF529ADE578C935BDDF9B13F2179D4855E6FC89E9E29CA12517D17DFA1EDCE0BEBF0EA7B461FFE61D94E2BDF72C196F89ACD3536B644064014DAE25A15DB6BB0852ECBD120916318D1CCDEA3C84C92ED743FC176D0BACA920D3FCF3158AFF731F88CE0623182A8ED67E650515F75745909F07D415F55FC15A35654D118C55A462D37A3ACDA08612F3F3F6571761EFCCBCC299AEE99B3A4FD6212CCFFF5EF37A2C334E871191F7E1C31960E010A54E86FA3F62E6D6905E1CD57732410A3EB0C6B4DEFDABE9F59BF1618758C751CD56CEF851D1C0EAA1C558E37AC108DA9089863D20E2E7E4BF475EC66FE6B3EFDCF - -ChunkListHeader = struct.Struct('<4sIBBBxQQQ') -assert ChunkListHeader.size == 0x24 - -Chunk = struct.Struct(' 0 - assert chunk_offset == 0x24 - assert signature_offset == chunk_offset + Chunk.size * chunk_count - for i in range(chunk_count): - data = f.read(Chunk.size) - hash_ctx.update(data) - chunk_size, chunk_sha256 = Chunk.unpack(data) - yield chunk_size, chunk_sha256 - digest = hash_ctx.digest() - if signature_method == 1: - data = f.read(256) - assert len(data) == 256 - signature = int_from_unsigned_bytes(data, 'little') - plaintext = 0x1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff003031300d0609608648016503040201050004200000000000000000000000000000000000000000000000000000000000000000 | int_from_unsigned_bytes(digest, 'big') - assert pow(signature, 0x10001, Apple_EFI_ROM_public_key_1) == plaintext - elif signature_method == 2: - data = f.read(32) - assert data == digest - raise RuntimeError('Chunklist missing digital signature') - else: - raise NotImplementedError - assert f.read(1) == b'' - -def get_session(args): - headers = { - 'Host' : 'osrecovery.apple.com', - 'Connection': 'close', - 'User-Agent': 'InternetRecovery/1.0', - } - - headers, output = run_query('http://osrecovery.apple.com/', headers) - - if args.verbose: - print('Session headers:') - for header in headers: - print('{}: {}'.format(header, headers[header])) - - for header in headers: - if header.lower() == 'set-cookie': - cookies = headers[header].split('; ') - for cookie in cookies: - if cookie.startswith('session='): - return cookie - - raise RuntimeError('No session in headers ' + str(headers)) - -def get_image_info(session, bid, mlb=MLB_ZERO, diag = False, os_type = 'default', cid=None): - headers = { - 'Host' : 'osrecovery.apple.com', - 'Connection' : 'close', - 'User-Agent' : 'InternetRecovery/1.0', - 'Cookie' : session, - 'Content-Type': 'text/plain', - } - - post = { - 'cid': generate_id(TYPE_SID, cid), - 'sn' : mlb, - 'bid': bid, - 'k' : generate_id(TYPE_K), - 'fg' : generate_id(TYPE_FG) - } - - if diag: - url = 'http://osrecovery.apple.com/InstallationPayload/Diagnostics' - else: - url = 'http://osrecovery.apple.com/InstallationPayload/RecoveryImage' - post['os'] = os_type - - headers, output = run_query(url, headers, post) - - if sys.version_info[0] >= 3: - output = output.decode('utf-8') - - info = {} - for line in output.split('\n'): - try: - key, value = line.split(': ') - info[key] = value - except: - continue - - for k in INFO_REQURED: - if k not in info: - raise RuntimeError('Missing key ' + k) - - return info - -def save_image(url, sess, filename='', dir=''): - purl = urlparse(url) - headers = { - 'Host' : purl.hostname, - 'Connection': 'close', - 'User-Agent': 'InternetRecovery/1.0', - 'Cookie' : '='.join(['AssetToken', sess]) - } - - if filename == '': - filename = os.path.basename(purl.path) - if filename.find('/') >= 0 or filename == '': - raise RuntimeError('Invalid save path ' + filename) - - print('Saving ' + url + ' to ' + filename + '...') - - with open (os.path.join(dir, filename), 'wb') as fh: - response = run_query(url, headers, raw=True) - size = 0 - while True: - chunk = response.read(2**20) - if not chunk: - break - fh.write(chunk) - size += len(chunk) - print('\r{} MBs downloaded...'.format(size / (2**20)), end='') - sys.stdout.flush() - print('\rDownload complete!') - - return os.path.join(dir, os.path.basename(filename)) - -def verify_image(dmgpath, cnkpath): - print('Verifying image with chunklist...') - - with open (dmgpath, 'rb') as dmgf: - cnkcount = 0 - for cnksize, cnkhash in verify_chunklist(cnkpath): - cnkcount += 1 - print('\rChunk {} ({} bytes)'.format(cnkcount, cnksize), end='') - sys.stdout.flush() - cnk = dmgf.read(cnksize) - if len(cnk) != cnksize: - raise RuntimeError('Invalid chunk {} size: expected {}, read {}'.format(cnkcount, cnksize, len(cnk))) - if hashlib.sha256(cnk).digest() != cnkhash: - raise RuntimeError('Invalid chunk {}: hash mismatch'.format(cnkcount)) - if dmgf.read(1) != b'': - raise RuntimeError('Invalid image: larger than chunklist') - print('\rImage verification complete!') - -def action_download(args): - """ - Reference information for queries: - - Recovery latest: - cid=3076CE439155BA14 - sn=... - bid=Mac-E43C1C25D4880AD6 - k=4BE523BB136EB12B1758C70DB43BDD485EBCB6A457854245F9E9FF0587FB790C - os=latest - fg=B2E6AA07DB9088BE5BDB38DB2EA824FDDFB6C3AC5272203B32D89F9D8E3528DC - - Recovery default: - cid=4A35CB95FF396EE7 - sn=... - bid=Mac-E43C1C25D4880AD6 - k=0A385E6FFC3DDD990A8A1F4EC8B98C92CA5E19C9FF1DD26508C54936D8523121 - os=default - fg=B2E6AA07DB9088BE5BDB38DB2EA824FDDFB6C3AC5272203B32D89F9D8E3528DC - - Diagnostics: - cid=050C59B51497CEC8 - sn=... - bid=Mac-E43C1C25D4880AD6 - k=37D42A8282FE04A12A7D946304F403E56A2155B9622B385F3EB959A2FBAB8C93 - fg=B2E6AA07DB9088BE5BDB38DB2EA824FDDFB6C3AC5272203B32D89F9D8E3528DC - """ - - session = get_session(args) - info = get_image_info(session, bid=args.board_id, mlb=args.mlb, - diag=args.diagnostics, os_type=args.os_type) - if args.verbose: - print(info) - print('Downloading ' + info[INFO_PRODUCT] + '...') - dmgname = '' if args.basename == '' else args.basename + '.dmg' - dmgpath = save_image(info[INFO_IMAGE_LINK], info[INFO_IMAGE_SESS], dmgname, args.outdir) - cnkname = '' if args.basename == '' else args.basename + '.chunklist' - cnkpath = save_image(info[INFO_SIGN_LINK], info[INFO_SIGN_SESS], cnkname, args.outdir) - try: - verify_image(dmgpath, cnkpath) - return 0 - except Exception as err: - if isinstance(err, AssertionError) and str(err)=='': - try: - tb = sys.exc_info()[2] - while tb.tb_next: - tb = tb.tb_next - err = linecache.getline(tb.tb_frame.f_code.co_filename, tb.tb_lineno, tb.tb_frame.f_globals).strip() - except: - err = "Invalid chunklist" - print('\rImage verification failed. ({})'.format(err)) - return 1 - -def action_selfcheck(args): - """ - Sanity check server logic for recovery: - - if not valid(bid): - return error() - ppp = get_ppp(sn) - if not valid(ppp): - return latest_recovery(bid = bid) # Returns newest for bid. - if valid(sn): - if os == 'default': - return default_recovery(sn = sn, ppp = ppp) # Returns oldest for sn. - else: - return latest_recovery(sn = sn, ppp = ppp) # Returns newest for sn. - return default_recovery(ppp = ppp) # Returns oldest. - """ - - session = get_session(args) - valid_default = get_image_info(session, bid=RECENT_MAC, mlb=MLB_VALID, - diag=False, os_type='default') - valid_latest = get_image_info(session, bid=RECENT_MAC, mlb=MLB_VALID, - diag=False, os_type='latest') - product_default = get_image_info(session, bid=RECENT_MAC, mlb=MLB_PRODUCT, - diag=False, os_type='default') - product_latest = get_image_info(session, bid=RECENT_MAC, mlb=MLB_PRODUCT, - diag=False, os_type='latest') - generic_default = get_image_info(session, bid=RECENT_MAC, mlb=MLB_ZERO, - diag=False, os_type='default') - generic_latest = get_image_info(session, bid=RECENT_MAC, mlb=MLB_ZERO, - diag=False, os_type='latest') - - if args.verbose: - print(valid_default) - print(valid_latest) - print(product_default) - print(product_latest) - print(generic_default) - print(generic_latest) - - if valid_default[INFO_PRODUCT] == valid_latest[INFO_PRODUCT]: - # Valid MLB must give different default and latest if this is not a too new product. - print('ERROR: Cannot determine any previous product, got {}'.format(valid_default[INFO_PRODUCT])) - return 1 - - if product_default[INFO_PRODUCT] != product_latest[INFO_PRODUCT]: - # Product-only MLB must give the same value for default and latest. - print('ERROR: Latest and default do not match for product MLB, got {} and {}'.format( - product_default[INFO_PRODUCT], product_latest[INFO_PRODUCT])) - return 1 - - if generic_default[INFO_PRODUCT] != generic_latest[INFO_PRODUCT]: - # Zero MLB always give the same value for default and latest. - print('ERROR: Generic MLB gives different product, got {} and {}'.format( - generic_default[INFO_PRODUCT], generic_latest[INFO_PRODUCT])) - return 1 - - if valid_latest[INFO_PRODUCT] != generic_latest[INFO_PRODUCT]: - # Valid MLB must always equal generic MLB. - print('ERROR: Cannot determine unified latest product, got {} and {}'.format( - valid_latest[INFO_PRODUCT], generic_latest[INFO_PRODUCT])) - return 1 - - if product_default[INFO_PRODUCT] != valid_default[INFO_PRODUCT]: - # Product-only MLB can give the same value with valid default MLB. - # This is not an error for all models, but for our chosen code it is. - print('ERROR: Valid and product MLB give mismatch, got {} and {}'.format( - product_default[INFO_PRODUCT], valid_default[INFO_PRODUCT])) - return 1 - - print('SUCCESS: Found no discrepancies with MLB validation algorithm!') - return 0 - -def action_verify(args): - """ - Try to verify MLB serial number. - """ - session = get_session(args) - generic_latest = get_image_info(session, bid=RECENT_MAC, mlb=MLB_ZERO, - diag=False, os_type='latest') - uvalid_default = get_image_info(session, bid=args.board_id, mlb=args.mlb, - diag=False, os_type='default') - uvalid_latest = get_image_info(session, bid=args.board_id, mlb=args.mlb, - diag=False, os_type='latest') - uproduct_default = get_image_info(session, bid=args.board_id, mlb=product_mlb(args.mlb), - diag=False, os_type='default') - - if args.verbose: - print(generic_latest) - print(uvalid_default) - print(uvalid_latest) - print(uproduct_default) - - # Verify our MLB number. - if uvalid_default[INFO_PRODUCT] != uvalid_latest[INFO_PRODUCT]: - if uvalid_latest[INFO_PRODUCT] == generic_latest[INFO_PRODUCT]: - print('SUCCESS: {} MLB looks valid and supported!'.format(args.mlb)) - else: - print('SUCCESS: {} MLB looks valid, but probably unsupported!'.format(args.mlb)) - return 0 - - print('UNKNOWN: Run selfcheck, check your board-id, or try again later!') - - # Here we have matching default and latest products. This can only be true for very - # new models. These models get either latest or special builds. - if uvalid_default[INFO_PRODUCT] == generic_latest[INFO_PRODUCT]: - print('UNKNOWN: {} MLB can be valid if very new!'.format(args.mlb)) - return 0 - if uproduct_default[INFO_PRODUCT] != uvalid_default[INFO_PRODUCT]: - print('UNKNOWN: {} MLB looks invalid, other models use product {} instead of {}!'.format( - args.mlb, uproduct_default[INFO_PRODUCT], uvalid_default[INFO_PRODUCT])) - return 0 - print('UNKNOWN: {} MLB can be valid if very new and using special builds!'.format(args.mlb)) - return 0 - -def action_guess(args): - """ - Attempt to guess which model does this MLB belong. - """ - - mlb = args.mlb - anon = mlb.startswith('000') - - with open(args.board_db, 'r') as fh: - db = json.load(fh) - - supported = {} - - session = get_session(args) - - generic_latest = get_image_info(session, bid=RECENT_MAC, mlb=MLB_ZERO, - diag=False, os_type='latest') - - for model in db: - try: - if anon: - # For anonymous lookup check when given model does not match latest. - model_latest = get_image_info(session, bid=model, mlb=MLB_ZERO, - diag=False, os_type='latest') - - if model_latest[INFO_PRODUCT] != generic_latest[INFO_PRODUCT]: - if db[model] == 'current': - print('WARN: Skipped {} due to using latest product {} instead of {}'.format( - model, model_latest[INFO_PRODUCT], generic_latest[INFO_PRODUCT])) - continue - - user_default = get_image_info(session, bid=model, mlb=mlb, - diag=False, os_type='default') - - if user_default[INFO_PRODUCT] != generic_latest[INFO_PRODUCT]: - supported[model] = [db[model], user_default[INFO_PRODUCT], generic_latest[INFO_PRODUCT]] - else: - # For normal lookup check when given model has mismatching normal and latest. - user_latest = get_image_info(session, bid=model, mlb=mlb, - diag=False, os_type='latest') - - user_default = get_image_info(session, bid=model, mlb=mlb, - diag=False, os_type='default') - - if user_latest[INFO_PRODUCT] != user_default[INFO_PRODUCT]: - supported[model] = [db[model], user_default[INFO_PRODUCT], user_latest[INFO_PRODUCT]] - - except Exception as e: - print('WARN: Failed to check {}, exception: {}'.format(model, str(e))) - - if len(supported) > 0: - print('SUCCESS: MLB {} looks supported for:'.format(mlb)) - for model in supported: - print('- {}, up to {}, default: {}, latest: {}'.format(model, supported[model][0], - supported[model][1], supported[model][2])) - return 0 - - print('UNKNOWN: Failed to determine supported models for MLB {}!'.format(mlb)) - -def main(): - parser = argparse.ArgumentParser(description='Gather recovery information for Macs') - parser.add_argument('action', choices = ['download', 'selfcheck', 'verify', 'guess'], - help='Action to perform: "download" - performs recovery downloading,' - ' "selfcheck" checks whether MLB serial validation is possible, "verify" performs' - ' MLB serial verification, "guess" tries to find suitable mac model for MLB.') - parser.add_argument('-o', '--outdir', type=str, default=os.getcwd(), - help='customise output directory for downloading, defaults to current directory') - parser.add_argument('-n', '--basename', type=str, default='', - help='customise base name for downloading, defaults to remote name') - parser.add_argument('-b', '--board-id', type=str, default=RECENT_MAC, - help='use specified board identifier for downloading, defaults to ' + RECENT_MAC) - parser.add_argument('-m', '--mlb', type=str, default=MLB_ZERO, - help='use specified logic board serial for downloading, defaults to ' + MLB_ZERO) - parser.add_argument('-e', '--code', type=str, default='', - help='generate product logic board serial with specified product EEEE code') - parser.add_argument('-os', '--os-type', type=str, default='default', choices = ['default', 'latest'], - help='use specified os type, defaults to default ' + MLB_ZERO) - parser.add_argument('-diag', '--diagnostics', action='store_true', help='download diagnostics image') - parser.add_argument('-v', '--verbose', action='store_true', help='print debug information') - parser.add_argument('-db', '--board-db', type=str, default=os.path.join(SELF_DIR, 'boards.json'), - help='use custom board list for checking, defaults to boards.json') - - args = parser.parse_args() - - if args.code != '': - args.mlb = mlb_from_eeee(args.code) - - if len(args.mlb) != 17: - print('ERROR: Cannot use MLBs in non 17 character format!') - sys.exit(1) - - if args.action == 'download': - return action_download(args) - elif args.action == 'selfcheck': - return action_selfcheck(args) - elif args.action == 'verify': - return action_verify(args) - elif args.action == 'guess': - return action_guess(args) - else: - assert(False) - -if __name__ == '__main__': - sys.exit(main()) diff --git a/quickemu b/quickemu index c00388c055..3842da47b0 100755 --- a/quickemu +++ b/quickemu @@ -288,6 +288,13 @@ function vm_boot() { GUEST_CPU_CORES="${cpu_cores}" fi + if [ "${guest_os}" == "macos" ] && [ "${GUEST_CPU_CORES}" -gt 10 ] || [ "${GUEST_CPU_CORES}" -eq 6 ] || [ "${GUEST_CPU_CORES}" -eq 7 ]; then + # macOS guests cannot boot with most core counts not powers of 2. This will fix the issue by rounding the core count down to a power of 2. Uses wc and factor from coreutils. + factorCPUCores=$(factor "${GUEST_CPU_CORES}") + GUEST_CPU_CORES=$(( 2 ** $(echo "${factorCPUCores#*:}" | grep -o '[0-9]' | wc -l) )) + fi + + # Account for Hyperthreading/SMT. if [ -e /sys/devices/system/cpu/smt/control ] && [ "${GUEST_CPU_CORES}" -ge 2 ]; then HOST_CPU_SMT=$(cat /sys/devices/system/cpu/smt/control) @@ -520,7 +527,7 @@ function vm_boot() { # A CPU with SSE4.1 support is required for >= macOS Sierra # A CPU with AVX2 support is required for >= macOS Ventura case ${macos_release} in - ventura) + ventura|sonoma) if check_cpu_flag sse4_1 && check_cpu_flag avx2; then CPU="-cpu Haswell,kvm=on,vendor=GenuineIntel,+sse3,+sse4.2,+aes,+xsave,+avx,+xsaveopt,+xsavec,+xgetbv1,+avx2,+bmi2,+smep,+bmi1,+fma,+movbe,+invtsc,+avx2" else @@ -557,7 +564,7 @@ function vm_boot() { NET_DEVICE="vmxnet3" USB_HOST_PASSTHROUGH_CONTROLLER="usb-ehci" ;; - big-sur|monterey|ventura) + big-sur|monterey|ventura|sonoma) BALLOON="-device virtio-balloon" MAC_DISK_DEV="virtio-blk-pci" NET_DEVICE="virtio-net" diff --git a/quickget b/quickget index f09168ba1f..63fda38132 100755 --- a/quickget +++ b/quickget @@ -178,9 +178,7 @@ function list_csv() { SVG="https://quickemu-project.github.io/quickemu-icons/svg/${FUNC}/${FUNC}-quickemu-white-pinkbg.svg" for RELEASE in $("releases_${FUNC}" | sed -Ee 's/eol-\S+//g' ); do # hide eol releases - if [ "${OS}" == "macos" ]; then - DOWNLOADER="macrecovery" - elif [[ "${OS}" == *"ubuntu"* ]] && [ "${RELEASE}" == "devel" ] && [ ${HAS_ZSYNC} -eq 1 ]; then + if [[ "${OS}" == *"ubuntu"* ]] && [ "${RELEASE}" == "devel" ] && [ ${HAS_ZSYNC} -eq 1 ]; then DOWNLOADER="zsync" else DOWNLOADER="${DL}" @@ -646,7 +644,7 @@ function editions_manjaro(){ } function releases_macos() { - echo high-sierra mojave catalina big-sur monterey ventura + echo high-sierra mojave catalina big-sur monterey ventura sonoma } function releases_manjaro() { @@ -930,6 +928,15 @@ function web_get() { else FILE="${URL##*/}" fi + + while (( "$#" )); do + if [[ $1 == --header ]]; then + HEADERS+=("$1" "$2") + shift 2 + else + shift + fi + done # Test mode for ISO if [ "${show_iso_url}" == 'on' ]; then @@ -945,14 +952,18 @@ function web_get() { exit 1 fi + if [[ ${OS} != windows && ${OS} != macos ]]; then + echo Downloading $(pretty_name "${OS}") ${RELEASE} ${EDITION:+ $EDITION} from ${URL} + fi + if command -v aria2c &>/dev/null; then - if ! aria2c --stderr -x16 --continue=true --summary-interval=0 --download-result=hide --console-log-level=error "${URL}" --dir "${DIR}" -o "${FILE}"; then + if ! aria2c --stderr -x16 --continue=true --summary-interval=0 --download-result=hide --console-log-level=error "${URL}" --dir "${DIR}" -o "${FILE}" "${HEADERS[@]}"; then echo #Necessary as aria2c in suppressed mode does not have new lines echo "ERROR! Failed to download ${URL} with aria2c. Try running 'quickget' again." exit 1 fi echo #Necessary as aria2c in suppressed mode does not have new lines - elif ! wget --quiet --continue --tries=3 --read-timeout=10 --show-progress --progress=bar:force:noscroll "${URL}" -O "${DIR}/${FILE}"; then + elif ! wget --quiet --continue --tries=3 --read-timeout=10 --show-progress --progress=bar:force:noscroll "${URL}" -O "${DIR}/${FILE}" "${HEADERS[@]}"; then echo "ERROR! Failed to download ${URL} with wget. Try running 'quickget' again." exit 1 fi @@ -983,6 +994,7 @@ function zsync_get() { exit 1 fi + echo -e Downloading $(pretty_name "${OS}") ${RELEASE} ${EDITION+ ${EDITION}} from ${URL}'\n' # Only force http for zsync - not earlier because we might fall through here if ! zsync "${URL/https/http}.zsync" -i "${DIR}/${OUT}" -o "${DIR}/${OUT}" 2>/dev/null; then echo "ERROR! Failed to download ${URL/https/http}.zsync" @@ -1657,10 +1669,20 @@ function get_lmde() { echo "${URL}/${ISO} ${HASH}" } +function generate_id() { + local macRecoveryID="" + local TYPE="${1}" + local valid_chars=("0" "1" "2" "3" "4" "5" "6" "7" "8" "9" "A" "B" "C" "D" "E" "F") + for ((i=0; i<$TYPE; i++)); do + macRecoveryID+="${valid_chars[$((RANDOM % 16))]}" + done + echo "${macRecoveryID}" +} + function get_macos() { local BOARD_ID="" local CWD="" - local MACRECOVERY="" + local CHUNKCHECK="" local MLB="00000000000000000" local OS_TYPE="default" @@ -1697,43 +1719,74 @@ function get_macos() { BOARD_ID="Mac-E43C1C25D4880AD6";; ventura) #13 BOARD_ID="Mac-BE088AF8C5EB4FA2";; + sonoma) + BOARD_ID="Mac-53FDB3D8DB8CA971";; *) echo "ERROR! Unknown release: ${RELEASE}" releases_macos exit 1;; esac - # Use a bundled macrecovery if possible CWD="$(dirname "${0}")" - if [ -x "${CWD}/macrecovery" ]; then - MACRECOVERY="${CWD}/macrecovery" - elif [ -x /usr/bin/macrecovery ]; then - MACRECOVERY="/usr/bin/macrecovery" + if [ -x "${CWD}/chunkcheck" ]; then + CHUNKCHECK="${CWD}/chunkcheck" + elif [ -x /usr/bin/chunkcheck ]; then + CHUNKCHECK="/usr/bin/chunkcheck" else - web_get "https://raw.githubusercontent.com/wimpysworld/quickemu/master/macrecovery" "${HOME}/.quickemu" - MACRECOVERY="python3 ${HOME}/.quickemu/macrecovery" + web_get "https://raw.githubusercontent.com/wimpysworld/quickemu/master/chunkcheck" "${HOME}/.quickemu" + CHUNKCHECK="${HOME}/.quickemu/chunkcheck" fi - if [ -z "${MACRECOVERY}" ]; then - echo "ERROR! Can not find a usable macrecovery." - exit 1 + if [ -z "${CHUNKCHECK}" ]; then + read -p "ERROR! Can not find chunkcheck. Will not be able to verify image. Proceed anyways?" skipVerification + if [ "${skipVerification,,}" != "y" ] && [ "${skipVerification,,}" != "yes" ]; then + exit 1 + fi + echo 'Skipping verification' && skipVerification=true + fi + + OpenCore_qcow2="https://github.com/kholia/OSX-KVM/raw/master/OpenCore/OpenCore.qcow2" + OVMF_CODE="https://github.com/kholia/OSX-KVM/raw/master/OVMF_CODE.fd" + OVMF_VARS="https://github.com/kholia/OSX-KVM/raw/master/OVMF_VARS-1920x1080.fd" + + local appleSession=$(curl -v -H "Host: osrecovery.apple.com" -H "Connection: close" -A "InternetRecovery/1.0" http://osrecovery.apple.com/ 2>&1 | tr ';' '\n' | awk -F'session=|;' '{print $2}' | grep 1) + local info=$(curl -s -X POST -H "Host: osrecovery.apple.com" -H "Connection: close" -A "InternetRecovery/1.0" -b "session=\"${appleSession}\"" -H "Content-Type: text/plain"\ + -d $'cid='$(generate_id 16)$'\nsn='${MLB}$'\nbid='${BOARD_ID}$'\nk='$(generate_id 64)$'\nfg='$(generate_id 64)$'\nos='${OS_TYPE} \ + http://osrecovery.apple.com/InstallationPayload/RecoveryImage | tr ' ' '\n') + local downloadLink=$(echo "$info" | grep 'oscdn' | grep 'dmg') + local downloadSession=$(echo "$info" | grep 'expires' | grep 'dmg') + local chunkListLink=$(echo "$info" | grep 'oscdn' | grep 'chunklist') + local chunkListSession=$(echo "$info" | grep 'expires' | grep 'chunklist') + + if [ "${show_iso_url}" == 'on' ]; then + echo -e "Recovery URL (inaccessible through normal browser):\n${downloadLink}\nChunklist (used for verifying the Recovery Image):\n${chunkListLink}\nFirmware URLs:\n${OpenCore_qcow2}\n${OVMF_CODE}\n${OVMF_VARS}" + exit 0 + elif [ "${test_iso_url}" == 'on' ]; then + wget --spider --header "Host: oscdn.apple.com" --header "Connection: close" --header "User-Agent: InternetRecovery/1.0" --header "Cookie: AssetToken=${downloadSession}" "${downloadLink}" + wget --spider --header "Host: oscdn.apple.com" --header "Connection: close" --header "User-Agent: InternetRecovery/1.0" --header "Cookie: AssetToken=${chunkListSession}" "${chunkListLink}" + exit 0 fi - # Get firmware - web_get "https://github.com/kholia/OSX-KVM/raw/master/OpenCore/OpenCore.qcow2" "${VM_PATH}" - web_get "https://github.com/kholia/OSX-KVM/raw/master/OVMF_CODE.fd" "${VM_PATH}" + echo Downloading macOS firmware + web_get "${OpenCore_qcow2}" "${VM_PATH}" + web_get "${OVMF_CODE}" "${VM_PATH}" if [ ! -e "${VM_PATH}/OVMF_VARS-1920x1080.fd" ]; then - web_get "https://github.com/kholia/OSX-KVM/raw/master/OVMF_VARS-1920x1080.fd" "${VM_PATH}" + web_get "${OVMF_VARS}" "${VM_PATH}" fi if [ ! -e "${VM_PATH}/RecoveryImage.chunklist" ]; then - echo "Downloading ${RELEASE}..." - ${MACRECOVERY} \ - --board-id "${BOARD_ID}" \ - --mlb "${MLB}" \ - --os-type "${OS_TYPE}" \ - --basename RecoveryImage \ - --outdir "${VM_PATH}" \ - download + echo "Downloading macOS ${RELEASE} from ${downloadLink}" + web_get "${downloadLink}" "${VM_PATH}" RecoveryImage.dmg --header "Host: oscdn.apple.com" --header "Connection: close" --header "User-Agent: InternetRecovery/1.0" --header "Cookie: AssetToken=${downloadSession}" + curl --progress-bar "${chunkListLink}" -o "${VM_PATH}/RecoveryImage.chunklist" --header "Host: oscdn.apple.com" --header "Connection: close" --header "User-Agent: InternetRecovery/1.0" --header "Cookie: AssetToken=${chunkListSession}" + fi + + if [ $skipVerification != true ]; then + if ! python3 "${CHUNKCHECK}" "${VM_PATH}" 2> /dev/null; then + echo Verification failed. + exit 1 + fi + echo Verified macOS ${RELEASE} image using chunklist. + else + echo Skipping verification of image. fi if [ -e "${VM_PATH}/RecoveryImage.dmg" ] && [ ! -e "${VM_PATH}/RecoveryImage.img" ]; then @@ -2636,6 +2689,10 @@ function download_windows() { fi if echo "$iso_download_link_html" | grep -q "We are unable to complete your request at this time."; then + if [ "${show_iso_url}" == 'on' ] || [ "${test_iso_url}" == 'on' ]; then + echo " - Failed to get URL: Microsoft blocked the automated download request based on your IP address." + exit 1 + fi echo " - Microsoft blocked the automated download request based on your IP address." failed=1 fi @@ -2659,7 +2716,15 @@ function download_windows() { return 1 fi - echo " - Got latest ISO download link (valid for 24 hours): $iso_download_link" + if [ "${show_iso_url}" == 'on' ]; then + echo -e " Windows ${RELEASE} Download (valid for 24 hours):\n${iso_download_link}" + exit 0 + elif [ "${test_iso_url}" == 'on' ]; then + wget --spider "${iso_download_link}" + exit 0 + fi + + echo Downloading Windows ${RELEASE} from "$iso_download_link" # Download ISO FILE_NAME="$(echo "$iso_download_link" | cut -d'?' -f1 | cut -d'/' -f5)" @@ -2667,7 +2732,6 @@ function download_windows() { } function get_windows() { - echo "Downloading Windows ${RELEASE}..." download_windows "${RELEASE}" echo "Downloading VirtIO drivers..." From e43d291b370f11c6e75f92fa4c96fe07e58f6353 Mon Sep 17 00:00:00 2001 From: Liam <33645555+lj3954@users.noreply.github.com> Date: Sun, 24 Dec 2023 01:08:32 -0600 Subject: [PATCH 006/369] Add Zorin OS 17 --- quickget | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quickget b/quickget index 63fda38132..90666096fc 100755 --- a/quickget +++ b/quickget @@ -884,7 +884,7 @@ function releases_xerolinux() { } function releases_zorin() { - echo 16 + echo 17 16 } function editions_zorin() { From df57ed1a68ef88eb165772741c4c1bef75517470 Mon Sep 17 00:00:00 2001 From: Liam <33645555+lj3954@users.noreply.github.com> Date: Sun, 24 Dec 2023 01:24:41 -0600 Subject: [PATCH 007/369] Implement download_iso for Windows & macOS --- README.md | 11 ++++++----- quickget | 39 +++++++++++++++++++++++++-------------- 2 files changed, 31 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index bd999b8fe1..1efdcfa713 100644 --- a/README.md +++ b/README.md @@ -259,15 +259,16 @@ You can also use `quickget` with options to: ``` shell # show an OS ISO download URL for {os} {release} [edition] quickget --show-iso-url fedora 38 Silverblue - # test if and OS ISO is available for {os} {release} [edition] + # test if an OS ISO is available for {os} {release} [edition] quickget --test-iso-url nixos 23.05 plasma5 # open an OS distribution homepage in a browser quickget --open-distro-homepage ubuntu-mate + # Only download image file into current directory, without creating VM + quickget --download-iso elementary 7.1 ``` -The `--show-iso-url` and `--test-iso-url` options **do not** work for -`Windows` (`quickget` will begin downloading the requested release and -edition of windows) +The `--show-iso-url`, `--test-iso-url`, and `--download-iso` options are fully +functional for all operating systems, including Windows and macOS. ## Other Operating Systems @@ -462,7 +463,7 @@ There are some considerations when running macOS via Quickemu. - Big Sur - Monterey - Ventura - - Sonoma (Not recommended) + - Sonoma - `quickemu` will automatically download the required [OpenCore](https://github.com/acidanthera/OpenCorePkg) bootloader and OVMF firmware from [OSX-KVM](https://github.com/kholia/OSX-KVM). diff --git a/quickget b/quickget index 90666096fc..b06b6cafea 100755 --- a/quickget +++ b/quickget @@ -1764,29 +1764,35 @@ function get_macos() { wget --spider --header "Host: oscdn.apple.com" --header "Connection: close" --header "User-Agent: InternetRecovery/1.0" --header "Cookie: AssetToken=${downloadSession}" "${downloadLink}" wget --spider --header "Host: oscdn.apple.com" --header "Connection: close" --header "User-Agent: InternetRecovery/1.0" --header "Cookie: AssetToken=${chunkListSession}" "${chunkListLink}" exit 0 - fi - - echo Downloading macOS firmware - web_get "${OpenCore_qcow2}" "${VM_PATH}" - web_get "${OVMF_CODE}" "${VM_PATH}" - if [ ! -e "${VM_PATH}/OVMF_VARS-1920x1080.fd" ]; then - web_get "${OVMF_VARS}" "${VM_PATH}" - fi - - if [ ! -e "${VM_PATH}/RecoveryImage.chunklist" ]; then + elif [ "${download_iso}" == 'on' ]; then echo "Downloading macOS ${RELEASE} from ${downloadLink}" web_get "${downloadLink}" "${VM_PATH}" RecoveryImage.dmg --header "Host: oscdn.apple.com" --header "Connection: close" --header "User-Agent: InternetRecovery/1.0" --header "Cookie: AssetToken=${downloadSession}" - curl --progress-bar "${chunkListLink}" -o "${VM_PATH}/RecoveryImage.chunklist" --header "Host: oscdn.apple.com" --header "Connection: close" --header "User-Agent: InternetRecovery/1.0" --header "Cookie: AssetToken=${chunkListSession}" + curl --progress-bar "${chunkListLink}" -o RecoveryImage.chunklist --header "Host: oscdn.apple.com" --header "Connection: close" --header "User-Agent: InternetRecovery/1.0" --header "Cookie: AssetToken=${chunkListSession}" + VM_PATH="$(pwd)" + + else + echo "Downloading macOS firmware" + web_get "${OpenCore_qcow2}" "${VM_PATH}" + web_get "${OVMF_CODE}" "${VM_PATH}" + if [ ! -e "${VM_PATH}/OVMF_VARS-1920x1080.fd" ]; then + web_get "${OVMF_VARS}" "${VM_PATH}" + fi + + if [ ! -e "${VM_PATH}/RecoveryImage.chunklist" ]; then + echo "Downloading macOS ${RELEASE} from ${downloadLink}" + web_get "${downloadLink}" "${VM_PATH}" RecoveryImage.dmg --header "Host: oscdn.apple.com" --header "Connection: close" --header "User-Agent: InternetRecovery/1.0" --header "Cookie: AssetToken=${downloadSession}" + curl --progress-bar "${chunkListLink}" -o "${VM_PATH}/RecoveryImage.chunklist" --header "Host: oscdn.apple.com" --header "Connection: close" --header "User-Agent: InternetRecovery/1.0" --header "Cookie: AssetToken=${chunkListSession}" + fi fi if [ $skipVerification != true ]; then if ! python3 "${CHUNKCHECK}" "${VM_PATH}" 2> /dev/null; then - echo Verification failed. + echo "Verification failed." exit 1 fi - echo Verified macOS ${RELEASE} image using chunklist. + echo "Verified macOS ${RELEASE} image using chunklist." else - echo Skipping verification of image. + echo "Skipping verification of image." fi if [ -e "${VM_PATH}/RecoveryImage.dmg" ] && [ ! -e "${VM_PATH}/RecoveryImage.img" ]; then @@ -1794,6 +1800,7 @@ function get_macos() { qemu-img convert "${VM_PATH}/RecoveryImage.dmg" -O raw "${VM_PATH}/RecoveryImage.img" 2>/dev/null fi + rm "${VM_PATH}/RecoveryImage.dmg" "${VM_PATH}/RecoveryImage.chunklist" make_vm_config RecoveryImage.img } @@ -2734,6 +2741,10 @@ function download_windows() { function get_windows() { download_windows "${RELEASE}" + if [ "${download_iso}" == 'on' ]; then + exit 0 + fi + echo "Downloading VirtIO drivers..." web_get "https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/stable-virtio/virtio-win.iso" "${VM_PATH}" From 194ec452be9255604a21a918a7ae1442eb50b620 Mon Sep 17 00:00:00 2001 From: Liam <33645555+lj3954@users.noreply.github.com> Date: Sun, 24 Dec 2023 02:00:39 -0600 Subject: [PATCH 008/369] Fix wget2 * Fix wget2 * Improve wget2 check * Use wget2 whenever available * Use curl instead of wget in order to check where URLs redirect --- quickget | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/quickget b/quickget index b06b6cafea..ed9d5708e6 100755 --- a/quickget +++ b/quickget @@ -963,6 +963,11 @@ function web_get() { exit 1 fi echo #Necessary as aria2c in suppressed mode does not have new lines + elif command -v wget2 &>/dev/null; then + if ! wget2 --quiet --continue --tries=3 --read-timeout=10 --force-progress --progress=bar:force:noscroll "${URL}" -O "${DIR}/${FILE}" "${HEADERS[@]}"; then + echo "ERROR! Failed to download ${URL} with wget2. Try running 'quickget' again." + exit 1 + fi elif ! wget --quiet --continue --tries=3 --read-timeout=10 --show-progress --progress=bar:force:noscroll "${URL}" -O "${DIR}/${FILE}" "${HEADERS[@]}"; then echo "ERROR! Failed to download ${URL} with wget. Try running 'quickget' again." exit 1 @@ -1266,8 +1271,8 @@ function get_archcraft() { local URL="" local TMPURL="" - TMPURL=$(wget -q -S -O- --max-redirect=0 "https://sourceforge.net/projects/archcraft/files/latest/download" 2>&1 | grep -i Location | cut -d' ' -f4) - URL=${TMPURL%\?*} + # Check where the URL redirects using curl. Output to a nonexistent directory so it's not possible to successfully download the image + URL=$(curl -Lfs "https://sourceforge.net/projects/archcraft/files/latest/download" -w %{url_effective} -o /this/is/a/nonexistent/directory/$RANDOM/$RANDOM) echo "${URL} ${HASH}" } @@ -1986,8 +1991,7 @@ function get_reactos() { local URL="" local TMPURL="" - TMPURL=$(wget -q -S -O- --max-redirect=0 "https://sourceforge.net/projects/reactos/files/latest/download" 2>&1 | grep -i Location | cut -d' ' -f4) - URL=${TMPURL%\?*} + URL=$(curl -Lfs "https://sourceforge.net/projects/reactos/files/latest/download" -w %{url_effective} -o /this/is/a/nonexistent/directory/$RANDOM/$RANDOM) echo "${URL} ${HASH}" } @@ -2256,8 +2260,7 @@ function get_zorin() { local ISO="" local URL="" - # Parse out the iso URL from the redirector - URL=$(wget -q -S -O- --max-redirect=0 "https://zrn.co/${RELEASE}${EDITION}" 2>&1 | grep Location | cut -d' ' -f4) + URL=$(curl -Lfs "https://zrn.co/${RELEASE}${EDITION}" -w %{url_effective} -o /this/is/a/nonexistent/directory/$RANDOM/$RANDOM) echo "${URL} ${HASH}" } From 3e22c2b3dec9aeb5313967b4a92fec92cd8ae7c2 Mon Sep 17 00:00:00 2001 From: Liam <33645555+lj3954@users.noreply.github.com> Date: Thu, 28 Dec 2023 18:23:08 -0600 Subject: [PATCH 009/369] Add support for windows languages & windows server * Add windows languages, many bugfixes Entering a release, edition, or OS with only part of a valid one will now throw an error, rather than attempting to download i.e. $quickget windows 1 > "ERROR! Windows 1 is not a supported release", rather than attempting to download a nonexistent ISO * Replace "languages" with "editions" to improve consistency * Remove unnecessary code, hardcode windows editions to not waste time * Add windows server, LTSC * Windows server requires IDE drive * Update Windows Guests section in README.md * Improve support for windows server Add back the language array, change stylization of languages in list --- README.md | 14 +++- quickemu | 20 ++++-- quickget | 208 ++++++++++++++++++++++++++++++++++++++++++++++-------- 3 files changed, 207 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 1efdcfa713..24935b76e9 100644 --- a/README.md +++ b/README.md @@ -511,7 +511,7 @@ sudo rm /Library/Preferences/SystemConfiguration/NetworkInterfaces.plist Now reboot, and the App Store should work. -## Windows 8, 10 & 11 Guests +## Windows Guests `quickget` can download [Windows10](https://www.microsoft.com/software-download/windows10) and @@ -523,6 +523,9 @@ Windows](https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/). Windows 8.1 is also supported but doesn't feature any automated installation or driver optimisation. +`quickget` can also download [Windows 10 LTSC](https://www.microsoft.com/en-us/evalcenter/download-windows-10-enterprise) and Windows Server [2012-r2](https://www.microsoft.com/en-us/evalcenter/download-windows-server-2012-r2), [2016](https://www.microsoft.com/en-us/evalcenter/download-windows-server-2016), [2019](https://www.microsoft.com/en-us/evalcenter/download-windows-server-2019), and [2022](https://www.microsoft.com/en-us/evalcenter/download-windows-server-2022). No automated installation is supported for these releases. + + ``` bash quickget windows 11 quickemu --vm windows-11.conf @@ -535,6 +538,15 @@ quickemu --vm windows-11.conf - Username: `Quickemu` - Password: `quickemu` +### Regional versions + +By default `quickget` will download the *"English International"* release (*"English (United States)"* for server releases), +but you can optionally specify one of the supported languages: For example: + +``` bash +quickget windows 11 "Chinese (Traditional)" +``` + The default Windows 11 configuration looks like this: ``` bash diff --git a/quickemu b/quickemu index 3842da47b0..fb129b5697 100755 --- a/quickemu +++ b/quickemu @@ -337,7 +337,7 @@ function vm_boot() { fi echo ", ${RAM_VM} RAM" - if [ "${guest_os}" == "macos" ] || [ "${guest_os}" == "windows" ]; then + if [ "${guest_os}" == "macos" ] || [ "${guest_os}" == "windows" ] || [ "${guest_os}" == "windows-server" ]; then if [ "${RAM_VM//G/}" -lt 4 ]; then echo "ERROR! You have insufficient RAM to run ${guest_os} in a VM" exit 1 @@ -348,7 +348,7 @@ function vm_boot() { boot=${boot,,} guest_os=${guest_os,,} - if [ "${guest_os}" == "macos" ] || [ "${guest_os}" == "windows" ]; then + if [ "${guest_os}" == "macos" ] || [ "${guest_os}" == "windows" ] || [ "${guest_os}" == "windows-server" ]; then # Display MSRs alert if the guest is macOS or windows ignore_msrs_alert fi @@ -585,7 +585,7 @@ function vm_boot() { disk_size="96G" fi ;; - windows) + windows|windows-server) if [ "${QEMU_VER_SHORT}" -gt 60 ]; then CPU="-cpu host,kvm=on,+hypervisor,+invtsc,l3-cache=on,migratable=no,hv_passthrough" else @@ -738,7 +738,7 @@ function vm_boot() { # qxl-vga supports seamless mouse and sane resolutions if only one scanout # is used. Which is whay '-vga none' is added to the QEMU command line. DISPLAY_DEVICE="qxl-vga" - elif [ "${guest_os}" == "windows" ]; then + elif [ "${guest_os}" == "windows" ] || [ "${guest_os}" == "windows-server" ]; then case ${OUTPUT} in # virtio-gpu "works" with gtk but is limited to 1024x1024 and exhibits other issues. # https://kevinlocke.name/bits/2021/12/10/windows-11-guest-virtio-libvirt/#video @@ -907,7 +907,7 @@ function vm_boot() { esac fi - if [ "${guest_os}" != "windows" ] && [ -n "${PUBLIC}" ]; then + if [ "${guest_os}" != "windows" ] || [ "${guest_os}" == "windows-server" ] && [ -n "${PUBLIC}" ]; then echo -n " - 9P: On guest: " if [ "${guest_os}" == "linux" ]; then echo "sudo mount -t 9p -o trans=virtio,version=9p2000.L,msize=104857600 ${PUBLIC_TAG} ~/$(basename "${PUBLIC}")" @@ -1153,6 +1153,10 @@ function vm_boot() { # shellcheck disable=SC2054,SC2206 args+=(-drive if=ide,index=0,media=disk,file="${disk_img}") + elif [ "${guest_os}" == "windows-server" ]; then + args+=(-device ide-hd,drive=SystemDisk + -drive id=SystemDisk,if=none,format=qcow2,file="${disk_img}" ${STATUS_QUO}) + else # shellcheck disable=SC2054,SC2206 args+=(-device virtio-blk-pci,drive=SystemDisk @@ -1161,7 +1165,7 @@ function vm_boot() { # https://wiki.qemu.org/Documentation/9psetup # https://askubuntu.com/questions/772784/9p-libvirt-qemu-share-modes - if [ "${guest_os}" != "windows" ] && [ -n "${PUBLIC}" ]; then + if [ "${guest_os}" != "windows" ] || [ "${guest_os}" == "windows-server" ] && [ -n "${PUBLIC}" ]; then # shellcheck disable=SC2054 args+=(-fsdev local,id=fsdev0,path="${PUBLIC}",security_model=mapped-xattr -device virtio-9p-pci,fsdev=fsdev0,mount_tag="${PUBLIC_TAG}") @@ -1917,6 +1921,10 @@ if [ ${VM_UP} -eq 0 ]; then sleep 3.5 monitor_send_cmd "sendkey ret" fi + if [ -n "${iso}" ] && [ "${guest_os}" == "windows-server" ]; then + sleep 7 + monitor_send_cmd "sendkey ret" + fi start_viewer else parse_ports_from_file diff --git a/quickget b/quickget index ed9d5708e6..7f0a3aaa66 100755 --- a/quickget +++ b/quickget @@ -108,6 +108,7 @@ function pretty_name() { vanillaos) PRETTY_NAME="Vanilla OS";; void) PRETTY_NAME="Void Linux";; vxlinux) PRETTY_NAME="VX Linux";; + windows-server) PRETTY_NAME="Windows Server";; xerolinux) PRETTY_NAME="XeroLinux";; zorin) PRETTY_NAME="Zorin OS";; *) PRETTY_NAME="${SIMPLE_NAME^}";; @@ -128,7 +129,7 @@ function validate_release() { esac RELEASES=$(${RELEASE_GENERATOR}) - if [[ "${RELEASES}" != *"${RELEASE}"* ]]; then + if [[ ! " ${RELEASES[*]} " =~ " ${RELEASE} " ]]; then echo -e "ERROR! ${DISPLAY_NAME} ${RELEASE} is not a supported release.\n" echo -n "${RELEASES}" exit 1 @@ -189,7 +190,8 @@ function list_csv() { for OPTION in $(editions_"${OS}"); do echo "${DISPLAY_NAME},${OS},${RELEASE},${OPTION},${DOWNLOADER},${PNG},${SVG}" done - elif [ "${OS}" == "windows" ]; then + elif [[ "${OS}" == "windows"* ]]; then + "languages_${OS}" for OPTION in "${LANGS[@]}"; do echo "${DISPLAY_NAME},${OS},${RELEASE},${OPTION},${DOWNLOADER},${PNG},${SVG}" done @@ -279,6 +281,7 @@ function os_support() { void \ vxlinux \ windows \ + windows-server \ xerolinux \ xubuntu \ zorin @@ -365,6 +368,7 @@ function os_homepages(){ void) HOMEPAGE="https://voidlinux.org/";; vxlinux) HOMEPAGE="https://vxlinux.org/";; windows) HOMEPAGE="https://www.microsoft.com/en-us/windows/";; + windows-server) HOMEPAGE="https://www.microsoft.com/en-us/windows-server/";; xerolinux) HOMEPAGE="https://xerolinux.xyz/";; xubuntu) HOMEPAGE="https://xubuntu.org/";; zorin) HOMEPAGE="https://zorin.com/os/";; @@ -876,7 +880,32 @@ function releases_vxlinux() { } function releases_windows() { - echo 8 10 11 + echo 8 10 10-ltsc 11 +} + +function languages_windows() { + if [ "${RELEASE}" == 8 ]; then + LANGS=("Arabic" "Brazilian Portuguese" "Bulgarian" "Chinese (Simplified)" "Chinese (Traditional)" "Chinese (Traditional Hong Kong)" \ + "Croatian" "Czech" "Danish" "Dutch" "English (United States)" "English International" "Estonian" "Finnish" "French" "German" "Greek" \ + "Hebrew" "Hungarian" "Italian" "Japanese" "Latvian" "Lithuanian" "Norwegian" "Polish" "Portuguese" "Romanian" "Russian" "Serbian Latin" \ + "Slovak" "Slovenian" "Spanish" "Swedish" "Thai" "Turkish" "Ukrainian") + elif [ "${RELEASE}" == "10-ltsc" ]; then + LANGS=("English (United States)" "English (Great Britain)" "Chinese (Simplified)" "Chinese (Traditional)" \ + "French" "German" "Italian" "Japanese" "Korean" "Portuguese (Brazil)" "Spanish") + else + LANGS=("Arabic" "Brazilian Portuguese" "Bulgarian" "Chinese (Simplified)" "Chinese (Traditional)" "Croatian" "Czech" "Danish" "Dutch" \ + "English (United States)" "English International" "Estonian" "Finnish" "French" "French Canadian" "German" "Greek" "Hebrew" "Hungarian" \ + "Italian" "Japanese" "Korean" "Latvian" "Lithuanian" "Norwegian" "Polish" "Portuguese" "Romanian" "Russian" "Serbian Latin" "Slovak" \ + "Slovenian" "Spanish" "Spanish (Mexico)" "Swedish" "Thai" "Turkish" "Ukrainian") + fi +} + +function releases_windows-server() { + echo 2012-r2 2016 2019 2022 +} + +function languages_windows-server() { + LANGS=("English (United States)" "Chinese (Simplified)" "French" "German" "Italian" "Japanese" "Russian" "Spanish") } function releases_xerolinux() { @@ -952,7 +981,7 @@ function web_get() { exit 1 fi - if [[ ${OS} != windows && ${OS} != macos ]]; then + if [[ ${OS} != windows && ${OS} != macos && ${OS} != windows-server ]]; then echo Downloading $(pretty_name "${OS}") ${RELEASE} ${EDITION:+ $EDITION} from ${URL} fi @@ -1069,16 +1098,15 @@ function make_vm_config() { windows) GUEST="windows" IMAGE_TYPE="iso";; + windows-server) + GUEST="windows-server" + IMAGE_TYPE="iso";; *) GUEST="linux" IMAGE_TYPE="iso";; esac - if [ -n "${EDITION}" ]; then - CONF_FILE="${OS}-${RELEASE}-${EDITION}.conf" - else - CONF_FILE="${OS}-${RELEASE}.conf" - fi + CONF_FILE="${VM_PATH}.conf" if [ ! -e "${CONF_FILE}" ]; then echo "Making ${CONF_FILE}" @@ -1164,7 +1192,7 @@ EOF echo "disk_size=\"32G\"" >> "${CONF_FILE}" fi # Enable TPM for Windows 11 - if [ "${OS}" == "windows" ] && [ "${RELEASE}" -ge 11 ]; then + if [ "${OS}" == "windows" ] && [ "${RELEASE}" == 11 ] || [ "${OS}" == "windows-server" ] && [ "${RELEASE}" == "2022" ]; then echo "tpm=\"on\"" >> "${CONF_FILE}" echo "secureboot=\"off\"" >> "${CONF_FILE}" fi @@ -2609,6 +2637,89 @@ handle_curl_error() { return 1 } +function download_windows-server() { + # Download enterprise evaluation windows versions + local windows_version="$1" + local enterprise_type="$2" + + local url="https://www.microsoft.com/en-us/evalcenter/download-$windows_version" + + local iso_download_page_html="$(curl --silent --location --fail --proto =https --tlsv1.2 --http1.1 -- "$url")" || { + handle_curl_error $? + return $? + } + + local CULTURE="" + local COUNTRY="" + local PRETTY_RELEASE="" + + case "$RELEASE" in + "10-ltsc") PRETTY_RELEASE="10 LTSC";; + "2012-r2") PRETTY_RELEASE="2012 R2";; + *) PRETTY_RELEASE="$RELEASE";; + esac + + + case "$LANG" in + "English (Great Britain)") + CULTURE="en-gb" + COUNTRY="GB";; + "Chinese (Simplified)") + CULTURE="zh-cn" + COUNTRY="CN";; + "Chinese (Traditional)") + CULTURE="zh-tw" + COUNTRY="TW";; + "French") + CULTURE="fr-fr" + COUNTRY="FR";; + "German") + CULTURE="de-de" + COUNTRY="DE";; + "Italian") + CULTURE="it-it" + COUNTRY="IT";; + "Japanese") + CULTURE="ja-jp" + COUNTRY="JP";; + "Korean") + CULTURE="ko-kr" + COUNTRY="KR";; + "Portuguese (Brazil)") + CULTURE="pt-br" + COUNTRY="BR";; + "Spanish") + CULTURE="es-es" + COUNTRY="ES";; + "Russian") + CULTURE="ru-ru" + COUNTRY="RU";; + *) + CULTURE="en-us" + COUNTRY="US";; + esac + + iso_download_links="$(echo "$iso_download_page_html" | grep -o "https://go.microsoft.com/fwlink/p/?LinkID=[0-9]\+&clcid=0x[0-9a-z]\+&culture=$CULTURE&country=$COUNTRY" | head -c 1024)" + + case "$enterprise_type" in + # Select x64 LTSC download link + "ltsc") iso_download_link=$(echo "$iso_download_links" | head -n 4 | tail -n 1) ;; + *) iso_download_link="$iso_download_links" ;; + esac + + iso_download_link="$(curl --silent --location --output /dev/null --silent --write-out "%{url_effective}" --head --fail --proto =https --tlsv1.2 --http1.1 -- "$iso_download_link")" + + if [ "${COUNTRY}" != "US" ]; then + echo Downloading $(pretty_name "${OS}") ${PRETTY_RELEASE} in "${LANG}" from "$iso_download_link" + else + echo Downloading $(pretty_name "${OS}") ${PRETTY_RELEASE} from "$iso_download_link" + fi + + FILE_NAME="${iso_download_link##*/}" + web_get "$iso_download_link" "${VM_PATH}" "${FILE_NAME}" + OS="windows-server" +} + function download_windows() { # Download newer consumer Windows versions from behind gated Microsoft API # This function aims to precisely emulate what Fido does down to the URL requests and HTTP headers (exceptions: updated user agent and referer adapts to Windows version instead of always being "windows11") but written in POSIX sh (with coreutils) and curl instead of PowerShell (also simplified to greatly reduce attack surface) @@ -2622,7 +2733,7 @@ function download_windows() { # # If this function in Mido fails to work for you then please test with the Fido script before creating an issue because we basically just copy what Fido does exactly: # https://github.com/pbatard/Fido - + # Either 8, 10, or 11 local windows_version="$1" @@ -2650,7 +2761,7 @@ function download_windows() { # tr: Filter for only numerics to prevent HTTP parameter injection # head -c was recently added to POSIX: https://austingroupbugs.net/view.php?id=407 local product_edition_id="$(echo "$iso_download_page_html" | grep -Eo '