From 4715a28fdcd87440400d17154bfa361d99db29cc Mon Sep 17 00:00:00 2001 From: Jan Tuomi Date: Tue, 12 May 2026 15:06:28 +0300 Subject: Add local vm for testing, adjust configs --- vm.sh | 246 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 246 insertions(+) create mode 100755 vm.sh (limited to 'vm.sh') diff --git a/vm.sh b/vm.sh new file mode 100755 index 0000000..337a16d --- /dev/null +++ b/vm.sh @@ -0,0 +1,246 @@ +#!/usr/bin/env bash +set -euo pipefail + +# vm.sh — Manage a QEMU FreeBSD aarch64 VM for testing Ansible playbooks. +# +# Usage: +# ./vm.sh dl Download the base image (one-time) +# ./vm.sh init Extract disk, create seed ISO and SSH key +# ./vm.sh up Start the VM (requires sudo for vmnet) +# ./vm.sh down Shut down the VM gracefully +# ./vm.sh kill Force-kill the VM +# ./vm.sh reset Delete disk & state, keeping the downloaded base image +# ./vm.sh ssh SSH into the VM +# ./vm.sh status Check if the VM is running +# ./vm.sh console Attach to serial console (ctrl-a x to exit) +# +# Prerequisites: brew install qemu socat cdrtools +# +# Networking (vmnet-shared, subnet 10.0.20.0/24): +# .1 Mac (vmnet gateway, provides NAT + DNS) +# .2 VM host LAN (lan0) +# .3 Ingress jail WAN interface +# .101-XXX Jails LAN (jail number + 100) +# +# All IPs are directly reachable from the Mac without extra routes. +# Run playbook: ansible-playbook -i inventory playbook.yml --limit test_vm + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +VM_DIR="${SCRIPT_DIR}/vm" +DISK="${VM_DIR}/disk.qcow2" +PFLASH_VARS="${VM_DIR}/pflash_vars.fd" +PID_FILE="${VM_DIR}/qemu.pid" +MONITOR_SOCK="${VM_DIR}/qemu-monitor.sock" +SERIAL_SOCK="${VM_DIR}/qemu-serial.sock" + +FREEBSD_VERSION="${FREEBSD_VERSION:-15.0}" +IMAGE_NAME="FreeBSD-${FREEBSD_VERSION}-RELEASE-arm64-aarch64-BASIC-CLOUDINIT-zfs.qcow2" +IMAGE_XZ="${VM_DIR}/${IMAGE_NAME}.xz" +IMAGE_URL="https://download.freebsd.org/releases/VM-IMAGES/${FREEBSD_VERSION}-RELEASE/aarch64/Latest/${IMAGE_NAME}.xz" + +PFLASH_CODE="$(brew --prefix)/share/qemu/edk2-aarch64-code.fd" +SEED_ISO="${VM_DIR}/seed.iso" +SSH_KEY="${VM_DIR}/id_ed25519" + +SSH_PORT_LAN=22 +VM_RAM=2048 +VM_CPUS=2 +DISK_SIZE=20G + +cmd_dl() { + mkdir -p "$VM_DIR" + if [[ -f "$IMAGE_XZ" ]]; then + echo "Base image already downloaded: $IMAGE_XZ" + return + fi + echo "Downloading FreeBSD ${FREEBSD_VERSION} aarch64 VM image..." + curl --fail -C - -L -o "$IMAGE_XZ" "$IMAGE_URL" || { rm -f "$IMAGE_XZ"; echo "Download failed: $IMAGE_URL"; exit 1; } + echo "Done." +} + +prepare_disk() { + if [[ ! -f "$IMAGE_XZ" ]]; then + echo "Base image not found. Run: ./vm.sh dl" + exit 1 + fi + echo "Extracting disk from base image..." + xz -dk "$IMAGE_XZ" + mv "${VM_DIR}/${IMAGE_NAME}" "$DISK" + qemu-img resize "$DISK" "$DISK_SIZE" +} + +prepare_seed() { + [[ -f "$SEED_ISO" ]] && return + if [[ ! -f "$SSH_KEY" ]]; then + ssh-keygen -t ed25519 -f "$SSH_KEY" -N "" -q + fi + local pubkey + pubkey=$(cat "${SSH_KEY}.pub") + local seed_dir="${VM_DIR}/seed" + mkdir -p "$seed_dir" + cat > "$seed_dir/meta-data" < "$seed_dir/user-data" </dev/null + fi + echo "Initialized. Run './vm.sh up' to start." +} + +cmd_up() { + if [[ -f "$PID_FILE" ]] && kill -0 "$(cat "$PID_FILE")" 2>/dev/null; then + echo "VM already running (PID $(cat "$PID_FILE"))" + exit 0 + fi + + if [[ ! -f "$DISK" ]]; then + echo "Not initialized. Run: ./vm.sh init" + exit 1 + fi + + echo "Starting VM (requires sudo for vmnet)..." + sudo -v || { echo "sudo required for vmnet networking"; exit 1; } + sudo qemu-system-aarch64 \ + -M virt,highmem=on \ + -accel hvf \ + -cpu host \ + -m "$VM_RAM" \ + -smp "$VM_CPUS" \ + -drive if=pflash,format=raw,file="$PFLASH_CODE",readonly=on \ + -drive if=pflash,format=raw,file="$PFLASH_VARS" \ + -drive file="$DISK",format=qcow2,if=virtio \ + -cdrom "$SEED_ISO" \ + -netdev vmnet-shared,id=lan,start-address=10.0.20.1,end-address=10.0.20.254,subnet-mask=255.255.255.0 \ + -device virtio-net-pci,netdev=lan \ + -netdev vmnet-shared,id=wan,start-address=10.0.20.1,end-address=10.0.20.254,subnet-mask=255.255.255.0 \ + -device virtio-net-pci,netdev=wan \ + -serial unix:"$SERIAL_SOCK",server,nowait \ + -monitor unix:"$MONITOR_SOCK",server,nowait \ + -pidfile "$PID_FILE" \ + -daemonize \ + -display none \ + && sudo chmod 644 "$PID_FILE" \ + && sudo chmod 777 "$SERIAL_SOCK" "$MONITOR_SOCK" \ + && echo "VM started (PID $(cat "$PID_FILE"))" \ + && echo "SSH: ./vm.sh ssh" \ + && echo "Console: ./vm.sh console" +} + +cmd_down() { + if [[ ! -S "$MONITOR_SOCK" ]]; then + echo "VM not running (no monitor socket)" + exit 1 + fi + echo "system_powerdown" | sudo socat - UNIX-CONNECT:"$MONITOR_SOCK" + echo "Sent powerdown signal." + local pid + pid=$(cat "$PID_FILE" 2>/dev/null || true) + if [[ -n "$pid" ]]; then + for _ in $(seq 1 30); do + sudo kill -0 "$pid" 2>/dev/null || break + sleep 1 + done + if sudo kill -0 "$pid" 2>/dev/null; then + echo "VM still running after 30s. Use './vm.sh kill' to force." + else + rm -f "$PID_FILE" + echo "VM stopped." + fi + fi +} + +cmd_kill() { + if [[ -f "$PID_FILE" ]]; then + local pid + pid=$(cat "$PID_FILE") + sudo kill -9 "$pid" 2>/dev/null && echo "Killed VM (PID $pid)" || echo "Process not found" + rm -f "$PID_FILE" + else + echo "No PID file found" + fi +} + +cmd_reset() { + cmd_kill 2>/dev/null || true + rm -f "$DISK" "$PFLASH_VARS" "$SEED_ISO" "$SSH_KEY" "${SSH_KEY}.pub" + rm -f "$MONITOR_SOCK" "$SERIAL_SOCK" "$PID_FILE" + echo "VM state reset. Base image kept at $IMAGE_XZ" +} + +cmd_ssh() { + exec ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \ + -i "$SSH_KEY" root@10.0.20.2 "$@" +} + +cmd_console() { + if [[ ! -S "$SERIAL_SOCK" ]]; then + echo "VM not running (no serial socket)" + exit 1 + fi + echo "Connecting to serial console (ctrl-a x to exit)..." + exec socat -,rawer,escape=0x01 UNIX-CONNECT:"$SERIAL_SOCK" +} + +cmd_status() { + if [[ -f "$PID_FILE" ]] && sudo kill -0 "$(cat "$PID_FILE")" 2>/dev/null; then + echo "VM running (PID $(cat "$PID_FILE"))" + else + echo "VM not running" + rm -f "$PID_FILE" 2>/dev/null + fi +} + +case "${1:-}" in + dl) cmd_dl ;; + init) cmd_init ;; + up) cmd_up ;; + down) cmd_down ;; + kill) cmd_kill ;; + reset) cmd_reset ;; + ssh) shift; cmd_ssh "$@" ;; + console) cmd_console ;; + status) cmd_status ;; + *) + echo "Usage: $0 {dl|init|up|down|kill|reset|ssh|console|status}" + exit 1 + ;; +esac -- cgit v1.3