Compare commits
8
Commits
9c5abac9bf
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
384a4ade9f | ||
|
|
6064bd650d | ||
|
|
3935e2d5e2 | ||
|
|
00695cdddc | ||
|
|
91ae8cb147 | ||
|
|
4eb26e3a96 | ||
|
|
1476b4a3de | ||
|
|
b37d1e002c |
No files matched your search
@@ -17,6 +17,7 @@ Download or check selected targets:
|
|||||||
```bash
|
```bash
|
||||||
./download-images.sh debian13
|
./download-images.sh debian13
|
||||||
./download-images.sh almalinux10
|
./download-images.sh almalinux10
|
||||||
|
./download-images.sh rocky9
|
||||||
./download-images.sh fedora44
|
./download-images.sh fedora44
|
||||||
./download-images.sh ubuntu2604
|
./download-images.sh ubuntu2604
|
||||||
./download-images.sh virtio-win
|
./download-images.sh virtio-win
|
||||||
@@ -57,6 +58,7 @@ The path interface prints one absolute path without downloading anything:
|
|||||||
```bash
|
```bash
|
||||||
./download-images.sh --print-path debian13
|
./download-images.sh --print-path debian13
|
||||||
./download-images.sh --print-path almalinux10
|
./download-images.sh --print-path almalinux10
|
||||||
|
./download-images.sh --print-path rocky9
|
||||||
./download-images.sh --print-path fedora44
|
./download-images.sh --print-path fedora44
|
||||||
./download-images.sh --print-path ubuntu2604
|
./download-images.sh --print-path ubuntu2604
|
||||||
./download-images.sh --print-path virtio-win
|
./download-images.sh --print-path virtio-win
|
||||||
@@ -71,18 +73,19 @@ ISO (for example `Win11_25H2_English_x64_v2.iso` →
|
|||||||
existing `*Win*11*x64*.qcow2` (`*Win*11*arm64*.qcow2` on arm64) in `base-qcow2/` is used instead.
|
existing `*Win*11*x64*.qcow2` (`*Win*11*arm64*.qcow2` on arm64) in `base-qcow2/` is used instead.
|
||||||
|
|
||||||
Download URLs can be overridden with `DEBIAN_IMAGE_URL`, `ALMA_IMAGE_URL`,
|
Download URLs can be overridden with `DEBIAN_IMAGE_URL`, `ALMA_IMAGE_URL`,
|
||||||
`FEDORA_IMAGE_URL`, `UBUNTU_IMAGE_URL`, `VIRTIO_WIN_ISO_URL`, and
|
`ROCKY_IMAGE_URL`, `FEDORA_IMAGE_URL`, `UBUNTU_IMAGE_URL`, `VIRTIO_WIN_ISO_URL`, and
|
||||||
`OPENSSH_WIN_ZIP_URL`. Each may
|
`OPENSSH_WIN_ZIP_URL`. Each may
|
||||||
list several mirrors of the same file, separated by whitespace; the Fedora
|
list several mirrors of the same file, separated by whitespace; the Fedora
|
||||||
image defaults to the TUNA, BFSU and NJU mirrors, and the Ubuntu image to the
|
image defaults to the TUNA, BFSU and NJU mirrors, the Rocky Linux image to the official
|
||||||
|
dl.rockylinux.org and NJU mirrors, and the Ubuntu image to the
|
||||||
TUNA, NJU and official cloud-images.ubuntu.com mirrors.
|
TUNA, NJU and official cloud-images.ubuntu.com mirrors.
|
||||||
|
|
||||||
When [aria2](https://aria2.github.io/) is installed (`aria2c`), an image is
|
When [aria2](https://aria2.github.io/) is installed (`aria2c`), an image is
|
||||||
downloaded from all of its mirrors at once, with a single connection to each
|
downloaded from all of its mirrors at once, with a single connection to each
|
||||||
mirror. Without it, `curl` (or `wget`) tries the mirrors in order. A partial
|
mirror. Without it, `curl` (or `wget`) tries the mirrors in order. A partial
|
||||||
download is only resumed with the same downloader and mirror list; otherwise
|
download is only resumed with the same downloader and mirror list; otherwise
|
||||||
it starts over. The Fedora and Ubuntu checksums can be overridden with
|
it starts over. The Rocky Linux, Fedora and Ubuntu checksums can be overridden with
|
||||||
`FEDORA_IMAGE_SHA256` and `UBUNTU_IMAGE_SHA256` when using a custom image.
|
`ROCKY_IMAGE_SHA256`, `FEDORA_IMAGE_SHA256` and `UBUNTU_IMAGE_SHA256` when using a custom image.
|
||||||
|
|
||||||
The `openssh-win` target downloads the Win32-OpenSSH zip matching the
|
The `openssh-win` target downloads the Win32-OpenSSH zip matching the
|
||||||
architecture into `tools-iso/`: `OpenSSH-Win64.zip` on amd64 hosts and
|
architecture into `tools-iso/`: `OpenSSH-Win64.zip` on amd64 hosts and
|
||||||
@@ -118,11 +121,12 @@ does not run Windows Setup again. `build-windows-base.sh` makes that base image
|
|||||||
installs Windows unattended from the ISO in `install-iso/` (it does everything
|
installs Windows unattended from the ISO in `install-iso/` (it does everything
|
||||||
itself: answer file in `windows11/seed/`, QEMU hardware in `windows11/qemu/`; this
|
itself: answer file in `windows11/seed/`, QEMU hardware in `windows11/qemu/`; this
|
||||||
directory has no dependency on any other repository). The answer file boots
|
directory has no dependency on any other repository). The answer file boots
|
||||||
into audit mode (no account and no credentials are needed) and runs
|
into audit mode (no account and no credentials are needed), adds the VirtIO drivers
|
||||||
|
from the virtio-win ISO (downloaded if missing) to the driver store and runs
|
||||||
`sysprep /generalize` there as its last step; the script waits for the guest to power
|
`sysprep /generalize` there as its last step; the script waits for the guest to power
|
||||||
off and stores the disk read-only. The image is clean and generic, like a cloud
|
off and stores the disk read-only. The image is clean and generic, like a cloud
|
||||||
image: nothing but Windows, with no account, no SSH, no settings, no VirtIO or
|
image: nothing but Windows and the VirtIO drivers, with no account, no SSH, no settings,
|
||||||
other hardware-specific drivers and no machine identity. Each VM is configured
|
no other hardware-specific drivers and no machine identity. Each VM is configured
|
||||||
on its first boot: Windows reads `Unattend.xml` from the root of an attached
|
on its first boot: Windows reads `Unattend.xml` from the root of an attached
|
||||||
CD-ROM during its specialize and OOBE passes (a VM launcher can mount such a
|
CD-ROM during its specialize and OOBE passes (a VM launcher can mount such a
|
||||||
seed ISO when it creates a VM disk).
|
seed ISO when it creates a VM disk).
|
||||||
@@ -130,7 +134,7 @@ It needs `qemu`, `qemu-img`, an ISO tool (`xorriso`, `genisoimage` or `mkisofs`)
|
|||||||
and `socat` or `nc`; there is no SSH to the guest.
|
and `socat` or `nc`; there is no SSH to the guest.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./build-windows-base.sh [--force] [--compress] [--from-img] [--window] [OUTPUT.qcow2]
|
./build-windows-base.sh [--force] [--compress] [--from-img] [--headless] [OUTPUT.qcow2]
|
||||||
```
|
```
|
||||||
|
|
||||||
It refuses to replace an existing base image without `--force`, because the
|
It refuses to replace an existing base image without `--force`, because the
|
||||||
|
|||||||
+54
-15
@@ -6,11 +6,12 @@
|
|||||||
# made once, and this script needs nothing outside this directory:
|
# made once, and this script needs nothing outside this directory:
|
||||||
# 1. an answer ISO is built from windows11/seed/Autounattend.xml;
|
# 1. an answer ISO is built from windows11/seed/Autounattend.xml;
|
||||||
# 2. QEMU installs Windows unattended from the ISO into a scratch directory.
|
# 2. QEMU installs Windows unattended from the ISO into a scratch directory.
|
||||||
# The answer file then boots into audit mode, where it runs sysprep
|
# The answer file then boots into audit mode, where it adds the VirtIO
|
||||||
|
# drivers from the virtio-win ISO to the driver store and runs sysprep
|
||||||
# /generalize, which removes the machine identity and shuts the guest down;
|
# /generalize, which removes the machine identity and shuts the guest down;
|
||||||
# 3. the shut-down disk is converted into the output file and made read-only.
|
# 3. the shut-down disk is converted into the output file and made read-only.
|
||||||
# The image holds nothing but Windows itself, like a cloud image: no VirtIO or
|
# The image holds nothing but Windows and the VirtIO drivers, like a cloud image:
|
||||||
# other hardware-specific drivers (the installation hardware, windows11/qemu,
|
# no other hardware-specific drivers (the installation hardware, windows11/qemu,
|
||||||
# only uses devices with inbox Windows drivers), no machine identity, no user
|
# only uses devices with inbox Windows drivers), no machine identity, no user
|
||||||
# account (the answer file holds no credentials either: audit mode needs no
|
# account (the answer file holds no credentials either: audit mode needs no
|
||||||
# account; the built-in Administrator is disabled), no SSH, no settings. Every
|
# account; the built-in Administrator is disabled), no SSH, no settings. Every
|
||||||
@@ -24,19 +25,21 @@
|
|||||||
# install-iso/ (see README.md).
|
# install-iso/ (see README.md).
|
||||||
#
|
#
|
||||||
# Usage: build-windows-base.sh [--force] [--keep-work] [--compress] [--from-img]
|
# Usage: build-windows-base.sh [--force] [--keep-work] [--compress] [--from-img]
|
||||||
# [--window] [OUTPUT.qcow2]
|
# [--headless] [OUTPUT.qcow2]
|
||||||
# OUTPUT defaults to the path that "download-images.sh --print-path windows11"
|
# OUTPUT defaults to the path that "download-images.sh --print-path windows11"
|
||||||
# prints.
|
# prints.
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
images_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
images_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
downloader="${images_dir}/download-images.sh"
|
downloader="${images_dir}/download-images.sh"
|
||||||
|
# Path lookups below must not offer to run this script again.
|
||||||
|
export VM_IMAGE_NO_PROMPT=1
|
||||||
win_dir="${images_dir}/windows11"
|
win_dir="${images_dir}/windows11"
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
cat <<EOF
|
cat <<EOF
|
||||||
Usage: $(basename "$0") [--force] [--keep-work] [--compress] [--from-img]
|
Usage: $(basename "$0") [--force] [--keep-work] [--compress] [--from-img]
|
||||||
[--window] [OUTPUT.qcow2]
|
[--headless] [OUTPUT.qcow2]
|
||||||
|
|
||||||
Install Windows 11 from the ISO, generalize it with sysprep and store the disk
|
Install Windows 11 from the ISO, generalize it with sysprep and store the disk
|
||||||
as the read-only, generic base image that VM disks are cloned from.
|
as the read-only, generic base image that VM disks are cloned from.
|
||||||
@@ -47,18 +50,21 @@ as the read-only, generic base image that VM disks are cloned from.
|
|||||||
--from-img Install from the disk image made by build-windows-install-disk.sh
|
--from-img Install from the disk image made by build-windows-install-disk.sh
|
||||||
(the ISO path ending in .img), which reads much faster than
|
(the ISO path ending in .img), which reads much faster than
|
||||||
the emulated CD-ROM
|
the emulated CD-ROM
|
||||||
--window Show the guest in a QEMU window (needs a desktop session)
|
--headless Serve the guest screen over VNC on 127.0.0.1:5990 instead of
|
||||||
instead of the VNC display on 127.0.0.1:5990
|
showing a QEMU window (the default; without a desktop
|
||||||
|
session on Linux, VNC is used as well)
|
||||||
-h, --help Show this help
|
-h, --help Show this help
|
||||||
|
|
||||||
Environment variables:
|
Environment variables:
|
||||||
VM_IMAGE_ARCH amd64 or arm64 (default: the host's)
|
VM_IMAGE_ARCH amd64 or arm64 (default: the host's)
|
||||||
WIN11_VM_INSTALL_ISO Windows 11 ISO (default: the path that
|
WIN11_VM_INSTALL_ISO Windows 11 ISO (default: the path that
|
||||||
"download-images.sh --print-path windows11 --iso" prints)
|
"download-images.sh --print-path windows11 --iso" prints)
|
||||||
|
WIN11_VM_TOOLS_ISO virtio-win ISO (default: the path that
|
||||||
|
"download-images.sh --print-path virtio-win" prints; downloaded if missing)
|
||||||
WIN11_VM_DISK_SIZE Size of the system disk (default: 64G)
|
WIN11_VM_DISK_SIZE Size of the system disk (default: 64G)
|
||||||
WIN11_VM_UEFI_CODE OVMF read-only firmware (auto-detected if unset)
|
WIN11_VM_UEFI_CODE OVMF read-only firmware (auto-detected if unset)
|
||||||
WIN11_VM_UEFI_VARS OVMF variable-store template (auto-detected if unset)
|
WIN11_VM_UEFI_VARS OVMF variable-store template (auto-detected if unset)
|
||||||
WIN11_VM_DISPLAY QEMU display with --window (default: cocoa on macOS,
|
WIN11_VM_DISPLAY QEMU window display (default: cocoa on macOS,
|
||||||
gtk elsewhere)
|
gtk elsewhere)
|
||||||
BASE_BUILD_TMPDIR Parent of the scratch state directory, which needs about
|
BASE_BUILD_TMPDIR Parent of the scratch state directory, which needs about
|
||||||
25 GB (default: /var/tmp)
|
25 GB (default: /var/tmp)
|
||||||
@@ -75,7 +81,7 @@ force=0
|
|||||||
keep_work=0
|
keep_work=0
|
||||||
compress=0
|
compress=0
|
||||||
from_img=0
|
from_img=0
|
||||||
window=0
|
window=1
|
||||||
output=""
|
output=""
|
||||||
while (($#)); do
|
while (($#)); do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
@@ -84,7 +90,7 @@ while (($#)); do
|
|||||||
--keep-work) keep_work=1 ;;
|
--keep-work) keep_work=1 ;;
|
||||||
--compress) compress=1 ;;
|
--compress) compress=1 ;;
|
||||||
--from-img) from_img=1 ;;
|
--from-img) from_img=1 ;;
|
||||||
--window) window=1 ;;
|
--headless) window=0 ;;
|
||||||
-*) usage >&2; exit 2 ;;
|
-*) usage >&2; exit 2 ;;
|
||||||
*) [[ -z "$output" ]] || { usage >&2; exit 2; }; output="$1" ;;
|
*) [[ -z "$output" ]] || { usage >&2; exit 2; }; output="$1" ;;
|
||||||
esac
|
esac
|
||||||
@@ -116,6 +122,22 @@ if ((!from_img)); then
|
|||||||
|| die "install socat or nc (it presses a key at the boot prompt of the ISO)."
|
|| die "install socat or nc (it presses a key at the boot prompt of the ISO)."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Without the ISO, offer to download it (needs a terminal).
|
||||||
|
if [[ -z "${WIN11_VM_INSTALL_ISO:-}" ]] && ! "$downloader" --print-path windows11 --iso >/dev/null 2>&1; then
|
||||||
|
"$downloader" --print-path windows11 --iso >&2 || true
|
||||||
|
if { : </dev/tty >/dev/tty; } 2>/dev/null; then
|
||||||
|
printf 'Download the Windows 11 ISO now? [Y/n] ' >/dev/tty
|
||||||
|
read -r answer </dev/tty || answer=
|
||||||
|
[[ -z "$answer" || "$answer" == [yY] || "$answer" == [yY][eE][sS] ]] || exit 1
|
||||||
|
"$downloader" windows11 || exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# The VirtIO drivers for the image come from the virtio-win ISO.
|
||||||
|
virtio_iso="${WIN11_VM_TOOLS_ISO:-$("$downloader" --print-path virtio-win)}"
|
||||||
|
[[ -s "$virtio_iso" ]] || "$downloader" virtio-win || exit 1
|
||||||
|
[[ -s "$virtio_iso" ]] || die "the virtio-win ISO was not found: ${virtio_iso}"
|
||||||
|
|
||||||
output="${output:-$("$downloader" --print-path windows11)}"
|
output="${output:-$("$downloader" --print-path windows11)}"
|
||||||
if [[ -e "$output" && $force -eq 0 ]]; then
|
if [[ -e "$output" && $force -eq 0 ]]; then
|
||||||
die "${output} exists (it may be the backing file of existing VM disks); use --force or another OUTPUT."
|
die "${output} exists (it may be the backing file of existing VM disks); use --force or another OUTPUT."
|
||||||
@@ -207,6 +229,7 @@ seed_dir="${work}/seed"
|
|||||||
mkdir -p "$seed_dir"
|
mkdir -p "$seed_dir"
|
||||||
sed "s/processorArchitecture=\"amd64\"/processorArchitecture=\"${arch}\"/" \
|
sed "s/processorArchitecture=\"amd64\"/processorArchitecture=\"${arch}\"/" \
|
||||||
"${win_dir}/seed/Autounattend.xml" >"${seed_dir}/Autounattend.xml"
|
"${win_dir}/seed/Autounattend.xml" >"${seed_dir}/Autounattend.xml"
|
||||||
|
cp -- "${win_dir}/seed/Install-VirtIO-Drivers.ps1" "${seed_dir}/"
|
||||||
seed_iso="${work}/seed.iso"
|
seed_iso="${work}/seed.iso"
|
||||||
case "$iso_tool" in
|
case "$iso_tool" in
|
||||||
xorriso) xorriso -as mkisofs -quiet -o "$seed_iso" -V ANSWER -J -r "$seed_dir" ;;
|
xorriso) xorriso -as mkisofs -quiet -o "$seed_iso" -V ANSWER -J -r "$seed_dir" ;;
|
||||||
@@ -227,13 +250,15 @@ config_text="$(<"$config_template")"
|
|||||||
for placeholder in uefi_code uefi_vars disk_image monitor_socket; do
|
for placeholder in uefi_code uefi_vars disk_image monitor_socket; do
|
||||||
value="${!placeholder}"
|
value="${!placeholder}"
|
||||||
[[ "$value" != *\"* ]] || die "${placeholder} must not contain double quotes: ${value}"
|
[[ "$value" != *\"* ]] || die "${placeholder} must not contain double quotes: ${value}"
|
||||||
config_text="${config_text//"\${${placeholder}}"/"$value"}"
|
# No outer double quotes: bash 3.2 (macOS) keeps inner quotes literally.
|
||||||
|
config_text=${config_text//"\${${placeholder}}"/"$value"}
|
||||||
done
|
done
|
||||||
printf '%s\n' "$config_text" >"$qemu_config"
|
printf '%s\n' "$config_text" >"$qemu_config"
|
||||||
|
|
||||||
|
if ((window)) && [[ -z "${DISPLAY:-}${WAYLAND_DISPLAY:-}" && "$host_os" != darwin ]]; then
|
||||||
|
window=0
|
||||||
|
fi
|
||||||
if ((window)); then
|
if ((window)); then
|
||||||
[[ -n "${DISPLAY:-}${WAYLAND_DISPLAY:-}" || "$host_os" == darwin ]] \
|
|
||||||
|| die "--window needs a desktop session (DISPLAY or WAYLAND_DISPLAY is not set)."
|
|
||||||
if [[ -n "${WIN11_VM_DISPLAY:-}" ]]; then display="$WIN11_VM_DISPLAY"
|
if [[ -n "${WIN11_VM_DISPLAY:-}" ]]; then display="$WIN11_VM_DISPLAY"
|
||||||
elif [[ "$host_os" == darwin ]]; then display=cocoa
|
elif [[ "$host_os" == darwin ]]; then display=cocoa
|
||||||
else display=gtk; fi
|
else display=gtk; fi
|
||||||
@@ -250,8 +275,11 @@ qemu_args=(
|
|||||||
-netdev user,id=net0
|
-netdev user,id=net0
|
||||||
-nic none
|
-nic none
|
||||||
-display "$display"
|
-display "$display"
|
||||||
-serial "file:${work}/console.log" -daemonize -pidfile "$pid_file"
|
-serial "file:${work}/console.log" -pidfile "$pid_file"
|
||||||
)
|
)
|
||||||
|
# The Cocoa/GTK window needs the original process (macOS crashes in fork()), so
|
||||||
|
# only the VNC display is daemonized; the window runs as a background job.
|
||||||
|
((window)) || qemu_args+=(-daemonize)
|
||||||
if [[ "$host_os" == darwin && "$arch" == arm64 ]] && ((!from_img)); then
|
if [[ "$host_os" == darwin && "$arch" == arm64 ]] && ((!from_img)); then
|
||||||
# With the HVF in-kernel GICv3, QEMU's usb-storage races on the status packet
|
# With the HVF in-kernel GICv3, QEMU's usb-storage races on the status packet
|
||||||
# and Windows Setup stalls 10 s after every burst of reads (utmapp/UTM#7970),
|
# and Windows Setup stalls 10 s after every burst of reads (utmapp/UTM#7970),
|
||||||
@@ -279,6 +307,8 @@ else
|
|||||||
attach_media install_media "$install_iso" 2 ide.1
|
attach_media install_media "$install_iso" 2 ide.1
|
||||||
fi
|
fi
|
||||||
attach_media answer_media "$seed_iso" 3 ide.2
|
attach_media answer_media "$seed_iso" 3 ide.2
|
||||||
|
# The VirtIO drivers are installed into the image from this ISO in audit mode.
|
||||||
|
attach_media virtio_media "$virtio_iso" 4 ide.3
|
||||||
|
|
||||||
# The Windows ISO boots through bootmgr, which waits for a key ("Press any key
|
# The Windows ISO boots through bootmgr, which waits for a key ("Press any key
|
||||||
# to boot from CD or DVD...") and gives up after a few seconds. Nobody is there
|
# to boot from CD or DVD...") and gives up after a few seconds. Nobody is there
|
||||||
@@ -295,7 +325,16 @@ press_key() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
"$qemu_binary" "${qemu_args[@]}"
|
if ((window)); then
|
||||||
|
"$qemu_binary" "${qemu_args[@]}" </dev/null &
|
||||||
|
qemu_job=$!
|
||||||
|
until [[ -s "$pid_file" ]]; do
|
||||||
|
kill -0 "$qemu_job" 2>/dev/null || die "QEMU exited before it started the guest."
|
||||||
|
sleep 0.2
|
||||||
|
done
|
||||||
|
else
|
||||||
|
"$qemu_binary" "${qemu_args[@]}"
|
||||||
|
fi
|
||||||
if ((!from_img)); then
|
if ((!from_img)); then
|
||||||
(
|
(
|
||||||
deadline=$((SECONDS + 30))
|
deadline=$((SECONDS + 30))
|
||||||
|
|||||||
+87
-14
@@ -19,9 +19,12 @@ if [[ "$image_arch" == arm64 ]]; then
|
|||||||
debian_image="${base_qcow2_dir}/debian-13-generic-arm64-20260810-2566.qcow2"
|
debian_image="${base_qcow2_dir}/debian-13-generic-arm64-20260810-2566.qcow2"
|
||||||
alma_image="${base_qcow2_dir}/AlmaLinux-10-GenericCloud-10.2-20260526.0.aarch64.qcow2"
|
alma_image="${base_qcow2_dir}/AlmaLinux-10-GenericCloud-10.2-20260526.0.aarch64.qcow2"
|
||||||
fedora_image="${base_qcow2_dir}/Fedora-Cloud-Base-Generic-44-1.7.aarch64.qcow2"
|
fedora_image="${base_qcow2_dir}/Fedora-Cloud-Base-Generic-44-1.7.aarch64.qcow2"
|
||||||
|
rocky_image="${base_qcow2_dir}/Rocky-9-GenericCloud-Base-9.8-20260525.0.aarch64.qcow2"
|
||||||
ubuntu_image="${base_qcow2_dir}/ubuntu-26.04-server-cloudimg-arm64-20260927.img"
|
ubuntu_image="${base_qcow2_dir}/ubuntu-26.04-server-cloudimg-arm64-20260927.img"
|
||||||
debian_url_default="https://cloud.debian.org/images/cloud/trixie/20260810-2566/debian-13-generic-arm64-20260810-2566.qcow2"
|
debian_url_default="https://cloud.debian.org/images/cloud/trixie/20260810-2566/debian-13-generic-arm64-20260810-2566.qcow2"
|
||||||
alma_url_default="https://repo.almalinux.org/almalinux/10/cloud/aarch64/images/AlmaLinux-10-GenericCloud-10.2-20260526.0.aarch64.qcow2"
|
alma_url_default="https://repo.almalinux.org/almalinux/10/cloud/aarch64/images/AlmaLinux-10-GenericCloud-10.2-20260526.0.aarch64.qcow2"
|
||||||
|
rocky_url_default="https://dl.rockylinux.org/pub/rocky/9/images/aarch64/Rocky-9-GenericCloud-Base-9.8-20260525.0.aarch64.qcow2
|
||||||
|
https://mirrors.nju.edu.cn/rocky/9/images/aarch64/Rocky-9-GenericCloud-Base-9.8-20260525.0.aarch64.qcow2"
|
||||||
fedora_url_default="https://mirrors.tuna.tsinghua.edu.cn/fedora/releases/44/Cloud/aarch64/images/Fedora-Cloud-Base-Generic-44-1.7.aarch64.qcow2
|
fedora_url_default="https://mirrors.tuna.tsinghua.edu.cn/fedora/releases/44/Cloud/aarch64/images/Fedora-Cloud-Base-Generic-44-1.7.aarch64.qcow2
|
||||||
https://mirrors.bfsu.edu.cn/fedora/releases/44/Cloud/aarch64/images/Fedora-Cloud-Base-Generic-44-1.7.aarch64.qcow2
|
https://mirrors.bfsu.edu.cn/fedora/releases/44/Cloud/aarch64/images/Fedora-Cloud-Base-Generic-44-1.7.aarch64.qcow2
|
||||||
https://mirrors.nju.edu.cn/fedora/releases/44/Cloud/aarch64/images/Fedora-Cloud-Base-Generic-44-1.7.aarch64.qcow2"
|
https://mirrors.nju.edu.cn/fedora/releases/44/Cloud/aarch64/images/Fedora-Cloud-Base-Generic-44-1.7.aarch64.qcow2"
|
||||||
@@ -35,14 +38,18 @@ if [[ "$image_arch" == arm64 ]]; then
|
|||||||
# ALMA_IMAGE_SHA256 / FEDORA_IMAGE_SHA256.
|
# ALMA_IMAGE_SHA256 / FEDORA_IMAGE_SHA256.
|
||||||
debian_sha512_default=""
|
debian_sha512_default=""
|
||||||
alma_sha256_default=""
|
alma_sha256_default=""
|
||||||
|
rocky_sha256_default="24692a444f1f0b8bb95375c38c8b43f8099a115347623691be2c330b40c8a1fe"
|
||||||
fedora_sha256_default=""
|
fedora_sha256_default=""
|
||||||
else
|
else
|
||||||
debian_image="${base_qcow2_dir}/debian-13-generic-amd64-20260810-2566.qcow2"
|
debian_image="${base_qcow2_dir}/debian-13-generic-amd64-20260810-2566.qcow2"
|
||||||
alma_image="${base_qcow2_dir}/AlmaLinux-10-GenericCloud-10.2-20260526.0.x86_64.qcow2"
|
alma_image="${base_qcow2_dir}/AlmaLinux-10-GenericCloud-10.2-20260526.0.x86_64.qcow2"
|
||||||
fedora_image="${base_qcow2_dir}/Fedora-Cloud-Base-Generic-44-1.7.x86_64.qcow2"
|
fedora_image="${base_qcow2_dir}/Fedora-Cloud-Base-Generic-44-1.7.x86_64.qcow2"
|
||||||
|
rocky_image="${base_qcow2_dir}/Rocky-9-GenericCloud-Base-9.8-20260525.0.x86_64.qcow2"
|
||||||
ubuntu_image="${base_qcow2_dir}/ubuntu-26.04-server-cloudimg-amd64-20260927.img"
|
ubuntu_image="${base_qcow2_dir}/ubuntu-26.04-server-cloudimg-amd64-20260927.img"
|
||||||
debian_url_default="https://cloud.debian.org/images/cloud/trixie/20260810-2566/debian-13-generic-amd64-20260810-2566.qcow2"
|
debian_url_default="https://cloud.debian.org/images/cloud/trixie/20260810-2566/debian-13-generic-amd64-20260810-2566.qcow2"
|
||||||
alma_url_default="https://repo.almalinux.org/almalinux/10/cloud/x86_64/images/AlmaLinux-10-GenericCloud-10.2-20260526.0.x86_64.qcow2"
|
alma_url_default="https://repo.almalinux.org/almalinux/10/cloud/x86_64/images/AlmaLinux-10-GenericCloud-10.2-20260526.0.x86_64.qcow2"
|
||||||
|
rocky_url_default="https://dl.rockylinux.org/pub/rocky/9/images/x86_64/Rocky-9-GenericCloud-Base-9.8-20260525.0.x86_64.qcow2
|
||||||
|
https://mirrors.nju.edu.cn/rocky/9/images/x86_64/Rocky-9-GenericCloud-Base-9.8-20260525.0.x86_64.qcow2"
|
||||||
fedora_url_default="https://mirrors.tuna.tsinghua.edu.cn/fedora/releases/44/Cloud/x86_64/images/Fedora-Cloud-Base-Generic-44-1.7.x86_64.qcow2
|
fedora_url_default="https://mirrors.tuna.tsinghua.edu.cn/fedora/releases/44/Cloud/x86_64/images/Fedora-Cloud-Base-Generic-44-1.7.x86_64.qcow2
|
||||||
https://mirrors.bfsu.edu.cn/fedora/releases/44/Cloud/x86_64/images/Fedora-Cloud-Base-Generic-44-1.7.x86_64.qcow2
|
https://mirrors.bfsu.edu.cn/fedora/releases/44/Cloud/x86_64/images/Fedora-Cloud-Base-Generic-44-1.7.x86_64.qcow2
|
||||||
https://mirrors.nju.edu.cn/fedora/releases/44/Cloud/x86_64/images/Fedora-Cloud-Base-Generic-44-1.7.x86_64.qcow2"
|
https://mirrors.nju.edu.cn/fedora/releases/44/Cloud/x86_64/images/Fedora-Cloud-Base-Generic-44-1.7.x86_64.qcow2"
|
||||||
@@ -51,6 +58,7 @@ else
|
|||||||
https://cloud-images.ubuntu.com/releases/26.04/release-20260927/ubuntu-26.04-server-cloudimg-amd64.img"
|
https://cloud-images.ubuntu.com/releases/26.04/release-20260927/ubuntu-26.04-server-cloudimg-amd64.img"
|
||||||
debian_sha512_default="f6978100d8031c266d55d7815ceea7fcdeacf28e1e5834fdb9c94ac96880a054a6e6f8681c2d3b0584e0057eaf3ef7353856b85212d04134744faa9b3bb1f24f"
|
debian_sha512_default="f6978100d8031c266d55d7815ceea7fcdeacf28e1e5834fdb9c94ac96880a054a6e6f8681c2d3b0584e0057eaf3ef7353856b85212d04134744faa9b3bb1f24f"
|
||||||
alma_sha256_default="47f2218668dd4776be140dd92fa3bea700be1766e2c7d88bdfd6a4b50f477b4d"
|
alma_sha256_default="47f2218668dd4776be140dd92fa3bea700be1766e2c7d88bdfd6a4b50f477b4d"
|
||||||
|
rocky_sha256_default="92c206cc6f790c61583247eefe87890f8828420662c17cacf247cec78ab4eec8"
|
||||||
fedora_sha256_default="28680fe5b371a5a82ebf43a31926e086a168e59949d03969c5093e7071f90b7f"
|
fedora_sha256_default="28680fe5b371a5a82ebf43a31926e086a168e59949d03969c5093e7071f90b7f"
|
||||||
ubuntu_sha256_default="8800651811af9a85465ad1d552add729947bb16488dddb4a9b5305a3d97332b2"
|
ubuntu_sha256_default="8800651811af9a85465ad1d552add729947bb16488dddb4a9b5305a3d97332b2"
|
||||||
fi
|
fi
|
||||||
@@ -107,8 +115,10 @@ windows_qcow2_path() {
|
|||||||
iso="$(basename -- "$iso")"
|
iso="$(basename -- "$iso")"
|
||||||
printf '%s/%s.qcow2\n' "$base_qcow2_dir" "${iso%.*}"
|
printf '%s/%s.qcow2\n' "$base_qcow2_dir" "${iso%.*}"
|
||||||
elif ! find_windows_image "$base_qcow2_dir" qcow2; then
|
elif ! find_windows_image "$base_qcow2_dir" qcow2; then
|
||||||
printf 'Error: no Windows 11 %s ISO (*Win*11*%s*.iso) found in %s\n' \
|
printf 'Error: no Windows 11 %s base QCOW2 image (*Win*11*%s*.qcow2) found in %s\n' \
|
||||||
"$windows_arch" "$windows_arch" "$install_iso_dir" >&2
|
"$windows_arch" "$windows_arch" "$base_qcow2_dir" >&2
|
||||||
|
printf 'Copy a base image there, set WIN11_VM_BASE_IMAGE, or build one from an ISO in %s with build-windows-base.sh.\n' \
|
||||||
|
"$install_iso_dir" >&2
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
@@ -118,27 +128,29 @@ windows_qcow2_path() {
|
|||||||
# them in order.
|
# them in order.
|
||||||
debian_url="${DEBIAN_IMAGE_URL:-$debian_url_default}"
|
debian_url="${DEBIAN_IMAGE_URL:-$debian_url_default}"
|
||||||
alma_url="${ALMA_IMAGE_URL:-$alma_url_default}"
|
alma_url="${ALMA_IMAGE_URL:-$alma_url_default}"
|
||||||
|
rocky_url="${ROCKY_IMAGE_URL:-$rocky_url_default}"
|
||||||
fedora_url="${FEDORA_IMAGE_URL:-$fedora_url_default}"
|
fedora_url="${FEDORA_IMAGE_URL:-$fedora_url_default}"
|
||||||
ubuntu_url="${UBUNTU_IMAGE_URL:-$ubuntu_url_default}"
|
ubuntu_url="${UBUNTU_IMAGE_URL:-$ubuntu_url_default}"
|
||||||
openssh_url="${OPENSSH_WIN_ZIP_URL:-https://github.com/PowerShell/Win32-OpenSSH/releases/download/10.0.0.0p2-Preview/${openssh_zip}}"
|
openssh_url="${OPENSSH_WIN_ZIP_URL:-https://wangguofang.net/github.com/PowerShell/Win32-OpenSSH/releases/download/10.0.0.0p2-Preview/${openssh_zip}}"
|
||||||
virtio_url="${VIRTIO_WIN_ISO_URL:-https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/stable-virtio/virtio-win.iso}"
|
virtio_url="${VIRTIO_WIN_ISO_URL:-https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/stable-virtio/virtio-win.iso}"
|
||||||
debian_sha512="${DEBIAN_IMAGE_SHA512:-$debian_sha512_default}"
|
debian_sha512="${DEBIAN_IMAGE_SHA512:-$debian_sha512_default}"
|
||||||
alma_sha256="${ALMA_IMAGE_SHA256:-$alma_sha256_default}"
|
alma_sha256="${ALMA_IMAGE_SHA256:-$alma_sha256_default}"
|
||||||
|
rocky_sha256="${ROCKY_IMAGE_SHA256:-$rocky_sha256_default}"
|
||||||
fedora_sha256="${FEDORA_IMAGE_SHA256:-$fedora_sha256_default}"
|
fedora_sha256="${FEDORA_IMAGE_SHA256:-$fedora_sha256_default}"
|
||||||
ubuntu_sha256="${UBUNTU_IMAGE_SHA256:-$ubuntu_sha256_default}"
|
ubuntu_sha256="${UBUNTU_IMAGE_SHA256:-$ubuntu_sha256_default}"
|
||||||
force_download=false
|
force_download=false
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
cat <<EOF
|
cat <<EOF
|
||||||
Usage: $(basename "$0") [--force] [all|debian13|almalinux10|fedora44|ubuntu2604|virtio-win|openssh-win|windows11] ...
|
Usage: $(basename "$0") [--force] [all|debian13|almalinux10|rocky9|fedora44|ubuntu2604|virtio-win|openssh-win|windows11] ...
|
||||||
$(basename "$0") --print-path TARGET [--iso]
|
$(basename "$0") --print-path TARGET [--iso]
|
||||||
|
|
||||||
Download one or more shared VM images. With no arguments, all targets are
|
Download one or more shared VM images. With no arguments, all targets are
|
||||||
processed. The Debian/AlmaLinux/Fedora/Ubuntu image architecture (arm64 or amd64)
|
processed. The Debian/AlmaLinux/Rocky Linux/Fedora/Ubuntu image architecture (arm64 or amd64)
|
||||||
is auto-detected from the host; override it with VM_IMAGE_ARCH.
|
is auto-detected from the host; override it with VM_IMAGE_ARCH.
|
||||||
|
|
||||||
When aria2c is installed and an image has several mirrors, the image is
|
When aria2c is installed, an image is downloaded with 16 connections in total
|
||||||
downloaded from all of them at once, with one connection per mirror.
|
(DOWNLOAD_CONNECTIONS, up to 16), spread across all its mirrors if it has several.
|
||||||
Otherwise curl or wget downloads from the mirrors in order.
|
Otherwise curl or wget downloads from the mirrors in order.
|
||||||
|
|
||||||
The windows11 target is not part of "all". It downloads the Windows 11 ISO for
|
The windows11 target is not part of "all". It downloads the Windows 11 ISO for
|
||||||
@@ -154,6 +166,36 @@ The Windows ISO is found automatically in install-iso/ by matching
|
|||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Ask a yes/no question on the terminal; fails when there is none or the answer
|
||||||
|
# is not yes (an empty answer means yes). stdout carries the printed path, so all dialogue goes to /dev/tty.
|
||||||
|
ask_yes_no() {
|
||||||
|
local answer
|
||||||
|
{ : </dev/tty >/dev/tty; } 2>/dev/null || return 1
|
||||||
|
printf '%s [Y/n] ' "$1" >/dev/tty
|
||||||
|
read -r answer </dev/tty || return 1
|
||||||
|
[[ -z "$answer" || "$answer" == [yY] || "$answer" == [yY][eE][sS] ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
# Print the base QCOW2 path. When the image does not exist and a terminal is
|
||||||
|
# attached, offer to run build-windows-base.sh, which in turn offers to download
|
||||||
|
# the ISO. The builder sets VM_IMAGE_NO_PROMPT so its own lookups never ask.
|
||||||
|
windows_base_path() {
|
||||||
|
local qcow2 builder="${images_dir}/build-windows-base.sh"
|
||||||
|
if qcow2="$(windows_qcow2_path 2>/dev/null)" && [[ -f "$qcow2" ]]; then
|
||||||
|
printf '%s\n' "$qcow2"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
windows_qcow2_path >/dev/null || true
|
||||||
|
if [[ -z "${VM_IMAGE_NO_PROMPT:-}" && -x "$builder" ]] && ask_yes_no "Build the Windows 11 base image now with build-windows-base.sh?"; then
|
||||||
|
# stdout carries the path, so the build's output goes to the terminal.
|
||||||
|
"$builder" >/dev/tty 2>&1 </dev/tty || return 1
|
||||||
|
windows_qcow2_path
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
# Name the expected path if the ISO fixes it; the caller reports it missing.
|
||||||
|
windows_qcow2_path 2>/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
print_image_path() {
|
print_image_path() {
|
||||||
local target="$1"
|
local target="$1"
|
||||||
local media="${2:-qcow2}"
|
local media="${2:-qcow2}"
|
||||||
@@ -161,6 +203,7 @@ print_image_path() {
|
|||||||
case "$target" in
|
case "$target" in
|
||||||
debian13) printf '%s\n' "$debian_image" ;;
|
debian13) printf '%s\n' "$debian_image" ;;
|
||||||
almalinux10) printf '%s\n' "$alma_image" ;;
|
almalinux10) printf '%s\n' "$alma_image" ;;
|
||||||
|
rocky9) printf '%s\n' "$rocky_image" ;;
|
||||||
fedora44) printf '%s\n' "$fedora_image" ;;
|
fedora44) printf '%s\n' "$fedora_image" ;;
|
||||||
ubuntu2604) printf '%s\n' "$ubuntu_image" ;;
|
ubuntu2604) printf '%s\n' "$ubuntu_image" ;;
|
||||||
virtio-win) printf '%s\n' "$virtio_image" ;;
|
virtio-win) printf '%s\n' "$virtio_image" ;;
|
||||||
@@ -169,7 +212,7 @@ print_image_path() {
|
|||||||
if [[ "$media" == iso ]]; then
|
if [[ "$media" == iso ]]; then
|
||||||
windows_iso_path
|
windows_iso_path
|
||||||
else
|
else
|
||||||
windows_qcow2_path
|
windows_base_path
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
@@ -210,7 +253,7 @@ fi
|
|||||||
|
|
||||||
for target in "${targets[@]}"; do
|
for target in "${targets[@]}"; do
|
||||||
case "$target" in
|
case "$target" in
|
||||||
all|debian13|almalinux10|fedora44|ubuntu2604|virtio-win|openssh-win|windows11) ;;
|
all|debian13|almalinux10|rocky9|fedora44|ubuntu2604|virtio-win|openssh-win|windows11) ;;
|
||||||
-h|--help) usage; exit 0 ;;
|
-h|--help) usage; exit 0 ;;
|
||||||
*) printf 'Error: unknown download target: %s\n' "$target" >&2; usage >&2; exit 2 ;;
|
*) printf 'Error: unknown download target: %s\n' "$target" >&2; usage >&2; exit 2 ;;
|
||||||
esac
|
esac
|
||||||
@@ -273,16 +316,17 @@ download_single_source() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Download to $partial with aria2c, splitting the file across all URLs with a
|
# Download to $partial with aria2c, splitting the file across all URLs with up
|
||||||
# single connection to each server.
|
# to DOWNLOAD_CONNECTIONS (default 16) connections in total and to each server.
|
||||||
download_multi_source() {
|
download_multi_source() {
|
||||||
local partial="$1"
|
local partial="$1"
|
||||||
shift
|
shift
|
||||||
|
local connections="${DOWNLOAD_CONNECTIONS:-16}"
|
||||||
|
|
||||||
aria2c \
|
aria2c \
|
||||||
--continue=true \
|
--continue=true \
|
||||||
--split="$#" \
|
--split="$connections" \
|
||||||
--max-connection-per-server=1 \
|
--max-connection-per-server="$connections" \
|
||||||
--file-allocation=none \
|
--file-allocation=none \
|
||||||
--auto-file-renaming=false \
|
--auto-file-renaming=false \
|
||||||
--max-tries=5 \
|
--max-tries=5 \
|
||||||
@@ -414,7 +458,27 @@ download_windows_iso() {
|
|||||||
printf 'Error: node is required to get the Windows 11 ISO URL.\n' >&2
|
printf 'Error: node is required to get the Windows 11 ISO URL.\n' >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
url="$(node "${images_dir}/nodejs/get-windows-iso-url.js" --arch "$windows_arch")"
|
if [[ ! -d "${images_dir}/nodejs/node_modules/playwright-core" ]]; then
|
||||||
|
printf 'Error: the Node.js dependencies are not installed (npm ci has not been run in %s/nodejs).\n' \
|
||||||
|
"$images_dir" >&2
|
||||||
|
command -v npm >/dev/null 2>&1 || { printf 'Error: npm is required; install Node.js with npm.\n' >&2; exit 1; }
|
||||||
|
ask_yes_no "Run npm ci in ${images_dir}/nodejs now?" || exit 1
|
||||||
|
(cd "${images_dir}/nodejs" && npm ci) || exit 1
|
||||||
|
fi
|
||||||
|
local status=0
|
||||||
|
url="$(node "${images_dir}/nodejs/get-windows-iso-url.js" --arch "$windows_arch")" || status=$?
|
||||||
|
if ((status == 2)) && [[ -z "${CHROME_PATH:-}" ]]; then
|
||||||
|
# Exit status 2 means Chromium was not found (the script has said so).
|
||||||
|
ask_yes_no "Install Chromium with playwright-core now?" || exit 1
|
||||||
|
# Default to the npmmirror mirror (the official CDN is slow in China); set
|
||||||
|
# PLAYWRIGHT_DOWNLOAD_HOST to override it.
|
||||||
|
(cd "${images_dir}/nodejs" \
|
||||||
|
&& PLAYWRIGHT_DOWNLOAD_HOST="${PLAYWRIGHT_DOWNLOAD_HOST:-https://cdn.npmmirror.com/binaries/playwright}" \
|
||||||
|
npx playwright-core install chromium) || exit 1
|
||||||
|
url="$(node "${images_dir}/nodejs/get-windows-iso-url.js" --arch "$windows_arch")" || exit 1
|
||||||
|
elif ((status != 0)); then
|
||||||
|
exit "$status"
|
||||||
|
fi
|
||||||
[[ -n "$url" ]] || { printf 'Error: no Windows 11 ISO URL.\n' >&2; exit 1; }
|
[[ -n "$url" ]] || { printf 'Error: no Windows 11 ISO URL.\n' >&2; exit 1; }
|
||||||
name="${url%%\?*}"
|
name="${url%%\?*}"
|
||||||
name="${name##*/}"
|
name="${name##*/}"
|
||||||
@@ -446,6 +510,15 @@ if wants_target almalinux10; then
|
|||||||
"$alma_sha256"
|
"$alma_sha256"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if wants_target rocky9; then
|
||||||
|
download_file \
|
||||||
|
'Rocky Linux 9 cloud image' \
|
||||||
|
"$rocky_url" \
|
||||||
|
"$rocky_image" \
|
||||||
|
sha256 \
|
||||||
|
"$rocky_sha256"
|
||||||
|
fi
|
||||||
|
|
||||||
if wants_target fedora44; then
|
if wants_target fedora44; then
|
||||||
download_file \
|
download_file \
|
||||||
'Fedora 44 cloud image' \
|
'Fedora 44 cloud image' \
|
||||||
|
|||||||
@@ -5,12 +5,11 @@
|
|||||||
// Usage: node get-windows-iso-url.js [--arch x64|arm64] [--lang NAME]
|
// Usage: node get-windows-iso-url.js [--arch x64|arm64] [--lang NAME]
|
||||||
// Environment:
|
// Environment:
|
||||||
// PLAYWRIGHT_CORE playwright-core directory (default: node_modules here)
|
// PLAYWRIGHT_CORE playwright-core directory (default: node_modules here)
|
||||||
// CHROME_PATH Chromium executable (default: newest one in ~/.cache/ms-playwright)
|
// CHROME_PATH Chromium executable (default: the one Playwright installed)
|
||||||
// The URL expires after about 24 hours and may be bound to the IP that
|
// The URL expires after about 24 hours and may be bound to the IP that
|
||||||
// requested it. Only the URL goes to stdout; logs go to stderr.
|
// requested it. Only the URL goes to stdout; logs go to stderr.
|
||||||
|
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const os = require('os');
|
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
||||||
const args = process.argv.slice(2);
|
const args = process.argv.slice(2);
|
||||||
@@ -28,8 +27,7 @@ Options:
|
|||||||
|
|
||||||
Environment:
|
Environment:
|
||||||
PLAYWRIGHT_CORE playwright-core directory (default: node_modules here)
|
PLAYWRIGHT_CORE playwright-core directory (default: node_modules here)
|
||||||
CHROME_PATH Chromium executable (default: newest one in
|
CHROME_PATH Chromium executable (default: the one Playwright installed)
|
||||||
~/.cache/ms-playwright)
|
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
node get-windows-iso-url.js --arch x64 --lang "Chinese Simplified"
|
node get-windows-iso-url.js --arch x64 --lang "Chinese Simplified"
|
||||||
@@ -60,17 +58,20 @@ function findPlaywright() {
|
|||||||
process.exit(2);
|
process.exit(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
function findChrome() {
|
// CHROME_PATH wins; otherwise Playwright picks the Chromium it installed (in
|
||||||
if (process.env.CHROME_PATH) return process.env.CHROME_PATH;
|
// its own per-OS cache, PLAYWRIGHT_BROWSERS_PATH if set).
|
||||||
const root = path.join(os.homedir(), '.cache/ms-playwright');
|
function launchOptions(chromium) {
|
||||||
const dirs = fs.existsSync(root) ? fs.readdirSync(root).filter(d => /^chromium-\d+$/.test(d)).sort() : [];
|
if (process.env.CHROME_PATH) return { executablePath: process.env.CHROME_PATH, headless: true };
|
||||||
if (!dirs.length) { console.error('Chromium not found: set CHROME_PATH or run npx playwright-core install chromium'); process.exit(2); }
|
if (!fs.existsSync(chromium.executablePath())) {
|
||||||
return path.join(root, dirs.pop(), 'chrome-linux64/chrome');
|
console.error('Chromium not found: set CHROME_PATH or run npx playwright-core install chromium');
|
||||||
|
process.exit(2);
|
||||||
|
}
|
||||||
|
return { headless: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
const { chromium } = require(findPlaywright());
|
const { chromium } = require(findPlaywright());
|
||||||
const browser = await chromium.launch({ executablePath: findChrome(), headless: true });
|
const browser = await chromium.launch(launchOptions(chromium));
|
||||||
try {
|
try {
|
||||||
const ctx = await browser.newContext({
|
const ctx = await browser.newContext({
|
||||||
locale: 'en-US',
|
locale: 'en-US',
|
||||||
|
|||||||
@@ -131,6 +131,11 @@
|
|||||||
</RunSynchronousCommand>
|
</RunSynchronousCommand>
|
||||||
<RunSynchronousCommand wcm:action="add">
|
<RunSynchronousCommand wcm:action="add">
|
||||||
<Order>4</Order>
|
<Order>4</Order>
|
||||||
|
<Description>Add the VirtIO drivers from the virtio-win ISO to the driver store</Description>
|
||||||
|
<Path>powershell.exe -NoProfile -ExecutionPolicy Bypass -Command "& ('{0}:\Install-VirtIO-Drivers.ps1' -f (Get-Volume -FileSystemLabel ANSWER).DriveLetter); exit $LASTEXITCODE"</Path>
|
||||||
|
</RunSynchronousCommand>
|
||||||
|
<RunSynchronousCommand wcm:action="add">
|
||||||
|
<Order>5</Order>
|
||||||
<Description>Generalize the image and power off</Description>
|
<Description>Generalize the image and power off</Description>
|
||||||
<Path>C:\Windows\System32\Sysprep\sysprep.exe /generalize /oobe /shutdown /quiet</Path>
|
<Path>C:\Windows\System32\Sysprep\sysprep.exe /generalize /oobe /shutdown /quiet</Path>
|
||||||
</RunSynchronousCommand>
|
</RunSynchronousCommand>
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
# Runs in audit mode, before sysprep: adds the VirtIO drivers from the virtio-win
|
||||||
|
# ISO to the driver store. The devices are not present during the installation
|
||||||
|
# (it uses ramfb and e1000e), so pnputil only stages the packages; they survive
|
||||||
|
# /generalize, and Windows binds them on the first start with virtio devices.
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
|
||||||
|
$arch = if ([Runtime.InteropServices.RuntimeInformation]::OSArchitecture -eq 'Arm64') { 'ARM64' } else { 'amd64' }
|
||||||
|
$roots = (Get-PSDrive -PSProvider FileSystem).Root
|
||||||
|
$drivers = @(
|
||||||
|
@{ Folder = 'NetKVM'; Inf = 'netkvm.inf'; Required = $true },
|
||||||
|
@{ Folder = 'viogpudo'; Inf = 'viogpudo.inf'; Required = $true },
|
||||||
|
@{ Folder = 'vioserial'; Inf = 'vioser.inf' },
|
||||||
|
@{ Folder = 'Balloon'; Inf = 'balloon.inf' },
|
||||||
|
@{ Folder = 'viorng'; Inf = 'viorng.inf' },
|
||||||
|
@{ Folder = 'vioinput'; Inf = 'vioinput.inf' }
|
||||||
|
)
|
||||||
|
|
||||||
|
$installed = 0
|
||||||
|
foreach ($driver in $drivers) {
|
||||||
|
$inf = $roots |
|
||||||
|
ForEach-Object { Join-Path $_ "$($driver.Folder)\w11\$arch\$($driver.Inf)" } |
|
||||||
|
Where-Object { Test-Path -LiteralPath $_ } |
|
||||||
|
Select-Object -First 1
|
||||||
|
if (-not $inf) {
|
||||||
|
if ($driver.Required) {
|
||||||
|
throw "The $arch $($driver.Folder) driver was not found on the attached media."
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
& pnputil.exe /add-driver $inf /install | Out-Host
|
||||||
|
# 3010 asks for a restart; 259 means that no device uses the driver.
|
||||||
|
if ($LASTEXITCODE -notin 0, 259, 3010) {
|
||||||
|
throw "pnputil failed to install $inf (exit code $LASTEXITCODE)."
|
||||||
|
}
|
||||||
|
$installed++
|
||||||
|
}
|
||||||
|
Write-Host "Added $installed VirtIO $arch driver packages."
|
||||||
Reference in new issue
Block a user