#!/usr/bin/env bash set -euo pipefail script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd -P) repo_dir=$(CDPATH= cd -- "${script_dir}/../.." && pwd -P) firecracker_bin='firecracker' config_path="${repo_dir}/firecracker-config.json" console_log_path="${repo_dir}/firecracker-console.log" api_socket='/tmp/firecracker.socket' vcpu_count='1' mem_size_mib='512' rootfs_read_only='false' default_net_dev_name='tap0' net_dev_name='' net_iface_id='eth0' net_guest_mac='06:00:AC:10:14:02' info() { printf '[INFO] %s\n' "$1" } warn() { printf '[WARN] %s\n' "$1" >&2 } fail() { printf '[FAIL] %s\n' "$1" >&2 if [ -n "${console_log_path:-}" ]; then printf '[FAIL] %s\n' "$1" >>"$console_log_path" || true fi exit 1 } usage() { cat <<'EOF' Usage: ./scripts/firecracker-menual/boot-microvm.sh EOF } has_command() { command -v "$1" >/dev/null 2>&1 } require_command() { if ! has_command "$1"; then fail "Required command not found: $1" fi } is_positive_integer() { case "$1" in '' | *[!0-9]* | 0) return 1 ;; *) return 0 ;; esac } abs_existing_file() { path=$1 if [ ! -f "$path" ]; then fail "File does not exist: $path" fi dir=$(dirname "$path") file=$(basename "$path") printf '%s/%s\n' "$(cd "$dir" && pwd -P)" "$file" } prepare_output_path() { path=$1 dir=$(dirname "$path") file=$(basename "$path") mkdir -p "$dir" printf '%s/%s\n' "$(cd "$dir" && pwd -P)" "$file" } resolve_executable() { executable=$1 case "$executable" in */*) if [ ! -x "$executable" ]; then fail "Firecracker executable is not runnable: $executable" fi abs_existing_file "$executable" ;; *) command -v "$executable" || fail "Firecracker executable not found: $executable" ;; esac } json_escape() { value=$1 value=${value//\\/\\\\} value=${value//\"/\\\"} value=${value//$'\n'/\\n} value=${value//$'\r'/\\r} value=${value//$'\t'/\\t} printf '%s' "$value" } default_boot_args() { args='console=ttyS0 reboot=k panic=1 pci=off root=/dev/vda rw' case "$(uname -m 2>/dev/null || printf 'unknown')" in aarch64 | arm64) args="keep_bootcon ${args}" ;; esac printf '%s\n' "$args" } check_kvm_access() { # Confirm that the host can expose KVM to Firecracker before booting. if [ ! -e /dev/kvm ]; then fail '/dev/kvm does not exist. Run the KVM readiness check before booting a microVM.' fi if [ ! -r /dev/kvm ] || [ ! -w /dev/kvm ]; then fail 'Current user cannot read and write /dev/kvm.' fi } prepare_api_socket() { # Remove only a stale Unix socket; do not delete arbitrary files at that path. if [ -S "$api_socket" ]; then rm -f "$api_socket" return fi if [ -e "$api_socket" ]; then fail "API socket path already exists and is not a socket: ${api_socket}" fi } check_network_inputs() { if [ ! -e "/sys/class/net/${default_net_dev_name}" ]; then warn "network: host ${default_net_dev_name} does not exist; booting without a network device" warn 'network: create it with: sudo ip tuntap add dev tap0 mode tap user "$(id -un)"' warn 'network: then run: sudo ip addr replace 172.16.20.1/24 dev tap0' warn 'network: then run: sudo ip link set tap0 up' return fi net_dev_name=$default_net_dev_name } write_config() { # Write the Firecracker configuration used by --config-file boot. escaped_kernel_path=$(json_escape "$kernel_path") escaped_rootfs_path=$(json_escape "$rootfs_path") escaped_boot_args=$(json_escape "$boot_args") escaped_net_iface_id=$(json_escape "$net_iface_id") escaped_net_guest_mac=$(json_escape "$net_guest_mac") escaped_net_dev_name=$(json_escape "$net_dev_name") network_config='' if [ -n "$net_dev_name" ]; then network_config=$(cat <"$config_path" <"$console_log_path" # Resolve all inputs to stable paths for the generated JSON config. firecracker_bin=$(resolve_executable "$firecracker_bin") kernel_path=$(abs_existing_file "$1") rootfs_path=$(abs_existing_file "$2") boot_args=$(default_boot_args) case "$rootfs_read_only" in true | false) ;; *) fail 'rootfs_read_only must be true or false.' ;; esac if ! is_positive_integer "$vcpu_count"; then fail 'vcpu_count must be a positive integer.' fi if ! is_positive_integer "$mem_size_mib"; then fail 'mem_size_mib must be a positive integer.' fi check_kvm_access check_network_inputs # Create the Firecracker config file. prepare_api_socket write_config info "config: ${config_path}" info "console log: ${console_log_path}" if [ -n "$net_dev_name" ]; then info "network: ${net_dev_name} -> ${net_iface_id} (${net_guest_mac})" fi info 'starting MicroVM; use the guest console to reboot or stop the VM when done' # Boot the MicroVM manually from the generated config and capture console output. set +e "$firecracker_bin" --api-sock "$api_socket" --config-file "$config_path" 2>&1 | tee "$console_log_path" firecracker_status=${PIPESTATUS[0]} set -e if [ "$firecracker_status" -ne 0 ]; then fail "Firecracker exited with status ${firecracker_status}." fi # Check the captured boot output after Firecracker exits. verify_boot_log info 'MicroVM boot completed successfully.' } main "$@"