+1
-1
@@ -1,5 +1,5 @@
|
|||||||
/build
|
/build
|
||||||
|
/cache
|
||||||
/output
|
/output
|
||||||
/win11-builder-amd64
|
/win11-builder-amd64
|
||||||
/win11-builder-arm64
|
/win11-builder-arm64
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+270
@@ -0,0 +1,270 @@
|
|||||||
|
#!/usr/bin/env /bin/bash
|
||||||
|
declare -r BASEDIR="`pwd`"
|
||||||
|
|
||||||
|
declare -r mode="full" # one of 'full'|'unattend'
|
||||||
|
|
||||||
|
declare -r ISO_BASEDIR="/mnt/machines/iso"
|
||||||
|
declare -r CONFIG_DIR="${BASEDIR}/configs"
|
||||||
|
|
||||||
|
declare -r UNATTEND_XML="${2:-"${CONFIG_DIR}/autounattend-builder-amd64-20260527.xml"}"
|
||||||
|
|
||||||
|
#declare -r WINDOWS_ISO="${ISO_BASEDIR}/nano11-25H2-Pro-Eng-Arm64-20260513.iso"
|
||||||
|
#declare -r WINDOWS_ISO="${ISO_BASEDIR}/tiny11-25H2-Pro-Eng-Arm64-20260514.iso"
|
||||||
|
#declare -r WINDOWS_ISO="${ISO_BASEDIR}/nano11-25H2-English-Pro-2026-03-15.iso"
|
||||||
|
#declare -r WINDOWS_ISO="${ISO_BASEDIR}/Tiny11-25H2-English-Pro-2026-05-20.iso"
|
||||||
|
#declare -r WINDOWS_ISO="${ISO_BASEDIR}/Tiny11Core-25H2-English-Pro-2026-05-20.iso"
|
||||||
|
#declare -r WINDOWS_ISO="${ISO_BASEDIR}/nano11-25H2-English-Pro-2026-05-20.iso"
|
||||||
|
|
||||||
|
#declare -r WINDOWS_ISO="${ISO_BASEDIR}/nano11-base-25H2-Pro-Eng-amd64-20260526.iso"
|
||||||
|
declare -r WINDOWS_ISO="${ISO_BASEDIR}/nano11-base-25H2-Pro-Eng-arm64-20260526.iso"
|
||||||
|
|
||||||
|
declare -r VIRTIO_ISO="${ISO_BASEDIR}/virtio-win-0.1.285.iso"
|
||||||
|
#declare -r VIRTIO_ISO="${BASEDIR}/virtio-win-0.1.285.iso"
|
||||||
|
|
||||||
|
|
||||||
|
######################################################################################
|
||||||
|
#
|
||||||
|
#
|
||||||
|
|
||||||
|
function detect_iso_arch {
|
||||||
|
local iso_file="${1}"
|
||||||
|
local fallback_arch="${2}"
|
||||||
|
case "${iso_file@L}" in
|
||||||
|
*-amd64-*|*-x64-*)
|
||||||
|
echo "amd64" ;;
|
||||||
|
*-arm64-*|*-aarch64-*)
|
||||||
|
echo "arm64" ;;
|
||||||
|
*) # Unknown use host arch as fallback
|
||||||
|
echo "NOTICE: Cannot detect architecture from ISO name, using fallback" >&2
|
||||||
|
echo "${fallback_arch}" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
function detect_host_arch {
|
||||||
|
case "$(uname -m | tr '[:upper:]' '[:lower:]')" in
|
||||||
|
"x86_64") echo "amd64" ;;
|
||||||
|
"aarch64") echo "arm64" ;;
|
||||||
|
*) return 1 ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
######################################################################################
|
||||||
|
# Detect host and guest architecture
|
||||||
|
declare -r HOST_ARCH="$(detect_host_arch)"
|
||||||
|
[[ -n "${HOST_ARCH}" ]] || {
|
||||||
|
echo "Failed to detect host arch" >&2;
|
||||||
|
exit 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare -r TARGET_ARCH="$(detect_iso_arch "${WINDOWS_ISO}")"
|
||||||
|
[[ -n "${TARGET_ARCH}" ]] || {
|
||||||
|
echo "Failed to detect target arch" >&2;
|
||||||
|
exit 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "${mode}" in
|
||||||
|
"full")
|
||||||
|
echo "Generating full ${TARGET_ARCH} installation ISO image..." >&2
|
||||||
|
declare -r DEFAULT_OUTPUT_FILE="install-${TARGET_ARCH}-$(date +%Y%m%d-%H%M00).iso"
|
||||||
|
;;
|
||||||
|
"unattend")
|
||||||
|
echo "Generating unattended ${TARGET_ARCH} ISO image..." >&2
|
||||||
|
declare -r DEFAULT_OUTPUT_FILE="unattend-${TARGET_ARCH}-$(date +%Y%m%d-%H%M00).iso"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Invalid mode '${mode}' expected either 'full'|'unattend'" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Output file
|
||||||
|
#
|
||||||
|
declare -r OUTPUT_FILE="${1:-${DEFAULT_OUTPUT_FILE}}"
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Validate input XML
|
||||||
|
#
|
||||||
|
xmllint -noout "${UNATTEND_XML}" || {
|
||||||
|
echo "$(basename "${UNATTEND_XML}") contains invalid XML, aborting" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
mkdir -p "${BASEDIR}/build" || exit $?
|
||||||
|
mkdir -p "${BASEDIR}/output" || exit $?
|
||||||
|
|
||||||
|
# Convert architecture for virtio installation media
|
||||||
|
case "${TARGET_ARCH}" in
|
||||||
|
"arm64")
|
||||||
|
VIRTIO_ARCH="${TARGET_ARCH@U}" ;; # Virtio ISO uses "ARM64"
|
||||||
|
*)
|
||||||
|
VIRTIO_ARCH="${TARGET_ARCH}" ;; # Virtio ISO uses "amd64"
|
||||||
|
esac
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Generate builder script
|
||||||
|
#
|
||||||
|
cat -> "${BASEDIR}/build/genimage-${TARGET_ARCH}.sh" <<-EOF
|
||||||
|
#!/usr/bin/env /bin/ash
|
||||||
|
|
||||||
|
#
|
||||||
|
# Required packages
|
||||||
|
#
|
||||||
|
echo "Installing required packages..." >&2
|
||||||
|
apk update || exit \$?
|
||||||
|
apk add xorriso 7zip || exit \$?
|
||||||
|
|
||||||
|
#
|
||||||
|
# Prepare directories / mountpoints
|
||||||
|
#
|
||||||
|
echo "Preparing build directory and mountpoints..." >&2
|
||||||
|
mkdir -p /tmp/base || exit \$?
|
||||||
|
mkdir -p /tmp/virtio || exit \$?
|
||||||
|
mkdir -p /tmp/build || exit \$?
|
||||||
|
|
||||||
|
#
|
||||||
|
# Mounting (if possible) / unpacking of ISO images
|
||||||
|
#
|
||||||
|
if [[ "x${mode}" == "xfull" ]]; then
|
||||||
|
echo "Attempting to mount windows ISO image..." >&2
|
||||||
|
mount -t udf -o loop,ro /input/windows.iso /tmp/base/ 2>/dev/null || {
|
||||||
|
echo "Unpacking windows ISO image (fallback)..." >&2
|
||||||
|
7z x -bb0 -o/tmp/base/ /input/windows.iso || exit \$?
|
||||||
|
}
|
||||||
|
else
|
||||||
|
echo "Building unattend ISO image, skipping windows mount" >&2
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Attempting to mount virtio-win ISO image..." >&2
|
||||||
|
mount -o loop,ro /input/virtio.iso /tmp/virtio/ 2>/dev/null || {
|
||||||
|
echo "Unpacking virtio-win ISO image (fallback)..." >&2
|
||||||
|
7z x -bb0 -o/tmp/virtio/ /input/virtio.iso || exit \$?
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# Autounattend XML configuration file (replaces any existing one)
|
||||||
|
#
|
||||||
|
echo "Copying autounattend.xml..." >&2
|
||||||
|
sed -e 's|processorArchitecture="[^"]\+"|processorArchitecture="${TARGET_ARCH}"|' \
|
||||||
|
< /input/autounattend.xml \
|
||||||
|
> /tmp/build/autounattend.xml || exit \$?
|
||||||
|
|
||||||
|
#
|
||||||
|
# Required drivers for installation (into "$WinPEDriver$")
|
||||||
|
#
|
||||||
|
echo "Creating \\\$WinPEDriver\\\$..." >&2
|
||||||
|
mkdir -p "/tmp/build/\\\$WinPEDriver\\\$" || exit \$?
|
||||||
|
for x in NetKVM viostor vioscsi; do
|
||||||
|
cp -vfR /tmp/virtio/\${x}/w11/${VIRTIO_ARCH} "/tmp/build/\\\$WinPEDriver\\\$/\${x}" || exit \$?
|
||||||
|
done
|
||||||
|
|
||||||
|
#
|
||||||
|
# OEM files (drivers, packages, etc.)
|
||||||
|
#
|
||||||
|
echo "Copying OEM files..." >&2
|
||||||
|
|
||||||
|
mkdir -p \
|
||||||
|
"/tmp/build/\\\$OEM\\\$/\\\$\\\$" \
|
||||||
|
"/tmp/build/\\\$OEM\\\$/\\\$1" \
|
||||||
|
|| exit \$?
|
||||||
|
|
||||||
|
# Viofs files for use in the final OS (virtiofsd)
|
||||||
|
mkdir -p "/tmp/build/\\\$OEM\\\$/\\\$1/drivers" || exit \$?
|
||||||
|
for x in /tmp/virtio/viofs/w11/${VIRTIO_ARCH}; do
|
||||||
|
drvname="\$(echo "\${x}" | cut -d'/' -f4)" || exit \$?
|
||||||
|
cp -vfR "\${x}" "/tmp/build/\\\$OEM\\\$/\\\$1/drivers/\${drvname}" || exit \$?
|
||||||
|
done
|
||||||
|
|
||||||
|
#
|
||||||
|
# Virtio drivers for auto-installation
|
||||||
|
#
|
||||||
|
echo "Copying virtio drivers for automatic installation..." >&2
|
||||||
|
|
||||||
|
mkdir -p "/tmp/build/drivers" || exit \$?
|
||||||
|
for x in /tmp/virtio/*/w11/${VIRTIO_ARCH}; do
|
||||||
|
drvname="\$(echo "\${x}" | cut -d'/' -f4)" || exit \$?
|
||||||
|
cp -vfR "\${x}" "/tmp/build/drivers/\${drvname}" || exit \$?
|
||||||
|
done
|
||||||
|
|
||||||
|
#
|
||||||
|
# ISO image generation
|
||||||
|
#
|
||||||
|
if [[ "x${mode}" == "xfull" ]]; then
|
||||||
|
echo "Generating ISO boot image..." >&2
|
||||||
|
mkisofs \\
|
||||||
|
-quiet \\
|
||||||
|
-b boot/etfsboot.com \\
|
||||||
|
-no-emul-boot \\
|
||||||
|
-boot-load-size 8 \\
|
||||||
|
-iso-level 3 -J -l -D -N -U -joliet-long -relaxed-filenames -rational-rock \\
|
||||||
|
-eltorito-alt-boot -e efi/microsoft/boot/efisys_noprompt.bin -no-emul-boot \\
|
||||||
|
-m "/tmp/base/autounattend.xml" \\
|
||||||
|
-V "Nano11-Builder" \\
|
||||||
|
-o "/output/$(basename "${OUTPUT_FILE}")" \\
|
||||||
|
"/tmp/base/" "/tmp/build/" || exit \$?
|
||||||
|
else
|
||||||
|
echo "Generating ISO unattend image..." >&2
|
||||||
|
mkisofs \\
|
||||||
|
-quiet \\
|
||||||
|
-iso-level 3 -J -l -D -N -U -joliet-long -relaxed-filenames -rational-rock \\
|
||||||
|
-V "Nano11-Builder-Unattend" \\
|
||||||
|
-o "/output/$(basename "${OUTPUT_FILE}")" \\
|
||||||
|
"/tmp/build/" || exit \$?
|
||||||
|
fi
|
||||||
|
|
||||||
|
# genisoimage \\
|
||||||
|
# -quiet \\
|
||||||
|
# -b boot/etfsboot.com \\
|
||||||
|
# -no-emul-boot \\
|
||||||
|
# -boot-load-seg 1984 \\
|
||||||
|
# -boot-load-size 8 \\
|
||||||
|
# -iso-level 3 -J -l -D -N -U -joliet-long -allow-limited-size -relaxed-filenames -rock \\
|
||||||
|
# -eltorito-alt-boot -e efi/microsoft/boot/efisys_noprompt.bin \\
|
||||||
|
# -m "/tmp/base/autounattend.xml" \\
|
||||||
|
# -V "Nano11-Builder" \\
|
||||||
|
# -o "/build/output.iso" \\
|
||||||
|
# "/tmp/base/" "/tmp/build/" || exit \$?
|
||||||
|
|
||||||
|
#
|
||||||
|
#
|
||||||
|
#
|
||||||
|
echo "Cleaning up..." >&2
|
||||||
|
umount /tmp/base/ 2>/dev/null
|
||||||
|
umount /tmp/virtio/ 2>/dev/null
|
||||||
|
EOF
|
||||||
|
|
||||||
|
chmod 0755 "${BASEDIR}/build/genimage-${TARGET_ARCH}.sh" || exit $?
|
||||||
|
|
||||||
|
declare -r CONTAINER_IMAGE="alpine:3.23"
|
||||||
|
declare -r CONTAINER_ARGS=(
|
||||||
|
"-it --rm --name isobuilder-${TARGET_ARCH}"
|
||||||
|
"-v ${VIRTIO_ISO}:/input/virtio.iso:ro,Z"
|
||||||
|
"-v ${WINDOWS_ISO}:/input/windows.iso:ro,Z"
|
||||||
|
"-v ${UNATTEND_XML}:/input/autounattend.xml:ro,Z"
|
||||||
|
"-v ${BASEDIR}/build:/build:ro,Z"
|
||||||
|
"-v ${BASEDIR}/output:/output:rw,Z"
|
||||||
|
"--privileged --network host"
|
||||||
|
)
|
||||||
|
|
||||||
|
#
|
||||||
|
# Run generation script in alpine 3.23 container
|
||||||
|
#
|
||||||
|
if type -p docker &>/dev/null; then
|
||||||
|
echo "Running isobuilder in docker container" >&2
|
||||||
|
docker run ${CONTAINER_ARGS[@]} ${CONTAINER_IMAGE} \
|
||||||
|
/build/genimage-${TARGET_ARCH}.sh || exit $?
|
||||||
|
elif type -p podman &>/dev/null; then
|
||||||
|
echo "Running isobuilder in podman container" >&2
|
||||||
|
podman run ${CONTAINER_ARGS[@]} ${CONTAINER_IMAGE} \
|
||||||
|
/build/genimage-${TARGET_ARCH}.sh || exit $?
|
||||||
|
else
|
||||||
|
echo "Failed to start build container: no runtime available" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
(cd "${BASEDIR}/output" && ls -sh "$(basename "${OUTPUT_FILE}")") || exit $?
|
||||||
Executable
+554
@@ -0,0 +1,554 @@
|
|||||||
|
#!/usr/bin/env /bin/bash
|
||||||
|
declare -r TOP_BASEDIR="`pwd`"
|
||||||
|
|
||||||
|
#
|
||||||
|
# ISO images
|
||||||
|
#
|
||||||
|
declare -r ISO_BASEDIR="/mnt/machines/iso"
|
||||||
|
|
||||||
|
# Windows installation ISO
|
||||||
|
#declare -r INSTALL_ISO="${ISO_BASEDIR}/Win11_25H2_English_x64_v2.iso"
|
||||||
|
#declare -r INSTALL_ISO="${ISO_BASEDIR}/nano11-25H2-English-Pro-2026-03-20.iso"
|
||||||
|
declare -r INSTALL_ISO="${TOP_BASEDIR}/output/install-builder-amd64-20260601.iso"
|
||||||
|
|
||||||
|
# Unattended installation add-on ISO
|
||||||
|
#declare -r UNATTEND_ISO="${TOP_BASEDIR}/output/unattend.iso"
|
||||||
|
|
||||||
|
declare -r VIRTIO_ISO="${ISO_BASEDIR}/virtio-win-0.1.285.iso"
|
||||||
|
|
||||||
|
#
|
||||||
|
# Disk images
|
||||||
|
#
|
||||||
|
declare -r DISK_BASEDIR="/mnt/machines/qemu-machines"
|
||||||
|
#declare -r DISK_IMG="${DISK_BASEDIR}/win11-builder-amd64.qcow2"
|
||||||
|
declare -r DISK_SIZE="32G"
|
||||||
|
|
||||||
|
#
|
||||||
|
# x86-64-v3: Haswell w/o intel-specific instructions (TSX, IBRS)
|
||||||
|
#
|
||||||
|
# x86-64-v1: +cmov,+cx8,+fpu,+fxsr,+mmx,+osfxsr,+sce,+sse,+sse2 (aka "kvm64")
|
||||||
|
# x86-64-v2: +cx16,+lahf-lm,+popcnt,+sse3,+sse4.1,+sse4.2,+ssse3
|
||||||
|
# x86-64-v3: +avx,+avx2,+bmi1,+bmi2,+f16c,+fma,+movbe,+xsave,+aes
|
||||||
|
# x86-64-v4: +avx512f,+avx512bw,+avx512cd,+avx512dq,+avx512vl
|
||||||
|
#
|
||||||
|
declare -r CPU_X64_V2="kvm64,+cx16,+lahf-lm,+popcnt,+sse3,+sse4.1,+sse4.2,+ssse3"
|
||||||
|
declare -r CPU_X64_V3="${CPU_X64_V2},+avx,+avx2,+bmi1,+bmi2,+f16c,+fma,+movbe,+xsave,+aes"
|
||||||
|
declare -r CPU_X64_V4="${CPU_X64_V3},+avx512f,+avx512bw,+avx512cd,+avx512dq,+avx512vl"
|
||||||
|
|
||||||
|
# AMD64: Minimum requirement is X86_64-V3
|
||||||
|
declare -r CPU_OPTIONS_AMD64="-cpu host -smp cpus=4,cores=4,sockets=1"
|
||||||
|
#declare -r CPU_OPTIONS_AMD64="-cpu ${CPU_X64_V3} -smp cpus=4,cores=4,sockets=1"
|
||||||
|
|
||||||
|
# ARM64: Minimum requirement for Windows 11 is Cortext A72?
|
||||||
|
declare -r CPU_OPTIONS_ARM64="-cpu host -smp 4"
|
||||||
|
#declare -r CPU_OPTIONS_ARM64="-cpu max,pauth-impdef=on -smp cpus=4,cores=4,sockets=1"
|
||||||
|
#declare -r CPU_OPTIONS_ARM64="-cpu neoverse-n1 -smp cpus=4,cores=4,sockets=1"
|
||||||
|
#declare -r CPU_OPTIONS_ARM64="-cpu cortext-a76 -smp cpus=4,cores=4,sockets=1"
|
||||||
|
|
||||||
|
#
|
||||||
|
# Memory config
|
||||||
|
#
|
||||||
|
declare -r MEM_SIZE="8G"
|
||||||
|
declare -r MEM_OPTIONS="-m ${MEM_SIZE}"
|
||||||
|
|
||||||
|
#
|
||||||
|
#
|
||||||
|
#
|
||||||
|
declare -r ARM64_EFI_CODE_IMG="/usr/share/edk2/aarch64/QEMU_EFI.fd"
|
||||||
|
declare -r ARM64_EFI_VARS_IMG=""
|
||||||
|
# Optional: required EFI ROM size
|
||||||
|
declare -r ARM64_EFI_CODE_SIZE="64M"
|
||||||
|
|
||||||
|
#################################################################
|
||||||
|
# No user-serviceable parts below this line
|
||||||
|
#################################################################
|
||||||
|
|
||||||
|
# Get builder command
|
||||||
|
declare -r COMMAND="${1:-build}"
|
||||||
|
shift
|
||||||
|
|
||||||
|
function detect_iso_arch {
|
||||||
|
local iso_file="${1}"
|
||||||
|
local fallback_arch="${2}"
|
||||||
|
case "${iso_file@L}" in
|
||||||
|
*-amd64-*|*-x64-*)
|
||||||
|
echo "amd64" ;;
|
||||||
|
*-arm64-*|*-aarch64-*)
|
||||||
|
echo "arm64" ;;
|
||||||
|
*) # Unknown use host arch as fallback
|
||||||
|
echo "NOTICE: Cannot detect architecture from ISO name, using host" >&2
|
||||||
|
echo "${fallback_arch}" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
function detect_host_arch {
|
||||||
|
case "$(uname -m | tr '[:upper:]' '[:lower:]')" in
|
||||||
|
"x86_64") echo "amd64" ;;
|
||||||
|
"aarch64") echo "arm64" ;;
|
||||||
|
*) return 1 ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# Detect host and guest architecture
|
||||||
|
declare -r HOST_ARCH="$(detect_host_arch)"
|
||||||
|
[[ -n "${HOST_ARCH}" ]] || {
|
||||||
|
echo "Failed to detect host arch" >&2;
|
||||||
|
exit 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare -r GUEST_ARCH="$(detect_iso_arch "${INSTALL_ISO}" "${HOST_ARCH}")"
|
||||||
|
[[ -n "${GUEST_ARCH}" ]] || {
|
||||||
|
echo "Failed to detect guest arch" >&2;
|
||||||
|
exit 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Setup locations
|
||||||
|
declare -r BASEDIR="${TOP_BASEDIR}/win11-builder-${GUEST_ARCH}"
|
||||||
|
declare -r CACHEDIR="${TOP_BASEDIR}/cache"
|
||||||
|
|
||||||
|
mkdir -p "${BASEDIR}" || exit $?
|
||||||
|
mkdir -p "${CACHEDIR}" || exit $?
|
||||||
|
|
||||||
|
declare -r DISK_IMG="${DISK_BASEDIR:-${BASEDIR}}/win11-builder-${GUEST_ARCH}.qcow2"
|
||||||
|
[[ -n "${DISK_IMG}" ]] || {
|
||||||
|
echo "Mandatory disk image configuration missing" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# Common options used by all configurations
|
||||||
|
#
|
||||||
|
QEMU_DYN_OPTIONS=(
|
||||||
|
# Use host's /dev/urandom
|
||||||
|
"-object rng-random,filename=/dev/urandom,id=rng0"
|
||||||
|
"-device virtio-rng-pci,rng=rng0"
|
||||||
|
|
||||||
|
# virtio-fs
|
||||||
|
"-chardev socket,id=char0,path=${BASEDIR}/virtiofsd.sock"
|
||||||
|
"-device vhost-user-fs-pci,queue-size=1024,chardev=char0,tag=sharedfs"
|
||||||
|
"-object memory-backend-memfd,id=mem,size=${MEM_SIZE},share=on"
|
||||||
|
"-numa node,memdev=mem"
|
||||||
|
)
|
||||||
|
|
||||||
|
#
|
||||||
|
# virtiofs
|
||||||
|
#
|
||||||
|
mkdir -p "${BASEDIR}/shared" || exit $?
|
||||||
|
echo "This directory is shared between host and guest" \
|
||||||
|
> "${BASEDIR}/shared/README.md" || exit $?
|
||||||
|
|
||||||
|
#
|
||||||
|
# Randomized user credentials?
|
||||||
|
#
|
||||||
|
#declare -r USER_NAME="User" || exit $?
|
||||||
|
#declare -r USER_PASSWORD="$(pwgen -cns 30 1)" || exit $?
|
||||||
|
#QEMU_DYN_OPTIONS+=(
|
||||||
|
# "-smbios type=11,value=AutoLogon\\${USER_NAME}\\${USER_PASSWORD}"
|
||||||
|
#)
|
||||||
|
|
||||||
|
function start_viofsd {
|
||||||
|
echo "Starting virtiofsd..." >&2
|
||||||
|
/usr/libexec/virtiofsd --sandbox=none --tag sharedfs \
|
||||||
|
--shared-dir "${CACHEDIR}" \
|
||||||
|
--socket-path "${BASEDIR}/virtiofsd.sock" \
|
||||||
|
--socket-group `id -gn` \
|
||||||
|
--translate-uid squash-guest:0:`id -u`:1000000 \
|
||||||
|
--translate-gid squash-guest:0:`id -g`:1000000 &
|
||||||
|
echo $! > "${BASEDIR}/virtiofsd.pid" || exit $?
|
||||||
|
}
|
||||||
|
|
||||||
|
function stop_viofsd {
|
||||||
|
local VIOFSD_PID="$(cat "${BASEDIR}/virtiofsd.pid")" || return $?
|
||||||
|
[[ -n "${VIOFSD_PID}" ]] || return 1
|
||||||
|
pgrep -p "${VIOFSD_PID}" || return 1
|
||||||
|
echo "Stopping virtiofsd..." >&2
|
||||||
|
kill -TERM "${VIOFSD_PID}"
|
||||||
|
}
|
||||||
|
|
||||||
|
trap "stop_viofsd" EXIT
|
||||||
|
start_viofsd
|
||||||
|
|
||||||
|
#
|
||||||
|
# Qemu
|
||||||
|
#
|
||||||
|
|
||||||
|
function qemu_image_type_from_fileext {
|
||||||
|
local img_file="${1@L}"
|
||||||
|
case "${img_file##*.}" in
|
||||||
|
"qcow2") echo "qcow2" ;;
|
||||||
|
*) echo "raw" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
function qemu_image_size {
|
||||||
|
local img_file="${1}"
|
||||||
|
local img_type="$(qemu_image_type_from_fileext "${1}")"
|
||||||
|
case "${img_type}" in
|
||||||
|
"qcow2")
|
||||||
|
echo "$(qemu-img info --output json "${img_file}" | jq '.["virtual-size"]')" || return $? ;;
|
||||||
|
"raw")
|
||||||
|
echo "$(stat -c '%s' "${img_file}")" || return $? ;;
|
||||||
|
*)
|
||||||
|
echo "Unknown image type '${img_type}', cannot detect size" >&2
|
||||||
|
return 1 ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
function fileext_from_qemu_image_type {
|
||||||
|
local img_type="${1@L}"
|
||||||
|
case "${img_type}" in
|
||||||
|
*) echo "${img_type}" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
function qemu_prepare_disk {
|
||||||
|
local QEMU_DISK_FILE="${1}"
|
||||||
|
local QEMU_DISK_SIZE="${2:-32G}"
|
||||||
|
|
||||||
|
# Remove leftover temp files
|
||||||
|
rm -f "${QEMU_DISK_FILE}.tmp" 2>/dev/null
|
||||||
|
|
||||||
|
if [[ ! -f "${QEMU_DISK_FILE}" || "${FORCE_RESET}" -eq 1 ]]; then
|
||||||
|
echo "Creating QEMU disk image..." >&2
|
||||||
|
qemu-img create -f qcow2 "${QEMU_DISK_FILE}" "${QEMU_DISK_SIZE}" >/dev/null || return $?
|
||||||
|
elif [[ -f "${QEMU_DISK_FILE}" ]]; then
|
||||||
|
local QEMU_COW_FILE="${QEMU_DISK_FILE}.cow"
|
||||||
|
local QEMU_COW_SIZE="$(qemu_image_size "${QEMU_DISK_FILE}")" || return $?
|
||||||
|
|
||||||
|
echo "Creating QEMU CoW disk image for '$(basename "${QEMU_DISK_FILE}")' ..." >&2
|
||||||
|
qemu-img create -f qcow2 -B qcow2 -b "${QEMU_DISK_FILE}" "${QEMU_COW_FILE}" "${QEMU_COW_SIZE}" >/dev/null || return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
#
|
||||||
|
QEMU_DYN_OPTIONS+=(
|
||||||
|
"-drive if=none,format=qcow2,file=${QEMU_COW_FILE:-${QEMU_DISK_FILE}},discard=unmap,id=hd0"
|
||||||
|
"-device virtio-blk-pci,drive=hd0,bootindex=1"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function qemu_prepare_cdrom_amd64 {
|
||||||
|
local QEMU_BOOTINDEX=2 # First non-HD drive bootindex
|
||||||
|
|
||||||
|
# Only assign cdrom drives for build command (TODO: clean this up)
|
||||||
|
[[ "${COMMAND}" == "build" ]] || return 0;
|
||||||
|
|
||||||
|
[[ -n "${UNATTEND_ISO}" ]] && {
|
||||||
|
QEMU_DYN_OPTIONS+=(
|
||||||
|
"-drive if=none,file=${UNATTEND_ISO},media=cdrom,id=cd2"
|
||||||
|
"-device ide-cd,drive=cd2,bootindex=$(( QEMU_BOOTINDEX++ )),bus=ahci.2"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
[[ -n "${INSTALL_ISO}" ]] && {
|
||||||
|
QEMU_DYN_OPTIONS+=(
|
||||||
|
"-drive if=none,file=${INSTALL_ISO},media=cdrom,id=cd0"
|
||||||
|
"-device ide-cd,drive=cd0,bootindex=$(( QEMU_BOOTINDEX++ )),bus=ahci.0"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
[[ -n "${VIRTIO_ISO}" ]] && {
|
||||||
|
QEMU_DYN_OPTIONS+=(
|
||||||
|
"-drive if=none,file=${VIRTIO_ISO},media=cdrom,id=cd1"
|
||||||
|
"-device ide-cd,drive=cd1,bootindex=$(( QEMU_BOOTINDEX++ )),bus=ahci.1"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function qemu_prepare_cdrom_arm64 {
|
||||||
|
local QEMU_BOOTINDEX=2 # First non-HD drive bootindex
|
||||||
|
|
||||||
|
# Only assign cdrom drives for build command (TODO: clean this up)
|
||||||
|
[[ "${COMMAND}" == "build" ]] || return 0;
|
||||||
|
|
||||||
|
[[ -n "${UNATTEND_ISO}" ]] && {
|
||||||
|
QEMU_DYN_OPTIONS+=(
|
||||||
|
"-drive if=none,file=${UNATTEND_ISO},media=cdrom,id=cd2"
|
||||||
|
"-device usb-storage,drive=cd2,bootindex=$(( QEMU_BOOTINDEX++ ))"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
[[ -n "${INSTALL_ISO}" ]] && {
|
||||||
|
QEMU_DYN_OPTIONS+=(
|
||||||
|
"-drive if=none,file=${INSTALL_ISO},media=cdrom,id=cd0"
|
||||||
|
"-device usb-storage,drive=cd0,bootindex=$(( QEMU_BOOTINDEX++ ))"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
[[ -n "${VIRTIO_ISO}" ]] && {
|
||||||
|
QEMU_DYN_OPTIONS+=(
|
||||||
|
"-drive if=none,file=${VIRTIO_ISO},media=cdrom,id=cd1"
|
||||||
|
"-device usb-storage,drive=cd1,bootindex=$(( QEMU_BOOTINDEX++ ))"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function expand_size {
|
||||||
|
local value="${1}"
|
||||||
|
[[ "${value}" =~ ^[0-9]+$ ]] && {
|
||||||
|
echo "${value}"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
local unit="${value:$(( ${#value} - 1 ))}"
|
||||||
|
case "${unit@U}" in
|
||||||
|
"B") echo "$(( ${value%${unit}} * 1 ))" ;;
|
||||||
|
"K") echo "$(( ${value%${unit}} * 2**10 ))" ;;
|
||||||
|
"M") echo "$(( ${value%${unit}} * 2**20 ))" ;;
|
||||||
|
"G") echo "$(( ${value%${unit}} * 2**30 ))" ;;
|
||||||
|
"T") echo "$(( ${value%${unit}} * 2**40 ))" ;;
|
||||||
|
*)
|
||||||
|
echo "Invalid unit suffix in value: '${value}'" >&2
|
||||||
|
return 1 ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
function resize_image {
|
||||||
|
local img_file="${1}"
|
||||||
|
|
||||||
|
local img_cur_size="$(qemu_image_size "${1}")"
|
||||||
|
local img_req_size="$(expand_size "${2:-${img_cur_size}}")"
|
||||||
|
[[ "${img_cur_size}" -eq "${img_req_size}" ]] && return 0;
|
||||||
|
[[ "${img_cur_size}" -lt "${img_req_size}" ]] || return 1;
|
||||||
|
|
||||||
|
local img_type="$(qemu_image_type_from_fileext "${img_file}")"
|
||||||
|
case "${img_type}" in
|
||||||
|
raw)
|
||||||
|
echo "Resizing raw image '$(basename "${img_file}")'" >&2
|
||||||
|
truncate --size="${img_req_size}" "${img_file}" || return $? ;;
|
||||||
|
*)
|
||||||
|
echo "Qcow2 image resizing is currently not implemented" >&2
|
||||||
|
return 1 ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
function qemu_prepare_efi_code_and_vars {
|
||||||
|
local SRC_EFI_CODE_IMG="${1}" # Required: EFI code image path
|
||||||
|
local SRC_EFI_VARS_IMG="${2}" # Optional: EFI vars image path
|
||||||
|
local SRC_EFI_CODE_SIZE="${3}" # Optional: EFI code image size
|
||||||
|
|
||||||
|
# Code image must exist
|
||||||
|
[[ -f "${SRC_EFI_CODE_IMG}" ]] || {
|
||||||
|
echo "EFI code ROM image '${SRC_EFI_CODE_IMG}' does not exist" >&2
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
local QEMU_EFI_CODE_SIZE="$(qemu_image_size "${SRC_EFI_CODE_IMG}")"
|
||||||
|
local QEMU_EFI_CODE_TYPE="$(qemu_image_type_from_fileext "${SRC_EFI_CODE_IMG}")"
|
||||||
|
|
||||||
|
local QEMU_EFI_CODE="${BASEDIR}/QEMU_EFI_CODE.$(fileext_from_qemu_image_type "${QEMU_EFI_CODE_TYPE}")"
|
||||||
|
if [[ ! -f "${QEMU_EFI_CODE}" || "${FORCE_RESET}" -eq 1 ]]; then
|
||||||
|
echo "Copying EFI code ROM image '$(basename "${SRC_EFI_CODE_IMG}")'..." >&2
|
||||||
|
cp -f "${SRC_EFI_CODE_IMG}" "${QEMU_EFI_CODE}" || return $?
|
||||||
|
|
||||||
|
resize_image "${QEMU_EFI_CODE}" "${SRC_EFI_CODE_SIZE:-}" || return $?
|
||||||
|
else
|
||||||
|
local src_checksum="$(sha256sum -b "${SRC_EFI_CODE_IMG}" | cut -d ' ' -f1)" || return $?
|
||||||
|
local dst_checksum="$(sha256sum -b "${QEMU_EFI_CODE}" | cut -d ' ' -f1)" || return $?
|
||||||
|
if [[ "${src_checksum}" != "${dst_checksum}" ]]; then
|
||||||
|
echo "Copying EFI code ROM image '$(basename "${SRC_EFI_CODE_IMG}")' (changed)..." >&2
|
||||||
|
cp -f "${SRC_EFI_CODE_IMG}" "${QEMU_EFI_CODE}" || return $?
|
||||||
|
|
||||||
|
resize_image "${QEMU_EFI_CODE}" "${SRC_EFI_CODE_SIZE:-}" || return $?
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
#
|
||||||
|
# UEFI variables flash rom
|
||||||
|
# Intialize a fresh new raw image
|
||||||
|
#
|
||||||
|
if [[ -n "${SRC_EFI_VARS_IMG}" ]]; then
|
||||||
|
# Vars image must exist
|
||||||
|
[[ -f "${SRC_EFI_VARS_IMG}" ]] || {
|
||||||
|
echo "EFI variables ROM image '${SRC_EFI_VARS_IMG}' does not exist" >&2
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
local QEMU_EFI_VARS_TYPE="$(qemu_image_type_from_fileext "${SRC_EFI_VARS_IMG}")" || return $?
|
||||||
|
local QEMU_EFI_VARS="${BASEDIR}/QEMU_EFI_VARS.$(fileext_from_qemu_image_type "${QEMU_EFI_VARS_TYPE}")"
|
||||||
|
local QEMU_EFI_VARS_SIZE="$(qemu_image_size "${QEMU_EFI_VARS}")"
|
||||||
|
|
||||||
|
if [[ ! -f "${QEMU_EFI_VARS}" || "${FORCE_RESET}" -eq 1 ]]; then
|
||||||
|
echo "Copying EFI variables ROM image '$(basename "${SRC_EFI_VARS_IMG}")'..." >&2
|
||||||
|
cp -f "${SRC_EFI_VARS_IMG}" "${QEMU_EFI_VARS}" || return $?
|
||||||
|
else
|
||||||
|
local src_checksum="$(sha256sum -b "${SRC_EFI_CODE_IMG}" | cut -d ' ' -f1)" || return $?
|
||||||
|
local dst_checksum="$(sha256sum -b "${QEMU_EFI_VARS}" | cut -d ' ' -f1)" || return $?
|
||||||
|
if [[ "${src_checksum}" != "${dst_checksum}" ]]; then
|
||||||
|
echo "Copying EFI variables ROM image '$(basename "${SRC_EFI_VARS_IMG}")' (changed)..." >&2
|
||||||
|
cp -f "${SRC_EFI_VARS_IMG}" "${QEMU_EFI_VARS}" || return $?
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
local QEMU_EFI_VARS="${BASEDIR}/QEMU_EFI_VARS.fd"
|
||||||
|
local QEMU_EFI_VARS_TYPE="$(qemu_image_type_from_fileext "${QEMU_EFI_VARS}")" || return $?
|
||||||
|
local QEMU_EFI_VARS_SIZE="$(qemu_image_size "${QEMU_EFI_CODE}")" # Match code image size
|
||||||
|
|
||||||
|
echo "Initializing UEFI variables flash ROM..." >&2
|
||||||
|
case "${QEMU_EFI_VARS_TYPE}" in
|
||||||
|
"qcow2") qemu-img create -t qcow2 "${QEMU_EFI_VARS}" "${QEMU_EFI_VARS_SIZE}" >/dev/null || return $? ;;
|
||||||
|
"raw") truncate --size=${QEMU_EFI_VARS_SIZE} "${QEMU_EFI_VARS}" || return $? ;;
|
||||||
|
*) echo "Unknown image type '${QEMU_EFI_VARS_TYPE}'" >&2; return 1 ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Append architecture specific configuration
|
||||||
|
QEMU_DYN_OPTIONS+=(
|
||||||
|
"-drive if=pflash,format=${QEMU_EFI_CODE_TYPE},unit=0,file=${QEMU_EFI_CODE},readonly=on"
|
||||||
|
"-drive if=pflash,format=${QEMU_EFI_VARS_TYPE},unit=1,file=${QEMU_EFI_VARS}"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function qemu_prepare_amd64 {
|
||||||
|
# Source images for EFI code and variables
|
||||||
|
local AMD64_EFI_CODE_IMG="/usr/share/edk2/OvmfX64/OVMF_CODE_4M.qcow2"
|
||||||
|
local AMD64_EFI_VARS_IMG=""
|
||||||
|
qemu_prepare_efi_code_and_vars "${AMD64_EFI_CODE_IMG}" "${AMD64_EFI_VARS_IMG}" || return $?
|
||||||
|
qemu_prepare_cdrom_amd64 "${INSTALL_ISO}" "${VIRTIO_ISO}" "${UNATTEND_ISO}" || return $?
|
||||||
|
}
|
||||||
|
|
||||||
|
function qemu_prepare_arm64 {
|
||||||
|
# Source images for EFI code and variables
|
||||||
|
qemu_prepare_efi_code_and_vars "${ARM64_EFI_CODE_IMG}" "${ARM64_EFI_VARS_IMG}" "${ARM64_EFI_CODE_SIZE}" || return $?
|
||||||
|
qemu_prepare_cdrom_arm64 "${INSTALL_ISO}" "${VIRTIO_ISO}" "${UNATTEND_ISO}" || return $?
|
||||||
|
}
|
||||||
|
|
||||||
|
function qemu_run_amd64_native {
|
||||||
|
echo "Starting X64 QEMU KVM..." >&2
|
||||||
|
qemu-system-x86_64 \
|
||||||
|
-machine type=q35,usb=on,acpi=on,hpet=off -accel kvm -boot menu=off \
|
||||||
|
${CPU_OPTIONS_AMD64} ${MEM_OPTIONS} \
|
||||||
|
-device virtio-gpu-pci,edid=on,xres=1280,yres=800 -vga virtio \
|
||||||
|
-device qemu-xhci -device usb-kbd -device usb-tablet \
|
||||||
|
-device ich9-ahci,id=ahci \
|
||||||
|
${QEMU_DYN_OPTIONS[@]} \
|
||||||
|
-netdev user,id=net0,hostfwd=tcp::2222-:22 \
|
||||||
|
-device virtio-net-pci,netdev=net0,mac=2A:50:A7:4E:D9:C5 \
|
||||||
|
-display gtk,show-tabs=on,show-menubar=on,zoom-to-fit=off \
|
||||||
|
-monitor unix:${BASEDIR}/monitor.sock,server,nowait \
|
||||||
|
-vnc unix:${BASEDIR}/vnc.sock,password=on \
|
||||||
|
-nodefaults
|
||||||
|
}
|
||||||
|
|
||||||
|
function qemu_run_arm64_native {
|
||||||
|
# Return list of the big CPU cores on the system (TODO: Check for Cortex-A72+ before attempting to start)
|
||||||
|
local CPU_CONFIG="$(lscpu -J -b -e=CPU,MODELNAME)"
|
||||||
|
[[ -n "${CPU_CONFIG}" ]] || {
|
||||||
|
echo "Failed to retrieve CPU configuration" >&2
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
local CPU_MODELS="$(jq -r '.cpus | map(.modelname) | unique | map(select(. | test("A72|A76"))) | join("\n")' <<< "${CPU_CONFIG}")"
|
||||||
|
[[ -n "${CPU_MODELS}" ]] || {
|
||||||
|
echo "No supported CPU models found" >&2
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
local SELECTED_CPU_CORES="$(jq -r '.cpus | group_by(.modelname) | sort_by(.[].modelname) | last | map(.cpu) | join(",")' <<< "${CPU_CONFIG}")"
|
||||||
|
[[ -n "${SELECTED_CPU_CORES}" ]] || {
|
||||||
|
echo "No supported CPU cores found" >&2
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Starting ARM64 QEMU KVM (on cores ${SELECTED_CPU_CORES})..." >&2
|
||||||
|
taskset -c "${SELECTED_CPU_CORES}" \
|
||||||
|
qemu-system-aarch64 \
|
||||||
|
-machine type=virt,virtualization=off,acpi=on -accel kvm -boot menu=off \
|
||||||
|
${CPU_OPTIONS_ARM64} ${MEM_OPTIONS} \
|
||||||
|
-device virtio-gpu-pci,edid=on,xres=1280,yres=800 -device ramfb \
|
||||||
|
-device qemu-xhci -device usb-kbd -device usb-tablet \
|
||||||
|
${QEMU_DYN_OPTIONS[@]} \
|
||||||
|
-netdev user,id=net0,hostfwd=tcp::2222-:22 \
|
||||||
|
-device virtio-net-pci,netdev=net0,mac=2A:50:A7:4E:D9:C5 \
|
||||||
|
-display gtk,show-tabs=on,show-menubar=on,zoom-to-fit=off \
|
||||||
|
-monitor unix:${BASEDIR}/monitor.sock,server,nowait \
|
||||||
|
-vnc unix:${BASEDIR}/vnc.sock,password=on \
|
||||||
|
-nodefaults
|
||||||
|
}
|
||||||
|
|
||||||
|
function qemu_run_arm64_emulated {
|
||||||
|
echo "Starting ARM64 QEMU TCG..." >&2
|
||||||
|
qemu-system-aarch64 \
|
||||||
|
-machine type=virt,virtualization=off,acpi=on -accel tcg,thread=multi -boot menu=off \
|
||||||
|
${CPU_OPTIONS_ARM64} ${MEM_OPTIONS} \
|
||||||
|
-device virtio-gpu-pci,edid=on,xres=1280,yres=800 -device ramfb \
|
||||||
|
-device qemu-xhci -device usb-kbd -device usb-tablet \
|
||||||
|
${QEMU_DYN_OPTIONS[@]} \
|
||||||
|
-netdev user,id=net0,hostfwd=tcp::2222-:22 \
|
||||||
|
-device virtio-net-pci,netdev=net0,mac=2A:50:A7:4E:D9:C5 \
|
||||||
|
-display gtk,show-tabs=on,show-menubar=on,zoom-to-fit=off \
|
||||||
|
-monitor unix:${BASEDIR}/monitor.sock,server,nowait \
|
||||||
|
-vnc unix:${BASEDIR}/vnc.sock,password=on \
|
||||||
|
-nodefaults
|
||||||
|
}
|
||||||
|
|
||||||
|
function qemu_prepare_and_run {
|
||||||
|
#
|
||||||
|
# Prepare QEMU VM resources
|
||||||
|
#
|
||||||
|
case "${GUEST_ARCH}" in
|
||||||
|
"amd64")
|
||||||
|
qemu_prepare_amd64 || return $? ;;
|
||||||
|
"arm64")
|
||||||
|
qemu_prepare_arm64 || return $? ;;
|
||||||
|
*)
|
||||||
|
echo "Unsupported guest architecture '${GUEST_ARCH}'" >&2
|
||||||
|
return 1 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
qemu_prepare_disk "${DISK_IMG}" "${DISK_SIZE}" || return $?
|
||||||
|
|
||||||
|
#
|
||||||
|
# Run QEMU VM
|
||||||
|
#
|
||||||
|
case "${HOST_ARCH}:${GUEST_ARCH}" in
|
||||||
|
"amd64:amd64") # Native: AMD64 on AMD64
|
||||||
|
qemu_run_amd64_native ;;
|
||||||
|
"amd64:arm64") # Emulated: ARM64 on AMD64
|
||||||
|
echo 'NOTICE: Running ARM64 emulated on AMD64, this will be slow' >&2
|
||||||
|
qemu_run_arm64_emulated ;;
|
||||||
|
"arm64:arm64") # Native: ARM64 on ARM64
|
||||||
|
qemu_run_arm64_native ;;
|
||||||
|
*)
|
||||||
|
echo "Unsupported host + guest architecture combination: '${GUEST_ARCH}' on '${HOST_ARCH}'" >&2
|
||||||
|
return 1 ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
function recompress_disk_image {
|
||||||
|
local IMG_FILE="${1}"
|
||||||
|
local IMG_SIZE="$(stat -c '%s' "${IMG_FILE}")" || return $?
|
||||||
|
|
||||||
|
[[ ${IMG_SIZE} -lt $(( 2 ** 30 )) ]] && {
|
||||||
|
echo 'Disk is smaller than 1 GiB, skipping recompression' >&2
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Optimizing QEMU disk image..." >&2
|
||||||
|
qemu-img convert -p -c -W -f qcow2 -O qcow2 "${IMG_FILE}" "${IMG_FILE}.tmp" || {
|
||||||
|
rm -f "${IMG_FILE}.tmp" 2>/dev/null
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
local IMG_SIZE_ORG="$(stat -c 'scale=2; %s / 2^30' "${IMG_FILE}" | bc)"
|
||||||
|
local IMG_SIZE_OPT="$(stat -c 'scale=2; %s / 2^30' "${IMG_FILE}.tmp" | bc)"
|
||||||
|
local PERCENT="$(bc <<< "scale=2; (${IMG_SIZE_OPT} * 100) / ${IMG_SIZE_ORG}")"
|
||||||
|
echo "'$(basename "${IMG_FILE}")': ${IMG_SIZE_ORG} GiB -> ${IMG_SIZE_OPT} GiB (${PERCENT} %)" >&2
|
||||||
|
mv -f "${IMG_FILE}.tmp" "${IMG_FILE}" || return $?
|
||||||
|
}
|
||||||
|
|
||||||
|
function handle_build_command {
|
||||||
|
qemu_prepare_and_run || return $?
|
||||||
|
recompress_disk_image "${DISK_IMG}" || return $?
|
||||||
|
}
|
||||||
|
|
||||||
|
function handle_run_command {
|
||||||
|
qemu_prepare_and_run || return $?
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# Handle user-supplied command
|
||||||
|
#
|
||||||
|
case "${COMMAND@L}" in
|
||||||
|
"build") handle_build_command || exit $? ;;
|
||||||
|
"run") handle_run_command || exit $? ;;
|
||||||
|
esac
|
||||||
Reference in New Issue
Block a user