168 lines
5.0 KiB
Bash
Executable File
168 lines
5.0 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
|
if [[ -n "${VM_IMAGE_HELPER:-}" ]]; then
|
|
image_downloader="$VM_IMAGE_HELPER"
|
|
elif [[ -x "${script_dir}/vm-images/download-images.sh" ]]; then
|
|
image_downloader="${script_dir}/vm-images/download-images.sh"
|
|
else
|
|
image_downloader="${script_dir}/../vm-images/download-images.sh"
|
|
fi
|
|
uses_shared_base_image=false
|
|
if [[ -n "${VM_BASE_IMAGE:-}" ]]; then
|
|
base_image="$VM_BASE_IMAGE"
|
|
else
|
|
[[ -x "$image_downloader" ]] || { printf 'Error: shared image helper is unavailable: %s\n' "$image_downloader" >&2; exit 1; }
|
|
base_image="$("$image_downloader" --print-path debian13)"
|
|
uses_shared_base_image=true
|
|
fi
|
|
state_dir="${VM_STATE_DIR:-${script_dir}/.vm/debian13}"
|
|
disk_image="${VM_DISK_IMAGE:-${state_dir}/disk.qcow2}"
|
|
seed_image="${VM_SEED_IMAGE:-$(dirname -- "$disk_image")/seed.iso}"
|
|
disk_size="${VM_DISK_SIZE:-20G}"
|
|
memory="${VM_MEMORY:-8G}"
|
|
cpus="${VM_CPUS:-8}"
|
|
ssh_port="${VM_SSH_PORT:-2222}"
|
|
uefi_code="${VM_UEFI_CODE:-/usr/share/OVMF/OVMF_CODE_4M.fd}"
|
|
uefi_vars_template="${VM_UEFI_VARS:-/usr/share/OVMF/OVMF_VARS_4M.fd}"
|
|
uefi_vars="${state_dir}/OVMF_VARS_4M.fd"
|
|
pid_file="${state_dir}/qemu.pid"
|
|
log_file="${state_dir}/console.log"
|
|
daemonize=false
|
|
extra_args=()
|
|
|
|
usage() {
|
|
cat <<EOF
|
|
Usage: $(basename "$0") [--daemon] [-- QEMU_ARGUMENT ...]
|
|
|
|
Start the Debian 13 virtual machine. A writable overlay is created under
|
|
.vm/debian13/, so the downloaded base image is never modified.
|
|
|
|
Options:
|
|
--daemon Run in the background while keeping the graphical window
|
|
-h, --help Show this help
|
|
|
|
Environment variables:
|
|
VM_MEMORY Guest memory (default: 8G)
|
|
VM_CPUS Virtual CPU count (default: 8)
|
|
VM_SSH_PORT Host port forwarded to guest SSH (default: 2222)
|
|
VM_BASE_IMAGE Base qcow2 image path (default: resolved by shared image helper)
|
|
VM_SEED_IMAGE cloud-init seed ISO path (default: alongside the VM disk)
|
|
VM_STATE_DIR Runtime state directory (default: .vm/debian13)
|
|
VM_DISK_IMAGE Writable overlay path
|
|
VM_DISK_SIZE New overlay virtual capacity (default: 20G)
|
|
VM_UEFI_CODE OVMF read-only firmware path
|
|
VM_UEFI_VARS OVMF variable-store template path
|
|
EOF
|
|
}
|
|
|
|
while (($#)); do
|
|
case "$1" in
|
|
--daemon)
|
|
daemonize=true
|
|
shift
|
|
;;
|
|
-h|--help)
|
|
usage
|
|
exit 0
|
|
;;
|
|
--)
|
|
shift
|
|
extra_args+=("$@")
|
|
break
|
|
;;
|
|
*)
|
|
printf 'Error: unknown option: %s\n' "$1" >&2
|
|
usage >&2
|
|
exit 2
|
|
;;
|
|
esac
|
|
done
|
|
|
|
for command_name in qemu-system-x86_64 qemu-img; do
|
|
if ! command -v "$command_name" >/dev/null 2>&1; then
|
|
printf 'Error: %s is not installed.\n' "$command_name" >&2
|
|
exit 1
|
|
fi
|
|
done
|
|
|
|
if [[ ! -f "$base_image" ]]; then
|
|
if [[ "$uses_shared_base_image" == true ]]; then
|
|
"$image_downloader" debian13
|
|
else
|
|
printf 'Error: Debian base image was not found: %s\n' "$base_image" >&2
|
|
exit 1
|
|
fi
|
|
fi
|
|
[[ -f "$base_image" ]] || { printf 'Error: downloader did not create: %s\n' "$base_image" >&2; exit 1; }
|
|
for firmware_file in "$uefi_code" "$uefi_vars_template"; do
|
|
[[ -f "$firmware_file" ]] || { printf 'Error: required UEFI firmware was not found: %s\n' "$firmware_file" >&2; exit 1; }
|
|
done
|
|
|
|
if [[ ! -f "$seed_image" ]]; then
|
|
seed_builder="${script_dir}/seediso/debian13/build-seed.sh"
|
|
if [[ ! -x "$seed_builder" ]]; then
|
|
printf 'Error: seed image is missing and builder is unavailable: %s\n' \
|
|
"$seed_builder" >&2
|
|
exit 1
|
|
fi
|
|
printf 'Seed image not found; building %s...\n' "$seed_image"
|
|
"$seed_builder" "$seed_image"
|
|
fi
|
|
|
|
mkdir -p "$state_dir"
|
|
[[ -f "$uefi_vars" ]] || cp -- "$uefi_vars_template" "$uefi_vars"
|
|
|
|
if [[ -f "$pid_file" ]]; then
|
|
old_pid="$(<"$pid_file")"
|
|
if [[ "$old_pid" =~ ^[0-9]+$ ]] && kill -0 "$old_pid" 2>/dev/null; then
|
|
printf 'Error: the virtual machine is already running (PID %s).\n' "$old_pid" >&2
|
|
exit 1
|
|
fi
|
|
rm -f "$pid_file"
|
|
fi
|
|
|
|
if [[ ! -f "$disk_image" ]]; then
|
|
qemu-img create -q -f qcow2 -F qcow2 -b "$base_image" "$disk_image" "$disk_size"
|
|
printf 'Created writable disk overlay: %s\n' "$disk_image"
|
|
fi
|
|
|
|
qemu_args=(
|
|
-name debian13
|
|
-machine q35
|
|
-display gtk
|
|
-m "$memory"
|
|
-smp "$cpus"
|
|
-drive "if=pflash,format=raw,readonly=on,file=${uefi_code}"
|
|
-drive "if=pflash,format=raw,file=${uefi_vars}"
|
|
-drive "file=${disk_image},if=virtio,format=qcow2"
|
|
-drive "file=${seed_image},media=cdrom,readonly=on"
|
|
-nic "user,model=virtio-net-pci,hostfwd=tcp::${ssh_port}-:22"
|
|
)
|
|
|
|
if [[ -r /dev/kvm && -w /dev/kvm ]]; then
|
|
qemu_args+=(-enable-kvm -cpu host)
|
|
accelerator=KVM
|
|
else
|
|
qemu_args+=(-accel tcg -cpu max)
|
|
accelerator=TCG
|
|
fi
|
|
|
|
printf 'Starting VM with %s acceleration; SSH is forwarded to localhost:%s.\n' \
|
|
"$accelerator" "$ssh_port"
|
|
|
|
if [[ "$daemonize" == true ]]; then
|
|
: >"$log_file"
|
|
qemu_args+=(
|
|
-serial "file:${log_file}"
|
|
-monitor none
|
|
-daemonize
|
|
-pidfile "$pid_file"
|
|
)
|
|
qemu-system-x86_64 "${qemu_args[@]}" "${extra_args[@]}"
|
|
printf 'VM started (PID %s); console log: %s\n' "$(<"$pid_file")" "$log_file"
|
|
else
|
|
exec qemu-system-x86_64 "${qemu_args[@]}" "${extra_args[@]}"
|
|
fi
|