diff --git a/.gitignore b/.gitignore index a404141..d8da10f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ # Build artifacts -build/ -grub2/ +/build/ +/grub2/ *.o *.obj *.mod diff --git a/host/initramfs/conf_replace.sh b/host/initramfs/conf_replace.sh new file mode 100644 index 0000000..0308459 --- /dev/null +++ b/host/initramfs/conf_replace.sh @@ -0,0 +1,63 @@ +#!/bin/sh +# conf_replace.sh -- Create modified ISO copy with replaced config files +# Parses HIPERISO_CONF_REPLACE="org=/path;new=/path" from environment +# If set, creates /tmp/modified.iso and prints its path on stdout. +# Called by init as a subprocess: _new=$(sh /conf_replace.sh) +# POSIX sh compatible + +. /hiperiso-lib.sh + +if [ -z "${HIPERISO_CONF_REPLACE:-}" ]; then + # No conf_replace configured -- output nothing, exit clean + return 0 2>/dev/null || exit 0 +fi + +_org="" +_new="" +OLDIFS="$IFS" +IFS=';' +for _pair in $HIPERISO_CONF_REPLACE; do + case "$_pair" in + org=*) _org="${_pair#org=}" ;; + new=*) _new="${_pair#new=}" ;; + esac +done +IFS="$OLDIFS" + +if [ -z "$_org" ] || [ -z "$_new" ]; then + hiperiso_log "conf_replace: malformed config, skipping" + return 0 2>/dev/null || exit 0 +fi + +_new_abs="${DATA_MOUNT}${_new}" +if [ ! -f "$_new_abs" ]; then + hiperiso_log "conf_replace: replacement file not found: $_new_abs" + return 0 2>/dev/null || exit 0 +fi + +if ! command -v xorriso >/dev/null 2>&1; then + hiperiso_log "conf_replace: xorriso not available, skipping ISO modification" + return 0 2>/dev/null || exit 0 +fi + +_modified_iso="/tmp/modified.iso" +hiperiso_log "conf_replace: creating modified ISO copy" + +if xorriso -indev "$ISO_PATH" \ + -outdev "$_modified_iso" \ + -boot_image any keep \ + -rm "$_org" \ + -add "$_new_abs"="$_org" \ + 2>/dev/null; then + + if [ -f "$_modified_iso" ]; then + hiperiso_log "conf_replace: modified ISO at $_modified_iso" + # Output the path for the calling process to capture + printf '%s' "$_modified_iso" + return 0 2>/dev/null || exit 0 + fi +fi + +hiperiso_log "conf_replace: xorriso failed, using original ISO" +rm -f "$_modified_iso" 2>/dev/null || true +return 0 2>/dev/null || exit 0 diff --git a/host/initramfs/fallback_boot.sh b/host/initramfs/fallback_boot.sh new file mode 100755 index 0000000..f1cdd4f --- /dev/null +++ b/host/initramfs/fallback_boot.sh @@ -0,0 +1,94 @@ +#!/bin/sh +# ============================================================ +# fallback_boot.sh -- Structured failure capture when KVM absent +# +# Called by init when hardware virtualization is unavailable +# and hiperiso_fallback is not "1". Writes structured session +# metadata (session.json, status.json, FAILURE.txt) so the boot +# attempt is captured even though no QEMU guest was launched, +# then warns the user and offers to reboot. +# ============================================================ + +. /hiperiso-lib.sh + +# ── Capture structured failure data ─────────────────────────── +if [ -n "${LOG_DIR:-}" ] && [ -d "$LOG_DIR" ]; then + hiperiso_log "FALLBACK: KVM unavailable -- capturing failure session" + + hiperiso_write_session_meta "failed" + hiperiso_write_status "failed" "fallback_no_kvm" + + { + printf '========================================\n' + printf ' hiperiso FAILURE: KVM Unavailable\n' + printf '========================================\n\n' + printf 'ISO: %s\n' "${HIPERISO_ISO_PATH:-unknown}" + printf 'Session: %s\n' "$(hiperiso_session_id)" + printf 'Timestamp: %s\n' "$(hiperiso_utc_iso)" + printf 'Reason: KVM not detected (VT-x/AMD-V unavailable)\n\n' + printf 'No QEMU guest was launched. No serial.log or\n' + printf 'trace.bin was produced for this session.\n' + printf '========================================\n' + } > "${LOG_DIR}/FAILURE.txt" + sync "${LOG_DIR}/FAILURE.txt" 2>/dev/null || sync + + hiperiso_run_report + # Finalize so the failed session gets a canonical manifest and an + # entry in the global JSONL index (failed sessions are indexed too). + hiperiso_finalize_session "failed" "" "fallback_no_kvm" +fi + +# Clear the terminal +printf '\033[2J\033[H' + +printf '\n' +printf '======================================================\n' +printf ' WARNING: Hardware Virtualization (KVM) Unavailable \n' +printf '======================================================\n' +printf '\n' +printf ' hiperiso requires KVM (Intel VT-x or AMD-V) to run\n' +printf ' an ISO as a guest virtual machine.\n' +printf '\n' +printf ' KVM was NOT detected on this system, so this ISO\n' +printf ' cannot be booted in hypervisor mode.\n' +printf '\n' +printf ' Possible reasons:\n' +printf ' 1. The CPU does not support VT-x / AMD-V\n' +printf ' 2. Virtualization is disabled in BIOS/UEFI\n' +printf ' 3. KVM kernel modules failed to load\n' +printf '\n' +printf ' How to proceed:\n' +printf ' - Reboot, enable VT-x/AMD-V in BIOS/UEFI, retry\n' +printf ' - Use a direct-boot tool (e.g. Rufus, balenaEtcher) for this ISO\n' +printf ' - Reboot with hiperiso_fallback=1 on the kernel\n' +printf ' command line to force TCG emulation (very slow)\n' +printf '\n' +if [ -n "${LOG_DIR:-}" ] && [ -d "$LOG_DIR" ]; then + printf ' Failure session captured: %s\n' "$LOG_DIR" + printf '\n' +fi +printf '======================================================\n' +printf '\n' + +printf ' Press ENTER to reboot to the boot menu,\n' +printf ' or type "shell" for a recovery shell: ' + +read -r _choice +case "$_choice" in + shell|sh) + printf '\nDropping to recovery shell...\n' + exec /bin/sh + ;; + *) + printf '\nRebooting...\n' + sync + if command -v reboot >/dev/null 2>&1; then + reboot -f + else + # Fallback: magic SysRq + printf 'b' > /proc/sysrq-trigger 2>/dev/null + fi + # Should not reach here + exit 0 + ;; +esac diff --git a/host/initramfs/hiperiso-lib.sh b/host/initramfs/hiperiso-lib.sh new file mode 100755 index 0000000..769ec05 --- /dev/null +++ b/host/initramfs/hiperiso-lib.sh @@ -0,0 +1,706 @@ +#!/bin/sh +# ============================================================ +# hiperiso-lib.sh -- Shared functions for hiperiso initramfs +# POSIX sh compatible (dash/ash/busybox sh) +# ============================================================ + +# ── Mount points (from INTERFACES.sh) ──────────────────────── +EFI_MOUNT="/mnt/efi" +DATA_MOUNT="/mnt/usb" + +# ── Paths on EFI partition (from INTERFACES.sh) ────────────── +EFI_PAYLOAD_DIR="/EFI/hiperiso" +KERNEL_PATH="${EFI_PAYLOAD_DIR}/vmlinuz" +INITRAMFS_PATH="${EFI_PAYLOAD_DIR}/initramfs.cpio.gz" +OVMF_PATH="${EFI_PAYLOAD_DIR}/OVMF.fd" +TRACE_EVENTS_DIR="${EFI_PAYLOAD_DIR}/trace" + +# ── hiperiso_strip_quotes() ────────────────────────────────── +# Remove surrounding double or single quotes from a string +hiperiso_strip_quotes() { + _s="$1" + _s="${_s#\"}" # strip leading double-quote + _s="${_s%\"}" # strip trailing double-quote + _s="${_s#\'}" # strip leading single-quote + _s="${_s%\'}" # strip trailing single-quote + printf '%s' "$_s" +} + +# ── hiperiso_parse_cmdline() ───────────────────────────────── +# Extract hiperiso_* parameters from /proc/cmdline. +# Sets and exports: HIPERISO_ISO_PATH, HIPERISO_LOG_DIR, +# HIPERISO_TRACE_LEVEL, HIPERISO_GUEST_RAM, HIPERISO_GUEST_CPUS, +# HIPERISO_DISPLAY, HIPERISO_VGA, HIPERISO_FALLBACK +hiperiso_parse_cmdline() { + HIPERISO_ISO_PATH="" + HIPERISO_LOG_DIR="" + HIPERISO_TRACE_LEVEL="" + HIPERISO_GUEST_RAM="" + HIPERISO_GUEST_CPUS="" + HIPERISO_DISPLAY="" + HIPERISO_VGA="" + HIPERISO_FALLBACK="" + HIPERISO_AUTO_INSTALL="" + HIPERISO_PERSISTENCE="" + HIPERISO_DUD="" + HIPERISO_INJECTION="" + HIPERISO_CONF_REPLACE="" + HIPERISO_SECURE_BOOT="" + HIPERISO_TPM="" + HIPERISO_CPU_FEATURES="" + HIPERISO_BOOT_MODE="" + HIPERISO_NET_DUMP="" + + for _token in $(cat /proc/cmdline 2>/dev/null); do + case "$_token" in + hiperiso_iso=*) HIPERISO_ISO_PATH="${_token#hiperiso_iso=}" ;; + hiperiso_log=*) HIPERISO_LOG_DIR="${_token#hiperiso_log=}" ;; + hiperiso_trace_level=*) HIPERISO_TRACE_LEVEL="${_token#hiperiso_trace_level=}" ;; + hiperiso_ram=*) HIPERISO_GUEST_RAM="${_token#hiperiso_ram=}" ;; + hiperiso_cpus=*) HIPERISO_GUEST_CPUS="${_token#hiperiso_cpus=}" ;; + hiperiso_display=*) HIPERISO_DISPLAY="${_token#hiperiso_display=}" ;; + hiperiso_vga=*) HIPERISO_VGA="${_token#hiperiso_vga=}" ;; + hiperiso_fallback=*) HIPERISO_FALLBACK="${_token#hiperiso_fallback=}" ;; + hiperiso_auto_install=*) HIPERISO_AUTO_INSTALL="${_token#hiperiso_auto_install=}" ;; + hiperiso_persistence=*) HIPERISO_PERSISTENCE="${_token#hiperiso_persistence=}" ;; + hiperiso_dud=*) HIPERISO_DUD="${_token#hiperiso_dud=}" ;; + hiperiso_injection=*) HIPERISO_INJECTION="${_token#hiperiso_injection=}" ;; + hiperiso_conf_replace=*) HIPERISO_CONF_REPLACE="${_token#hiperiso_conf_replace=}" ;; + hiperiso_secure_boot=*) HIPERISO_SECURE_BOOT="${_token#hiperiso_secure_boot=}" ;; + hiperiso_tpm=*) HIPERISO_TPM="${_token#hiperiso_tpm=}" ;; + hiperiso_cpu_features=*) HIPERISO_CPU_FEATURES="${_token#hiperiso_cpu_features=}" ;; + hiperiso_boot_mode=*) HIPERISO_BOOT_MODE="${_token#hiperiso_boot_mode=}" ;; + hiperiso_net_dump=*) HIPERISO_NET_DUMP="${_token#hiperiso_net_dump=}" ;; + esac + done + + HIPERISO_ISO_PATH=$(hiperiso_strip_quotes "$HIPERISO_ISO_PATH") + HIPERISO_LOG_DIR=$(hiperiso_strip_quotes "$HIPERISO_LOG_DIR") + HIPERISO_TRACE_LEVEL=$(hiperiso_strip_quotes "$HIPERISO_TRACE_LEVEL") + HIPERISO_GUEST_RAM=$(hiperiso_strip_quotes "$HIPERISO_GUEST_RAM") + HIPERISO_GUEST_CPUS=$(hiperiso_strip_quotes "$HIPERISO_GUEST_CPUS") + HIPERISO_DISPLAY=$(hiperiso_strip_quotes "$HIPERISO_DISPLAY") + HIPERISO_VGA=$(hiperiso_strip_quotes "$HIPERISO_VGA") + HIPERISO_FALLBACK=$(hiperiso_strip_quotes "$HIPERISO_FALLBACK") + HIPERISO_AUTO_INSTALL=$(hiperiso_strip_quotes "$HIPERISO_AUTO_INSTALL") + HIPERISO_PERSISTENCE=$(hiperiso_strip_quotes "$HIPERISO_PERSISTENCE") + HIPERISO_DUD=$(hiperiso_strip_quotes "$HIPERISO_DUD") + HIPERISO_INJECTION=$(hiperiso_strip_quotes "$HIPERISO_INJECTION") + HIPERISO_CONF_REPLACE=$(hiperiso_strip_quotes "$HIPERISO_CONF_REPLACE") + HIPERISO_SECURE_BOOT=$(hiperiso_strip_quotes "$HIPERISO_SECURE_BOOT") + HIPERISO_TPM=$(hiperiso_strip_quotes "$HIPERISO_TPM") + HIPERISO_CPU_FEATURES=$(hiperiso_strip_quotes "$HIPERISO_CPU_FEATURES") + HIPERISO_BOOT_MODE=$(hiperiso_strip_quotes "$HIPERISO_BOOT_MODE") + HIPERISO_NET_DUMP=$(hiperiso_strip_quotes "$HIPERISO_NET_DUMP") + + export HIPERISO_ISO_PATH HIPERISO_LOG_DIR HIPERISO_TRACE_LEVEL + export HIPERISO_GUEST_RAM HIPERISO_GUEST_CPUS + export HIPERISO_DISPLAY HIPERISO_VGA HIPERISO_FALLBACK + export HIPERISO_AUTO_INSTALL HIPERISO_PERSISTENCE HIPERISO_DUD + export HIPERISO_INJECTION HIPERISO_CONF_REPLACE HIPERISO_SECURE_BOOT + export HIPERISO_TPM HIPERISO_CPU_FEATURES HIPERISO_BOOT_MODE + export HIPERISO_NET_DUMP +} + +# ── hiperiso_find_usb_partition() ──────────────────────────── +# Scan block devices for the hiperiso USB data partition. +# Outputs the device path (e.g. /dev/sdb2) and returns 0, +# or outputs nothing and returns 1 if not found. +# Strategy: blkid label "HIPERISO" -> known partition 2 devices +# -> sysfs removable scan +hiperiso_find_usb_partition() { + # Strategy 1: blkid by label HIPERISO + if command -v blkid >/dev/null 2>&1; then + _dev=$(blkid -L HIPERISO 2>/dev/null) + if [ -n "$_dev" ] && [ -e "$_dev" ]; then + printf '%s' "$_dev" + return 0 + fi + _dev=$(blkid -t LABEL=HIPERISO -o device 2>/dev/null | head -n 1) + if [ -n "$_dev" ] && [ -e "$_dev" ]; then + printf '%s' "$_dev" + return 0 + fi + fi + + # Strategy 2: try known partition-1 device nodes (sd[a-f] covers + # most USB storage; the data partition is partition 1). + for _dev in /dev/sda1 /dev/sdb1 /dev/sdc1 /dev/sdd1 /dev/sde1 /dev/sdf1; do + [ -e "$_dev" ] || continue + if command -v blkid >/dev/null 2>&1; then + # Verify it has a filesystem + if blkid "$_dev" >/dev/null 2>&1; then + printf '%s' "$_dev" + return 0 + fi + else + # No blkid available -- accept device if it exists + printf '%s' "$_dev" + return 0 + fi + done + + # Strategy 3: scan sysfs for removable block devices with partition 1. + # Only returns a device if sysfs marks it as removable to avoid + # accidentally selecting the system disk. + for _blk in /sys/block/sd[a-z] /sys/block/nvme[0-9]n[0-9] /sys/block/mmcblk[0-9]; do + [ -e "$_blk" ] || continue + _name=$(basename "$_blk") + # NVMe (nvme0n1) and MMC (mmcblk0) use 'p' separator before partition number + case "$_name" in + nvme*|mmcblk*) _part="/dev/${_name}p1" ;; + *) _part="/dev/${_name}1" ;; + esac + [ -e "$_part" ] || continue + _removable=$(cat "${_blk}/removable" 2>/dev/null) + if [ "$_removable" = "1" ]; then + printf '%s' "$_part" + return 0 + fi + done + + return 1 +} + +# ── hiperiso_log() ─────────────────────────────────────────── +# Log a message to console and env.txt (if LOG_DIR is set). +hiperiso_log() { + _msg="$1" + printf '[hiperiso] %s\n' "$_msg" + if [ -n "${LOG_DIR:-}" ] && [ -d "$LOG_DIR" ]; then + printf '[hiperiso] %s\n' "$_msg" >> "${LOG_DIR}/env.txt" + fi +} + +# ── hiperiso_write_env() ───────────────────────────────────── +# Write full environment snapshot to ${LOG_DIR}/env.txt. +# Caller must set LOG_DIR and have /proc, /sys mounted. +hiperiso_write_env() { + _envfile="${LOG_DIR}/env.txt" + : > "$_envfile" + + { + printf '=== hiperiso Environment Snapshot ===\n' + printf 'Uptime: %s\n' "$(cut -d' ' -f1 /proc/uptime 2>/dev/null)" + printf '\n' + printf -- '--- Host Kernel ---\n' + cat /proc/version 2>/dev/null + printf 'Cmdline: %s\n' "$(cat /proc/cmdline 2>/dev/null)" + printf '\n' + printf -- '--- Memory ---\n' + grep '^MemTotal\|^MemFree\|^MemAvailable\|^SwapTotal' /proc/meminfo 2>/dev/null + printf '\n' + printf -- '--- CPU ---\n' + grep '^model name' /proc/cpuinfo 2>/dev/null | head -n 1 + grep '^cpu cores' /proc/cpuinfo 2>/dev/null | head -n 1 + grep '^siblings' /proc/cpuinfo 2>/dev/null | head -n 1 + printf 'Logical processors: %s\n' \ + "$(grep -c '^processor' /proc/cpuinfo 2>/dev/null)" + if grep -q -E '(vmx|svm)' /proc/cpuinfo 2>/dev/null; then + printf 'Hardware virtualization: supported (vmx/svm)\n' + else + printf 'Hardware virtualization: not detected\n' + fi + printf '\n' + printf -- '--- Block Devices ---\n' + ls /dev/sd* 2>/dev/null + ls /dev/nvme* 2>/dev/null + printf '\n' + printf -- '--- hiperiso Configuration ---\n' + printf 'ISO path: %s\n' "${HIPERISO_ISO_PATH:-}" + printf 'Log dir: %s\n' "${HIPERISO_LOG_DIR:-}" + printf 'Trace level: %s\n' "${HIPERISO_TRACE_LEVEL:-standard}" + printf 'Guest RAM: %s MB\n' "${HIPERISO_GUEST_RAM:-2048}" + printf 'Guest CPUs: %s\n' "${HIPERISO_GUEST_CPUS:-2}" + printf 'Display: %s\n' "${HIPERISO_DISPLAY:-none}" + printf 'VGA: %s\n' "${HIPERISO_VGA:-std}" + printf 'Fallback: %s\n' "${HIPERISO_FALLBACK:-0}" + printf 'Data mount: %s\n' "$DATA_MOUNT" + printf 'EFI mount: %s\n' "$EFI_MOUNT" + printf 'Log dir (abs): %s\n' "$LOG_DIR" + printf 'Session ID: %s\n' "$(hiperiso_session_id)" + printf '\n' + } >> "$_envfile" +} + +# ── hiperiso_check_ram() ───────────────────────────────────── +# Verify enough RAM for the guest VM. +# Args: $1 = guest RAM in MB (default 2048) +# Returns: 0 if sufficient, 1 if not +hiperiso_check_ram() { + _guest_ram="${1:-2048}" + _host_overhead=512 # host kernel + initramfs working set + _needed=$(( _guest_ram + _host_overhead )) + _total_kb=$(awk '/^MemTotal:/ { print $2 }' /proc/meminfo 2>/dev/null) + if [ -z "$_total_kb" ]; then + return 0 # cannot determine -- assume OK + fi + _total_mb=$(( _total_kb / 1024 )) + if [ "$_total_mb" -lt "$_needed" ]; then + return 1 + fi + return 0 +} + +# ── hiperiso_utc_stamp() ────────────────────────────────────── +# Output a compact UTC timestamp suitable for directory names: +# YYYYMMDDTHHMMSSZ +# Falls back to uptime-based identifier if date(1) is unavailable. +hiperiso_utc_stamp() { + _ts=$(date -u +%Y%m%dT%H%M%SZ 2>/dev/null) + if [ -z "$_ts" ]; then + _up=$(cut -d. -f1 /proc/uptime 2>/dev/null) + _ts="boot${_up:-0}" + fi + printf '%s' "$_ts" +} + +# ── hiperiso_utc_iso() ──────────────────────────────────────── +# Output ISO 8601 UTC timestamp for JSON metadata: +# YYYY-MM-DDTHH:MM:SSZ +# Falls back to "unknown" if date(1) is unavailable. +hiperiso_utc_iso() { + _ts=$(date -u +%Y-%m-%dT%H:%M:%SZ 2>/dev/null) + if [ -z "$_ts" ]; then + printf '%s' "unknown" + else + printf '%s' "$_ts" + fi +} + +# ── hiperiso_json_escape() ──────────────────────────────────── +# Escape backslashes and double quotes for JSON string safety. +# Uses sed if available; falls back to raw value. +hiperiso_json_escape() { + printf '%s' "$1" | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g' 2>/dev/null \ + || printf '%s' "$1" +} + +# ── hiperiso_session_id() ───────────────────────────────────── +# Derive the session identifier (directory basename) from the +# current HIPERISO_LOG_DIR. E.g. "/hiperiso/logs/ubuntu_Ts/" → +# "ubuntu_Ts". +hiperiso_session_id() { + _sid="${HIPERISO_LOG_DIR%/}" + printf '%s' "${_sid##*/}" +} + +# ── hiperiso_derive_session_dir() ───────────────────────────── +# Given a base log dir from GRUB (e.g. /hiperiso/logs/ubuntu/), +# append a UTC timestamp so every boot gets a unique directory. +# Input: $1 = base log dir (from hiperiso_log= cmdline param) +# Output: /hiperiso/logs/_/ +hiperiso_derive_session_dir() { + _base="${1%/}" + _ts=$(hiperiso_utc_stamp) + _name="${_base##*/}" + _parent="${_base%/*}" + printf '%s/%s_%s/' "$_parent" "$_name" "$_ts" +} + +# ── hiperiso_write_session_meta() ───────────────────────────── +# Write session.json into LOG_DIR with structured session metadata +# for agent consumption. Caller must have LOG_DIR set and created. +hiperiso_write_session_meta() { + [ -n "${LOG_DIR:-}" ] && [ -d "$LOG_DIR" ] || return 1 + + _file="${LOG_DIR}/session.json" + _ts=$(hiperiso_utc_iso) + _sid=$(hiperiso_session_id) + + # Capture the session start time on first invocation so the + # final manifest rewrite can preserve it. + if [ -z "${HIPERISO_SESSION_START:-}" ]; then + HIPERISO_SESSION_START="$_ts" + export HIPERISO_SESSION_START + fi + + _iso_basename="${HIPERISO_ISO_PATH##*/}" + _iso_esc=$(hiperiso_json_escape "${HIPERISO_ISO_PATH:-}") + _log_esc=$(hiperiso_json_escape "${HIPERISO_LOG_DIR%/}") + + { + printf '{\n' + printf ' "session_id": "%s",\n' "$_sid" + printf ' "status": "%s",\n' "${1:-started}" + printf ' "iso_path": "%s",\n' "$_iso_esc" + printf ' "iso_basename": "%s",\n' "$_iso_basename" + printf ' "log_dir": "%s",\n' "$_log_esc" + printf ' "start_time_utc": "%s",\n' "$_ts" + printf ' "kvm": "%s",\n' "${KVM_STATUS:-unknown}" + printf ' "trace_level": "%s",\n' "${HIPERISO_TRACE_LEVEL:-standard}" + printf ' "guest_ram_mb": %s,\n' "${HIPERISO_GUEST_RAM:-2048}" + printf ' "guest_cpus": %s\n' "${HIPERISO_GUEST_CPUS:-2}" + printf '}\n' + } > "$_file" + sync "$_file" 2>/dev/null || sync +} + +# ── hiperiso_write_status() ─────────────────────────────────── +# Update status.json in LOG_DIR with current session state. +# Args: $1 = status (started|running|complete|failed) +# $2 = stage (mounted|kvm_ok|tcg_emulation|launching_qemu| +# qemu_running|report_generated|session_complete| +# fallback_no_kvm|iso_not_found|fatal_error|...) +# $3 = qemu_exit_code (optional, integer) +hiperiso_write_status() { + [ -n "${LOG_DIR:-}" ] && [ -d "$LOG_DIR" ] || return 1 + + _status="$1" + _stage="$2" + _qexit="${3:-}" + _file="${LOG_DIR}/status.json" + _ts=$(hiperiso_utc_iso) + _sid=$(hiperiso_session_id) + + { + printf '{\n' + printf ' "session_id": "%s",\n' "$_sid" + printf ' "status": "%s",\n' "$_status" + printf ' "stage": "%s",\n' "$_stage" + if [ -n "$_qexit" ]; then + printf ' "qemu_exit_code": %s,\n' "$_qexit" + fi + printf ' "timestamp_utc": "%s"\n' "$_ts" + printf '}\n' + } > "$_file" + sync "$_file" 2>/dev/null || sync +} + +# ── hiperiso_run_report() ───────────────────────────────────── +# Run the hiperiso-log analysis tool on LOG_DIR to auto-generate +# report.json and report.txt. Returns 0 on success, 1 if the tool +# is missing or fails. +hiperiso_run_report() { + [ -n "${LOG_DIR:-}" ] && [ -d "$LOG_DIR" ] || return 1 + + _tool="" + for _p in /usr/bin/hiperiso-log /bin/hiperiso-log hiperiso-log; do + if command -v "$_p" >/dev/null 2>&1; then + _tool="$_p" + break + fi + done + + if [ -z "$_tool" ]; then + hiperiso_log "report: hiperiso-log not found -- skipping report generation" + return 1 + fi + + hiperiso_log "report: generating report.json and report.txt..." + if "$_tool" analyze "$LOG_DIR" >>"${LOG_DIR}/report.log" 2>&1; then + hiperiso_log "report: generated successfully" + return 0 + else + _rc=$? + hiperiso_log "report: generation failed (exit $_rc)" + return 1 + fi +} + +# ── hiperiso_mark_session_complete() ────────────────────────── +# Write the session-complete sentinel and do a final sync so agents +# can detect that the boot session finished cleanly. +hiperiso_mark_session_complete() { + [ -n "${LOG_DIR:-}" ] && [ -d "$LOG_DIR" ] || return 1 + + _ts=$(hiperiso_utc_iso) + printf '%s\n' "$_ts" > "${LOG_DIR}/SESSION_COMPLETE" + sync "${LOG_DIR}/SESSION_COMPLETE" 2>/dev/null || sync +} + +# ── hiperiso_read_analysis_meta() ───────────────────────────── +# Parse ${LOG_DIR}/analysis.meta (flat KEY=VALUE written by the +# hiperiso-log report tool) into HIPERISO_A_* shell variables. +# Missing file leaves all values empty. POSIX sh: no arrays, no +# $(/dev/null) + _host_cpu_model=$(grep '^model name' /proc/cpuinfo 2>/dev/null | head -n 1 | cut -d: -f2- | sed 's/^[[:space:]]*//') + _host_cpu_logical=$(grep -c '^processor' /proc/cpuinfo 2>/dev/null) + _host_ram_kb=$(awk '/^MemTotal:/ { print $2 }' /proc/meminfo 2>/dev/null) + _host_ram_mb=$(( _host_ram_kb / 1024 )) + if grep -q -E '(vmx|svm)' /proc/cpuinfo 2>/dev/null; then + _host_virt="true" + else + _host_virt="false" + fi + + printf ' "host": {\n' + printf ' "kernel_version": "%s",\n' "$(hiperiso_json_escape "${_host_kernel:-}")" + printf ' "cpu_model": "%s",\n' "$(hiperiso_json_escape "${_host_cpu_model:-}")" + printf ' "cpu_logical_cores": %s,\n' "${_host_cpu_logical:-0}" + printf ' "total_ram_mb": %s,\n' "${_host_ram_mb:-0}" + printf ' "virt_supported": %s\n' "$_host_virt" + printf ' },\n' + printf ' "analysis": {\n' + printf ' "boot_result": "%s",\n' "${HIPERISO_A_BOOT_RESULT:-unknown}" + printf ' "boot_stage_final": "%s",\n' "${HIPERISO_A_BOOT_STAGE:-unknown}" + printf ' "failure_domain": "%s",\n' "${HIPERISO_A_FAILURE_DOMAIN:-unknown}" + printf ' "reached_login": %s,\n' "${HIPERISO_A_REACHED_LOGIN:-0}" + printf ' "kernel_panic_count": %s,\n' "${HIPERISO_A_KERNEL_PANIC:-0}" + printf ' "error_count": %s,\n' "${HIPERISO_A_ERROR_COUNT:-0}" + printf ' "has_serial": %s,\n' "${HIPERISO_A_HAS_SERIAL:-0}" + printf ' "has_trace": %s,\n' "${HIPERISO_A_HAS_TRACE:-0}" + printf ' "boot_duration_ms": %s,\n' "${HIPERISO_A_DURATION_MS:-0}" + printf ' "graphics_drivers": "%s",\n' "$_gdrv_esc" + printf ' "graphics_resolution": "%s"\n' "$_gres_esc" + printf ' },\n' + + # hw_inventory: list which hardware data files exist so agents + # know what's available without scanning the directory. + _hw_dir="${LOG_DIR}/hw" + _hw_files="" + if [ -d "$_hw_dir" ]; then + _hw_files=$(ls "$_hw_dir" 2>/dev/null | tr '\n' ',' | sed 's/,$//') + fi + + printf ' "hw_inventory": {\n' + printf ' "available": %s,\n' $([ -n "$_hw_files" ] && echo true || echo false) + printf ' "files": "%s",\n' "$(hiperiso_json_escape "$_hw_files")" + printf ' "has_timing": %s,\n' $([ -f "${LOG_DIR}/timing.json" ] && echo true || echo false) + printf ' "has_network_pcap": %s\n' $([ -f "${LOG_DIR}/network.pcap" ] && echo true || echo false) + printf ' }\n' + printf '}\n' + } > "$_file" + sync "$_file" 2>/dev/null || sync +} + +# ── hiperiso_append_global_index() ──────────────────────────── +# Append one JSONL record to /hiperiso/logs/index.jsonl — the +# append-only corpus-level index. Creates the file if absent. +# One compact JSON object per line so failed + successful sessions +# are both indexed for downstream ingestion. +# Args: $1=status $2=qemu_exit_code $3=start_utc $4=end_utc +# $5=report_available(0|1) +hiperiso_append_global_index() { + [ -n "${LOG_DIR:-}" ] && [ -d "$LOG_DIR" ] || return 1 + + _status="$1" + _qexit="${2:-}" + _start_ts="${3:-unknown}" + _end_ts="${4:-unknown}" + _report_avail="${5:-0}" + + # LOG_DIR = .../hiperiso/logs// -> parent = .../hiperiso/logs + _idx_parent=$(dirname "$LOG_DIR") + _idx="${_idx_parent}/index.jsonl" + _sid=$(hiperiso_session_id) + _iso_basename="${HIPERISO_ISO_PATH##*/}" + _iso_esc=$(hiperiso_json_escape "${HIPERISO_ISO_PATH:-}") + _log_esc=$(hiperiso_json_escape "${HIPERISO_LOG_DIR%/}") + _gdrv_esc=$(hiperiso_json_escape "${HIPERISO_A_GRAPHICS_DRIVERS:-}") + + _result="${HIPERISO_A_BOOT_RESULT:-}" + [ -n "$_result" ] || _result="$_status" + + { + printf '{"session_id":"%s","status":"%s","result":"%s",' \ + "$_sid" "$_status" "$_result" + printf '"iso_basename":"%s","iso_path":"%s","log_dir":"%s",' \ + "$(hiperiso_json_escape "$_iso_basename")" "$_iso_esc" "$_log_esc" + printf '"start_time_utc":"%s","end_time_utc":"%s",' \ + "$_start_ts" "$_end_ts" + printf '"kvm":"%s","trace_level":"%s",' \ + "${KVM_STATUS:-unknown}" "${HIPERISO_TRACE_LEVEL:-standard}" + if [ -n "$_qexit" ]; then + printf '"qemu_exit_code":%s,' "$_qexit" + else + printf '"qemu_exit_code":null,' + fi + printf '"report_available":%s,' "$_report_avail" + printf '"boot_result":"%s","boot_stage_final":"%s","failure_domain":"%s",' \ + "${HIPERISO_A_BOOT_RESULT:-unknown}" \ + "${HIPERISO_A_BOOT_STAGE:-unknown}" \ + "${HIPERISO_A_FAILURE_DOMAIN:-unknown}" + printf '"graphics_drivers":"%s"}\n' "$_gdrv_esc" + } >> "$_idx" + sync "$_idx" 2>/dev/null || sync +} + +# ── hiperiso_finalize_session() ─────────────────────────────── +# Finalize the boot session: rewrite the canonical session.json +# manifest with full end/result metadata, update status.json, +# append to the global JSONL index, and write the completion +# sentinel. Called on BOTH success and failure paths so every +# session — including failed ones — is indexed. +# Args: $1=status (complete|failed) +# $2=qemu_exit_code (optional) +# $3=stage label (optional, e.g. session_complete|iso_not_found| +# fallback_no_kvm) +hiperiso_finalize_session() { + [ -n "${LOG_DIR:-}" ] && [ -d "$LOG_DIR" ] || return 1 + + _status="${1:-complete}" + _qexit="${2:-}" + _stage="${3:-session_complete}" + + _end_ts=$(hiperiso_utc_iso) + _start_ts="${HIPERISO_SESSION_START:-unknown}" + + hiperiso_read_analysis_meta + + if [ -f "${LOG_DIR}/report.json" ]; then + _report_avail=1 + else + _report_avail=0 + fi + + hiperiso_write_final_manifest "$_status" "$_qexit" \ + "$_start_ts" "$_end_ts" "$_report_avail" + hiperiso_write_status "$_status" "$_stage" "$_qexit" + hiperiso_append_global_index "$_status" "$_qexit" \ + "$_start_ts" "$_end_ts" "$_report_avail" + hiperiso_mark_session_complete + + hiperiso_log "session finalized: status=$_status result=${HIPERISO_A_BOOT_RESULT:-n/a}" + hiperiso_log "global index: $(dirname "$LOG_DIR")/index.jsonl" +} + +# ── hiperiso_timing_mark() ──────────────────────────────────── +# Record a boot phase timing mark. Uses /proc/uptime (seconds.microseconds +# since kernel boot) as a monotonic clock. Appends to ${LOG_DIR}/timing.dat +# as raw TSV: phaseuptimeiso8601 +# Args: $1 = phase name (e.g. kernel_start, data_mounted, qemu_started) +hiperiso_timing_mark() { + [ -n "${LOG_DIR:-}" ] && [ -d "$LOG_DIR" ] || return 0 + + _phase="$1" + _up=$(cut -d' ' -f1 /proc/uptime 2>/dev/null) + _ts=$(hiperiso_utc_iso) + + printf '%s\t%s\t%s\n' "$_phase" "${_up:-0}" "$_ts" >> "${LOG_DIR}/timing.dat" +} + +# ── hiperiso_timing_write_json() ────────────────────────────── +# Convert timing.dat into structured timing.json for agent consumption. +# Calculates per-phase deltas and total duration. Called at session end. +hiperiso_timing_write_json() { + _dat="${LOG_DIR}/timing.dat" + _out="${LOG_DIR}/timing.json" + [ -f "$_dat" ] || return 0 + + # First and last uptime values for total duration + _first_up=$(head -n1 "$_dat" | cut -f2) + _last_up=$(tail -n1 "$_dat" | cut -f2) + _duration="" + if [ -n "$_first_up" ] && [ -n "$_last_up" ]; then + _duration=$(awk "BEGIN { printf \"%.3f\", $_last_up - $_first_up }" 2>/dev/null) + fi + + { + printf '{\n' + printf ' "total_duration_s": %s,\n' "${_duration:-null}" + printf ' "phases": [\n' + + _prev_up="" + _first=1 + while IFS="$(printf '\t')" read -r _phase _up _ts || [ -n "$_phase" ]; do + [ -n "$_phase" ] || continue + + if [ "$_first" -eq 1 ]; then + _first=0 + else + printf ',\n' + fi + + # Delta from previous phase + _delta="" + if [ -n "$_prev_up" ] && [ -n "$_up" ]; then + _delta=$(awk "BEGIN { printf \"%.3f\", $_up - $_prev_up }" 2>/dev/null) + fi + + printf ' {"phase": "%s", "uptime_s": %s, "timestamp_utc": "%s", "delta_s": %s}' \ + "$_phase" "${_up:-0}" "$_ts" "${_delta:-null}" + + _prev_up="$_up" + done < "$_dat" + + printf '\n ]\n' + printf '}\n' + } > "$_out" + sync "$_out" 2>/dev/null || sync +} diff --git a/host/initramfs/hw_collect.sh b/host/initramfs/hw_collect.sh new file mode 100755 index 0000000..7bd6580 --- /dev/null +++ b/host/initramfs/hw_collect.sh @@ -0,0 +1,506 @@ +#!/bin/sh +# ============================================================ +# hw_collect.sh -- Hardware inventory collector for hiperiso +# +# Collects comprehensive hardware data across three phases: +# +# hw_collect.sh pre Before QEMU: host /proc, /sys, ACPI, +# SMBIOS, KVM params, USB, EFI +# hw_collect.sh monitor Background: send HMP commands to QEMU +# via FIFO pipe chardev (18 commands) +# hw_collect.sh post After QEMU: parse raw dump, generate +# JSON summaries, extract crash data +# +# Output directory: ${LOG_DIR}/hw/ +# +# Host files (always present if pre-phase ran): +# host_cpuinfo.txt /proc/cpuinfo +# host_meminfo.txt /proc/meminfo +# host_interrupts.txt /proc/interrupts +# host_iomem.txt /proc/iomem (physical memory map) +# host_ioports.txt /proc/ioports +# host_cmdline.txt /proc/cmdline +# host_pci_devices.txt /proc/bus/pci/devices +# host_cpu_cache.txt CPU cache topology from sysfs +# host_numa.txt NUMA node topology +# host_iommu.txt IOMMU groups (VT-d/AMD-Vi) +# host_kvm.txt /dev/kvm presence +# host_block.txt Block device info +# host_dmesg.txt Host kernel boot messages +# host_version.txt /proc/version +# host_diskstats.txt /proc/diskstats +# host_usb.txt USB device tree from sysfs +# host_dmi.txt DMI/SMBIOS identifiers from sysfs +# acpi/ Host ACPI tables (binary .aml files) +# smbios/ Host DMI/SMBIOS raw tables +# kvm_caps.json KVM capabilities (module params) +# +# QEMU files (present if QEMU launched and ran >= 3 seconds): +# qemu_version.txt QEMU version string +# qemu_qtree.txt Full QEMU device tree +# qemu_pci.txt PCI device list as seen by guest +# qemu_memmap.txt Guest memory map (mtree) +# qemu_memmap_flat.txt Flat memory map (mtree -f) +# qemu_cpuid.txt CPUID leaves exposed to guest +# qemu_chardev.txt QEMU character devices +# qemu_block.txt QEMU block devices +# qemu_net.txt QEMU network devices +# qemu_ioapic.txt IO-APIC interrupt routing +# qemu_lapic.txt Local APIC state +# qemu_registers.txt CPU register snapshot +# qemu_tlb.txt TLB state (TCG only) +# qemu_numa.txt Guest NUMA topology +# qemu_hpet.txt HPET timer state +# qemu_irq.txt IRQ statistics +# qemu_qomtree.txt QOM object hierarchy +# qemu_smbios.txt SMBIOS table (if QEMU supports it) +# pci_summary.json Structured PCI device list +# ============================================================ + +. /hiperiso-lib.sh + +LOG_DIR="${LOG_DIR:?hw_collect: LOG_DIR not set}" +HW_DIR="${LOG_DIR}/hw" +MON_IN="/tmp/hw_mon.in" +MON_OUT="/tmp/hw_mon.out" + +mkdir -p "$HW_DIR" + +# ── Mode: pre ──────────────────────────────────────────────── +hw_collect_pre() { + hiperiso_log "hw_collect: collecting host hardware inventory..." + + # ── /proc snapshots ────────────────────────────────────── + cat /proc/cpuinfo > "$HW_DIR/host_cpuinfo.txt" 2>/dev/null + cat /proc/meminfo > "$HW_DIR/host_meminfo.txt" 2>/dev/null + cat /proc/version > "$HW_DIR/host_version.txt" 2>/dev/null + cat /proc/diskstats > "$HW_DIR/host_diskstats.txt" 2>/dev/null + + [ -f /proc/interrupts ] && cat /proc/interrupts > "$HW_DIR/host_interrupts.txt" 2>/dev/null + [ -f /proc/iomem ] && cat /proc/iomem > "$HW_DIR/host_iomem.txt" 2>/dev/null + [ -f /proc/ioports ] && cat /proc/ioports > "$HW_DIR/host_ioports.txt" 2>/dev/null + [ -f /proc/cmdline ] && cat /proc/cmdline > "$HW_DIR/host_cmdline.txt" 2>/dev/null + [ -f /proc/bus/pci/devices ] && cat /proc/bus/pci/devices > "$HW_DIR/host_pci_devices.txt" 2>/dev/null + + # ── CPU cache topology ─────────────────────────────────── + { + printf '=== CPU Cache Topology ===\n' + for _idx in /sys/devices/system/cpu/cpu*/cache/index*/; do + [ -d "$_idx" ] || continue + _level=$(cat "${_idx}level" 2>/dev/null) + _type=$(cat "${_idx}type" 2>/dev/null) + _size=$(cat "${_idx}size" 2>/dev/null) + _shared=$(cat "${_idx}shared_cpu_list" 2>/dev/null) + printf 'cpu=%s level=%s type=%s size=%s shared=%s\n' \ + "$(printf '%s' "$_idx" | sed 's|.*/cpu\(cpu[0-9]*\)/.*|\1|')" \ + "$_level" "$_type" "$_size" "$_shared" + done + } > "$HW_DIR/host_cpu_cache.txt" 2>/dev/null + + # ── CPU topology (thread/core/socket IDs) ──────────────── + { + printf '=== CPU Topology ===\n' + for _cpu in /sys/devices/system/cpu/cpu[0-9]*; do + [ -d "$_cpu" ] || continue + _name=$(basename "$_cpu") + _core=$(cat "${_cpu}/topology/core_id" 2>/dev/null) + _sock=$(cat "${_cpu}/topology/physical_package_id" 2>/dev/null) + _threads=$(cat "${_cpu}/topology/thread_siblings_list" 2>/dev/null) + printf '%s: core=%s socket=%s siblings=%s\n' \ + "$_name" "${_core:-?}" "${_sock:-?}" "${_threads:-?}" + done + } > "$HW_DIR/host_cpu_topology.txt" 2>/dev/null + + # ── NUMA topology ──────────────────────────────────────── + if [ -d /sys/devices/system/node ]; then + { + printf '=== NUMA Topology ===\n' + for _node in /sys/devices/system/node/node[0-9]*; do + [ -d "$_node" ] || continue + _name=$(basename "$_node") + _cpus=$(cat "${_node}/cpulist" 2>/dev/null) + printf '%s: cpus=%s\n' "$_name" "$_cpus" + done + } > "$HW_DIR/host_numa.txt" 2>/dev/null + fi + + # ── IOMMU groups (VT-d / AMD-Vi) ───────────────────────── + if [ -d /sys/kernel/iommu_groups ]; then + { + printf '=== IOMMU Groups ===\n' + for _grp in /sys/kernel/iommu_groups/[0-9]*; do + [ -d "$_grp" ] || continue + _gid=$(basename "$_grp") + printf 'group %s:\n' "$_gid" + for _dev in "$_grp"/devices/*; do + [ -e "$_dev" ] || continue + printf ' %s\n' "$(basename "$_dev")" + done + done + } > "$HW_DIR/host_iommu.txt" 2>/dev/null + fi + + # ── KVM presence ───────────────────────────────────────── + if [ -e /dev/kvm ]; then + { + printf '=== KVM Device ===\n' + ls -la /dev/kvm 2>/dev/null + } > "$HW_DIR/host_kvm.txt" 2>/dev/null + fi + + # ── KVM capabilities (module parameters) ───────────────── + _kvm_json="$HW_DIR/kvm_caps.json" + { + printf '{\n' + printf ' "kvm_present": %s,\n' $([ -e /dev/kvm ] && echo true || echo false) + + # Core KVM parameters + _first=1 + printf ' "kvm_core": {' + if [ -d /sys/module/kvm/parameters ]; then + for _p in /sys/module/kvm/parameters/*; do + [ -f "$_p" ] || continue + _pname=$(basename "$_p") + _pval=$(cat "$_p" 2>/dev/null) + if [ "$_first" -eq 1 ]; then _first=0; else printf ','; fi + printf '\n "%s": "%s"' "$_pname" "$_pval" + done + fi + [ "$_first" -eq 0 ] && printf '\n ' + printf '},\n' + + # Intel VT-x parameters + _first=1 + printf ' "kvm_intel": {' + if [ -d /sys/module/kvm_intel/parameters ]; then + for _p in /sys/module/kvm_intel/parameters/*; do + [ -f "$_p" ] || continue + _pname=$(basename "$_p") + _pval=$(cat "$_p" 2>/dev/null) + if [ "$_first" -eq 1 ]; then _first=0; else printf ','; fi + printf '\n "%s": "%s"' "$_pname" "$_pval" + done + fi + [ "$_first" -eq 0 ] && printf '\n ' + printf '},\n' + + # AMD-V parameters + _first=1 + printf ' "kvm_amd": {' + if [ -d /sys/module/kvm_amd/parameters ]; then + for _p in /sys/module/kvm_amd/parameters/*; do + [ -f "$_p" ] || continue + _pname=$(basename "$_p") + _pval=$(cat "$_p" 2>/dev/null) + if [ "$_first" -eq 1 ]; then _first=0; else printf ','; fi + printf '\n "%s": "%s"' "$_pname" "$_pval" + done + fi + [ "$_first" -eq 0 ] && printf '\n ' + printf '}\n' + printf '}\n' + } > "$_kvm_json" 2>/dev/null + + # ── Block devices ──────────────────────────────────────── + { + printf '=== Block Devices ===\n' + for _blk in /sys/block/sd* /sys/block/nvme* /sys/block/sr* /sys/block/mmcblk*; do + [ -d "$_blk" ] || continue + _name=$(basename "$_blk") + _size=$(cat "${_blk}/size" 2>/dev/null) + _rm=$(cat "${_blk}/removable" 2>/dev/null) + _ro=$(cat "${_blk}/readonly" 2>/dev/null) + _model=$(cat "${_blk}/device/model" 2>/dev/null) + _vendor=$(cat "${_blk}/device/vendor" 2>/dev/null) + printf '%s: sectors=%s removable=%s readonly=%s model=%s vendor=%s\n' \ + "$_name" "${_size:-?}" "${_rm:-?}" "${_ro:-?}" \ + "$(printf '%s' "$_model" | tr -d ' ')" \ + "$(printf '%s' "$_vendor" | tr -d ' ')" + done + } > "$HW_DIR/host_block.txt" 2>/dev/null + + # ── USB device tree ────────────────────────────────────── + { + printf '=== USB Devices ===\n' + for _dev in /sys/bus/usb/devices/*; do + [ -d "$_dev" ] || continue + _name=$(basename "$_dev") + _vid=$(cat "${_dev}/idVendor" 2>/dev/null) + _pid=$(cat "${_dev}/idProduct" 2>/dev/null) + _mfr=$(cat "${_dev}/manufacturer" 2>/dev/null) + _prod=$(cat "${_dev}/product" 2>/dev/null) + _speed=$(cat "${_dev}/speed" 2>/dev/null) + [ -n "$_vid" ] || continue + printf '%s: %s:%s speed=%s mfr=%s product=%s\n' \ + "$_name" "$_vid" "$_pid" "${_speed:-?}" \ + "$(printf '%s' "$_mfr" | tr -d '\n')" \ + "$(printf '%s' "$_prod" | tr -d '\n')" + done + } > "$HW_DIR/host_usb.txt" 2>/dev/null + + # ── ACPI tables (host firmware) ────────────────────────── + if [ -d /sys/firmware/acpi/tables ]; then + mkdir -p "$HW_DIR/acpi" + cp -r /sys/firmware/acpi/tables/* "$HW_DIR/acpi/" 2>/dev/null + { + printf '=== ACPI Tables ===\n' + for _t in /sys/firmware/acpi/tables/[A-Z]*; do + [ -f "$_t" ] || continue + _sig=$(basename "$_t") + _size=$(wc -c < "$_t" 2>/dev/null) + printf '%s: %s bytes\n' "$_sig" "${_size:-?}" + done + } > "$HW_DIR/acpi/_summary.txt" 2>/dev/null + fi + + # ── DMI / SMBIOS (host firmware) ───────────────────────── + if [ -d /sys/firmware/dmi/tables ]; then + mkdir -p "$HW_DIR/smbios" + cp /sys/firmware/dmi/tables/* "$HW_DIR/smbios/" 2>/dev/null + fi + if [ -d /sys/class/dmi/id ]; then + { + printf '=== DMI Identifiers ===\n' + for _f in /sys/class/dmi/id/*; do + [ -f "$_f" ] || continue + _key=$(basename "$_f") + _val=$(cat "$_f" 2>/dev/null) + [ -n "$_val" ] && printf '%s: %s\n' "$_key" "$_val" + done + } > "$HW_DIR/host_dmi.txt" 2>/dev/null + fi + + # ── EFI firmware info (if UEFI boot) ───────────────────── + if [ -d /sys/firmware/efi ]; then + { + printf '=== EFI Firmware ===\n' + cat /sys/firmware/efi/fw_platform_size 2>/dev/null + printf 'Runtime services: ' + ls /sys/firmware/efi/runtime 2>/dev/null || printf '(not exported)\n' + printf 'EFI variables:\n' + ls /sys/firmware/efi/efivars/ 2>/dev/null | head -20 + } > "$HW_DIR/host_efi.txt" 2>/dev/null + fi + + # ── Kernel config (if available) ───────────────────────── + if [ -f /proc/config.gz ]; then + cp /proc/config.gz "$HW_DIR/host_kernel_config.gz" 2>/dev/null + fi + + # ── DMESG (host kernel messages) ───────────────────────── + if command -v dmesg >/dev/null 2>&1; then + dmesg > "$HW_DIR/host_dmesg.txt" 2>/dev/null + fi + + _count=$(ls "$HW_DIR"/host_*.txt "$HW_DIR"/*.json 2>/dev/null | wc -l) + hiperiso_log "hw_collect: host inventory complete ($_count files)" +} + +# ── Mode: monitor ──────────────────────────────────────────── +hw_setup_monitor_fifo() { + mkfifo "$MON_IN" "$MON_OUT" 2>/dev/null + + cat "$MON_OUT" > "${HW_DIR}/qemu_monitor_raw.txt" 2>/dev/null & + MON_READER_PID=$! + printf '%s\n' "$MON_READER_PID" > /tmp/hw_mon_reader.pid + + # Writer: sends HMP commands via echo-delimited markers. + # QEMU pipe mode does not echo input, so we inject markers + # using QEMU's HMP 'echo' command to enable section splitting. + ( + sleep 3 + + # ── P0: Core inventory ─────────────────────────────── + printf 'echo ===SECTION===version\n'; sleep 0.1 + printf 'info version\n'; sleep 0.3 + printf 'echo ===SECTION===qtree\n'; sleep 0.1 + printf 'info qtree\n'; sleep 0.5 + printf 'echo ===SECTION===pci\n'; sleep 0.1 + printf 'info pci\n'; sleep 0.3 + + # ── P1: CPU + memory map ───────────────────────────── + printf 'echo ===SECTION===cpuid\n'; sleep 0.1 + printf 'info cpuid\n'; sleep 0.3 + printf 'echo ===SECTION===memmap\n'; sleep 0.1 + printf 'info mtree\n'; sleep 0.3 + printf 'echo ===SECTION===memmap_flat\n'; sleep 0.1 + printf 'info mtree -f\n'; sleep 0.3 + + # ── P2: SMBIOS + registers + TLB ───────────────────── + printf 'echo ===SECTION===smbios\n'; sleep 0.1 + printf 'info smbios\n'; sleep 0.3 + printf 'echo ===SECTION===registers\n'; sleep 0.1 + printf 'info registers\n'; sleep 0.3 + printf 'echo ===SECTION===tlb\n'; sleep 0.1 + printf 'info tlb\n'; sleep 0.3 + + # ── P3: Interrupt routing + KVM caps ───────────────── + printf 'echo ===SECTION===ioapic\n'; sleep 0.1 + printf 'info ioapic\n'; sleep 0.3 + printf 'echo ===SECTION===lapic\n'; sleep 0.1 + printf 'info lapic\n'; sleep 0.3 + printf 'echo ===SECTION===irq\n'; sleep 0.1 + printf 'info irq\n'; sleep 0.3 + + # ── Additional: topology + timers ──────────────────── + printf 'echo ===SECTION===numa\n'; sleep 0.1 + printf 'info numa\n'; sleep 0.3 + printf 'echo ===SECTION===hpet\n'; sleep 0.1 + printf 'info hpet\n'; sleep 0.3 + printf 'echo ===SECTION===qomtree\n'; sleep 0.1 + printf 'info qom-tree\n'; sleep 0.3 + + # ── Device summaries ───────────────────────────────── + printf 'echo ===SECTION===chardev\n'; sleep 0.1 + printf 'info chardev\n'; sleep 0.3 + printf 'echo ===SECTION===block\n'; sleep 0.1 + printf 'info block\n'; sleep 0.3 + printf 'echo ===SECTION===net\n'; sleep 0.1 + printf 'info networking\n'; sleep 0.3 + ) > "$MON_IN" 2>/dev/null & + MON_WRITER_PID=$! + printf '%s\n' "$MON_WRITER_PID" > /tmp/hw_mon_writer.pid + + hiperiso_log "hw_collect: monitor FIFO reader (PID $MON_READER_PID)" + hiperiso_log "hw_collect: monitor FIFO writer (PID $MON_WRITER_PID)" +} + +# ── Mode: post ─────────────────────────────────────────────── +hw_collect_post() { + _raw="${HW_DIR}/qemu_monitor_raw.txt" + + if [ ! -f "$_raw" ] || [ ! -s "$_raw" ]; then + hiperiso_log "hw_collect: no QEMU monitor data (raw file missing or empty)" + hw_cleanup_procs + return 1 + fi + + hiperiso_log "hw_collect: parsing QEMU monitor output..." + + # Split raw dump into per-section files using echo markers + # injected by the writer. Each ===SECTION===xxx line triggers + # a section change; subsequent output goes to that file. + _awk_script=' + BEGIN { section = "header"; } + /===SECTION===version/ { section = "version"; next; } + /===SECTION===qtree/ { section = "qtree"; next; } + /===SECTION===pci/ { section = "pci"; next; } + /===SECTION===cpuid/ { section = "cpuid"; next; } + /===SECTION===memmap_flat/ { section = "memmap_flat"; next; } + /===SECTION===memmap/ { section = "memmap"; next; } + /===SECTION===smbios/ { section = "smbios"; next; } + /===SECTION===registers/ { section = "registers"; next; } + /===SECTION===tlb/ { section = "tlb"; next; } + /===SECTION===ioapic/ { section = "ioapic"; next; } + /===SECTION===lapic/ { section = "lapic"; next; } + /===SECTION===irq/ { section = "irq"; next; } + /===SECTION===numa/ { section = "numa"; next; } + /===SECTION===hpet/ { section = "hpet"; next; } + /===SECTION===qomtree/ { section = "qomtree"; next; } + /===SECTION===chardev/ { section = "chardev"; next; } + /===SECTION===block/ { section = "block"; next; } + /===SECTION===net/ { section = "net"; next; } + { + if (section != "header") + print > "'"$HW_DIR"'/qemu_" section ".txt"; + } + ' + + awk "$_awk_script" "$_raw" 2>/dev/null + + for _f in \ + qemu_version qemu_qtree qemu_pci qemu_cpuid \ + qemu_memmap qemu_memmap_flat qemu_smbios \ + qemu_registers qemu_tlb qemu_ioapic qemu_lapic \ + qemu_irq qemu_numa qemu_hpet qemu_qomtree \ + qemu_chardev qemu_block qemu_net; do + _fp="${HW_DIR}/${_f}.txt" + if [ -f "$_fp" ] && [ ! -s "$_fp" ]; then + rm -f "$_fp" + fi + done + + # Generate PCI summary JSON from qemu_pci.txt + hw_gen_pci_json + + _count=$(ls "$HW_DIR"/qemu_*.txt "$HW_DIR"/*.json 2>/dev/null | wc -l) + hiperiso_log "hw_collect: parsed $_count QEMU + JSON files" + + hw_cleanup_procs + + hiperiso_log "hw_collect: inventory files:" + ls -la "$HW_DIR"/ 2>/dev/null >> "${LOG_DIR}/env.txt" +} + +# ── PCI summary JSON ───────────────────────────────────────── +# Parse qemu_pci.txt to extract structured PCI device data. +hw_gen_pci_json() { + _pci="${HW_DIR}/qemu_pci.txt" + _out="${HW_DIR}/pci_summary.json" + [ -f "$_pci" ] || return 0 + + { + printf '{\n' + printf ' "devices": [\n' + + awk ' + /^Bus / { + if (found) printf " },\n" + found = 1 + bus = $2; gsub(/,/, "", bus) + dev = $4; gsub(/,/, "", dev) + fn = $6; gsub(/:/, "", fn) + printf " {\"bus\": %s, \"device\": %s, \"function\": %s", bus, dev, fn + } + /vendor_id = / { + gsub(/.*= /, "", $0) + printf ", \"vendor_id\": \"%s\"", $NF + } + /device_id = / { + gsub(/.*= /, "", $0) + printf ", \"device_id\": \"%s\"", $NF + } + /class = / { + gsub(/.*= /, "", $0) + printf ", \"class\": \"%s\"", $NF + } + /IRQ / { + _irq = $2; gsub(/\./, "", _irq) + printf ", \"irq\": \"%s\"", _irq + } + END { if (found) printf " }"; printf "\n" } + ' "$_pci" 2>/dev/null + + printf ' ]\n' + printf '}\n' + } > "$_out" 2>/dev/null + + if [ ! -s "$_out" ]; then + rm -f "$_out" + fi +} + +# ── Process cleanup ────────────────────────────────────────── +hw_cleanup_procs() { + rm -f "$MON_IN" "$MON_OUT" 2>/dev/null + for _pidfile in /tmp/hw_mon_reader.pid /tmp/hw_mon_writer.pid; do + if [ -f "$_pidfile" ]; then + _pid=$(cat "$_pidfile" 2>/dev/null) + if [ -n "$_pid" ] && kill -0 "$_pid" 2>/dev/null; then + kill "$_pid" 2>/dev/null + fi + rm -f "$_pidfile" + fi + done +} + +# ── Main ───────────────────────────────────────────────────── +case "${1:-}" in + pre) hw_collect_pre ;; + monitor) hw_setup_monitor_fifo ;; + post) hw_collect_post ;; + *) + printf 'Usage: hw_collect.sh {pre|monitor|post}\n' >&2 + exit 1 + ;; +esac diff --git a/host/initramfs/init b/host/initramfs/init new file mode 100755 index 0000000..6fc0525 --- /dev/null +++ b/host/initramfs/init @@ -0,0 +1,270 @@ +#!/bin/sh +# ============================================================ +# init -- PID 1 for the hiperiso hypervisor host initramfs +# +# Boots a selected ISO as a KVM guest with full bootlogging. +# Kernel cmdline parameters (see INTERFACES.sh): +# hiperiso_iso="/ISOs/ubuntu.iso" +# hiperiso_log="/hiperiso/logs/ubuntu/" +# hiperiso_trace_level="standard|detailed|full|none" +# hiperiso_ram=2048 hiperiso_cpus=2 +# hiperiso_display="none|gtk|vnc" +# hiperiso_vga="std|none|virtio" +# hiperiso_fallback="1" +# ============================================================ + +PATH="/usr/bin:/bin:/usr/sbin:/sbin" +export PATH + +# ── Step 1: Mount essential virtual filesystems ────────────── +mkdir -p /proc /sys /dev /tmp /run + +mount -t proc proc /proc 2>/dev/null +mount -t sysfs sysfs /sys 2>/dev/null +mount -t devtmpfs devtmpfs /dev 2>/dev/null || mount -t tmpfs tmpfs /dev 2>/dev/null +mount -t tmpfs tmpfs /tmp 2>/dev/null +mount -t tmpfs tmpfs /run 2>/dev/null + +. /hiperiso-lib.sh + +_INIT_UPTIME=$(cut -d' ' -f1 /proc/uptime 2>/dev/null) + +printf '\n' +printf '======================================================\n' +printf ' hiperiso hypervisor host -- KVM ISO boot logger \n' +printf '======================================================\n' +printf '\n' + +# ── Step 2: Parse kernel command line ──────────────────────── +hiperiso_parse_cmdline + +if [ -z "$HIPERISO_ISO_PATH" ]; then + printf '[hiperiso] FATAL: hiperiso_iso= not set on kernel command line\n' + printf '[hiperiso] Dropping to recovery shell\n' + exec /bin/sh +fi + +hiperiso_log "ISO path: $HIPERISO_ISO_PATH" +hiperiso_log "Log dir: $HIPERISO_LOG_DIR" +hiperiso_log "Trace: ${HIPERISO_TRACE_LEVEL:-standard}" +hiperiso_log "RAM: ${HIPERISO_GUEST_RAM:-2048} MB" +hiperiso_log "CPUs: ${HIPERISO_GUEST_CPUS:-2}" + +# ── Step 3: Find and mount the USB data partition ──────────── +DATA_PART=$(hiperiso_find_usb_partition) +if [ -z "$DATA_PART" ]; then + hiperiso_log "FATAL: Could not find hiperiso USB data partition" + hiperiso_log "Scanned: blkid label, /dev/sd[abcd]2, sysfs" + exec /bin/sh +fi +hiperiso_log "Data partition: $DATA_PART" + +mkdir -p "$DATA_MOUNT" +_mounted=0 +for _fstype in exfat ntfs3 ntfs vfat ext4 ext3 ext2; do + if mount -t "$_fstype" "$DATA_PART" "$DATA_MOUNT" 2>/dev/null; then + hiperiso_log "Mounted $DATA_PART at $DATA_MOUNT (type: $_fstype)" + _mounted=1 + break + fi +done + +if [ "$_mounted" -eq 0 ]; then + if mount "$DATA_PART" "$DATA_MOUNT" 2>/dev/null; then + hiperiso_log "Mounted $DATA_PART at $DATA_MOUNT (auto-detected)" + _mounted=1 + fi +fi + +if [ "$_mounted" -eq 0 ]; then + hiperiso_log "FATAL: Failed to mount $DATA_PART" + exec /bin/sh +fi + +# ── Step 4: Mount the EFI System Partition (for OVMF) ──────── +ESP_PART=$(printf '%s' "$DATA_PART" | sed 's/1$/2/') + +if [ ! -e "$ESP_PART" ]; then + for _d in /dev/sda2 /dev/sdb2 /dev/sdc2 /dev/sdd2 /dev/sde2 /dev/sdf2 /dev/nvme0n1p2 /dev/nvme1n1p2 /dev/mmcblk0p2; do + if [ -e "$_d" ]; then + ESP_PART="$_d" + break + fi + done +fi + +mkdir -p "$EFI_MOUNT" +if mount -t vfat "$ESP_PART" "$EFI_MOUNT" 2>/dev/null; then + hiperiso_log "Mounted ESP $ESP_PART at $EFI_MOUNT" +else + hiperiso_log "WARNING: Could not mount ESP $ESP_PART -- OVMF may be unavailable" +fi + +# ── Step 5: Resolve absolute paths and create log dir ──────── +# Derive a unique per-session directory with UTC timestamp so +# every boot gets its own log set (no overwriting). +HIPERISO_LOG_DIR=$(hiperiso_derive_session_dir "$HIPERISO_LOG_DIR") +export HIPERISO_LOG_DIR + +LOG_DIR="${DATA_MOUNT}${HIPERISO_LOG_DIR}" +ISO_PATH="${DATA_MOUNT}${HIPERISO_ISO_PATH}" +OVMF_FULL_PATH="${EFI_MOUNT}${OVMF_PATH}" + +# Collision-safe: if session dir already exists (e.g. same-second boot), +# append an incrementing suffix so logs are never overwritten. +if [ -d "$LOG_DIR" ]; then + _i=2 + while [ -d "${LOG_DIR%/}_${_i}" ]; do + _i=$((_i + 1)) + done + LOG_DIR="${LOG_DIR%/}_${_i}/" + HIPERISO_LOG_DIR="${HIPERISO_LOG_DIR%/}_${_i}/" +fi +mkdir -p "$LOG_DIR" + +export LOG_DIR ISO_PATH OVMF_FULL_PATH EFI_MOUNT DATA_MOUNT + +# ── Record early timing marks retroactively ────────────────── +# These phases elapsed before LOG_DIR existed; capture them now +# using the uptime values saved at each checkpoint. +printf 'kernel_start\t%s\t%s\n' "${_INIT_UPTIME:-0}" "$(hiperiso_utc_iso)" >> "${LOG_DIR}/timing.dat" +hiperiso_timing_mark "session_created" + +hiperiso_log "Session dir: $HIPERISO_LOG_DIR" + +# ── Step 6: Write session metadata and environment snapshot ── +hiperiso_write_session_meta "started" +hiperiso_write_status "running" "mounted" +hiperiso_write_env +hiperiso_timing_mark "env_written" + +# ── Step 7: KVM detection ──────────────────────────────────── +KVM_RESULT=$(/kvm_check.sh) +printf 'KVM_CHECK_RESULT=%s\n' "$KVM_RESULT" >> "${LOG_DIR}/env.txt" + +case "$KVM_RESULT" in + KVM_OK) + hiperiso_log "KVM available -- proceeding with hypervisor boot" + export KVM_AVAILABLE=1 + export KVM_STATUS="available" + hiperiso_write_status "running" "kvm_ok" + ;; + KVM_FAIL:*) + hiperiso_log "KVM check failed: ${KVM_RESULT#KVM_FAIL:}" + export KVM_AVAILABLE=0 + export KVM_STATUS="unavailable" + if [ "$HIPERISO_FALLBACK" != "1" ]; then + hiperiso_write_status "failed" "fallback_no_kvm" + /fallback_boot.sh + exec /bin/sh + fi + hiperiso_log "Fallback forced -- continuing with TCG emulation" + hiperiso_write_status "running" "tcg_emulation" + ;; +esac +hiperiso_timing_mark "kvm_checked" + +# ── Step 8: Verify ISO exists ──────────────────────────────── +if [ ! -f "$ISO_PATH" ]; then + hiperiso_log "FATAL: ISO file not found: $ISO_PATH" + hiperiso_log "Contents of $DATA_MOUNT:" + ls -la "$DATA_MOUNT" 2>/dev/null >> "${LOG_DIR}/env.txt" + hiperiso_write_status "failed" "iso_not_found" + hiperiso_run_report + hiperiso_finalize_session "failed" "" "iso_not_found" + exec /bin/sh +fi +hiperiso_log "ISO found: $ISO_PATH" +hiperiso_timing_mark "iso_verified" + +# ── Step 8b: Run conf_replace plugin (if configured) ───────── +_modified_iso=$(sh /conf_replace.sh 2>"${LOG_DIR}/conf_replace.log" || true) +if [ -n "$_modified_iso" ] && [ -f "$_modified_iso" ]; then + ISO_PATH="$_modified_iso" + export ISO_PATH + hiperiso_log "Using modified ISO: $ISO_PATH" +fi + +# ── Step 9: Start log flush daemon ─────────────────────────── +rm -f /tmp/hiperiso_done +/log_flush.sh "$LOG_DIR" & +LOG_FLUSH_PID=$! +hiperiso_log "Log flush daemon started (PID $LOG_FLUSH_PID)" + +hiperiso_write_status "running" "launching_qemu" +hiperiso_timing_mark "qemu_launching" + +# ── Step 10: Launch QEMU ───────────────────────────────────── +/qemu_launch.sh +QEMU_EXIT_CODE=$? + +# ── Step 11: Flush remaining logs ──────────────────────────── +touch /tmp/hiperiso_done +hiperiso_timing_mark "qemu_returned" + +_wait=0 +while kill -0 "$LOG_FLUSH_PID" 2>/dev/null; do + _wait=$((_wait + 1)) + if [ "$_wait" -gt 10 ]; then + kill "$LOG_FLUSH_PID" 2>/dev/null + break + fi + sleep 1 +done + +sync +printf 'QEMU_EXIT_CODE=%s\n' "$QEMU_EXIT_CODE" >> "${LOG_DIR}/env.txt" + +hiperiso_write_status "running" "qemu_exited" "$QEMU_EXIT_CODE" + +if [ "$QEMU_EXIT_CODE" -eq 0 ]; then + _session_status="complete" + _session_stage="session_complete" +else + _session_status="failed" + _session_stage="qemu_error" +fi + +hiperiso_run_report +hiperiso_timing_mark "report_done" +hiperiso_finalize_session "$_session_status" "$QEMU_EXIT_CODE" "$_session_stage" +hiperiso_timing_mark "session_finalized" +hiperiso_timing_write_json + +# ── Step 12: Print session summary ─────────────────────────── +printf '\n' +printf '======================================================\n' +printf ' hiperiso boot session complete \n' +printf '======================================================\n' +printf ' ISO: %s\n' "$HIPERISO_ISO_PATH" +printf ' Session: %s\n' "$(hiperiso_session_id)" +printf ' QEMU exit: %s\n' "$QEMU_EXIT_CODE" +printf ' Logs: %s\n' "$LOG_DIR" +printf '======================================================\n' +printf '\n' +printf 'Log files:\n' +ls -la "$LOG_DIR" 2>/dev/null +printf '\n' + +# ── Step 13: Offer reboot ──────────────────────────────────── +printf 'Press ENTER to reboot, or type "shell" for recovery: ' +read -r _response + +case "$_response" in + shell|sh) + printf '\nDropping to recovery shell...\n' + exec /bin/sh + ;; +esac + +printf '\nRebooting...\n' +sync + +if command -v reboot >/dev/null 2>&1; then + reboot -f +else + printf 'b' > /proc/sysrq-trigger 2>/dev/null +fi + +# Kernel will panic if PID 1 exits -- but reboot should have fired +exec /bin/sh diff --git a/host/initramfs/kvm_check.sh b/host/initramfs/kvm_check.sh new file mode 100755 index 0000000..5475f55 --- /dev/null +++ b/host/initramfs/kvm_check.sh @@ -0,0 +1,82 @@ +#!/bin/sh +# ============================================================ +# kvm_check.sh -- Detect KVM / hardware virtualization support +# Output: "KVM_OK" on success +# "KVM_FAIL:" on failure +# Exit: 0 on success, 1 on failure +# ============================================================ + +# ── Helper: attempt to load a kernel module ────────────────── +_try_modprobe() { + if command -v modprobe >/dev/null 2>&1; then + modprobe "$1" 2>/dev/null + return $? + fi + if command -v insmod >/dev/null 2>&1; then + # Fallback: try insmod with common module paths + for _modpath in \ + "/lib/modules/$(uname -r 2>/dev/null)/kernel/arch/x86/kvm/$1.ko" \ + "/lib/modules/$(uname -r 2>/dev/null)/$1.ko" \ + "/$1.ko"; do + if [ -f "$_modpath" ]; then + insmod "$_modpath" 2>/dev/null && return 0 + fi + done + fi + return 1 +} + +# ── Check 1: /dev/kvm already exists ───────────────────────── +if [ -c /dev/kvm ]; then + printf 'KVM_OK\n' + exit 0 +fi + +# ── Check 2: CPU supports virtualization ───────────────────── +# vmx = Intel VT-x, svm = AMD-V +if ! grep -q -E '(vmx|svm)' /proc/cpuinfo 2>/dev/null; then + printf 'KVM_FAIL:no_hw_virt (CPU lacks vmx/svm flags -- check BIOS/UEFI settings)\n' + exit 1 +fi + +# ── Determine CPU vendor for correct KVM module ────────────── +_cpu_vendor="" +if [ -f /proc/cpuinfo ]; then + _cpu_vendor=$(grep -m1 '^vendor_id' /proc/cpuinfo 2>/dev/null \ + | cut -d: -f2 | tr -d ' ') +fi + +# ── Check 3: Load KVM kernel modules ───────────────────────── +_try_modprobe kvm + +case "$_cpu_vendor" in + GenuineIntel) + _try_modprobe kvm-intel + ;; + AuthenticAMD) + _try_modprobe kvm-amd + ;; + *) + # Unknown vendor -- try both + _try_modprobe kvm-intel 2>/dev/null || _try_modprobe kvm-amd 2>/dev/null + ;; +esac + +# Give devtmpfs/udev a moment to create the device node +sleep 1 + +# ── Check 4: Verify /dev/kvm now exists ────────────────────── +if [ ! -e /dev/kvm ]; then + printf 'KVM_FAIL:no_dev_kvm (modules may not have loaded -- dmesg for details)\n' + exit 1 +fi + +# ── Check 5: Verify /dev/kvm is a char device ──────────────── +if [ ! -c /dev/kvm ]; then + printf 'KVM_FAIL:dev_kvm_not_char_device\n' + exit 1 +fi + +# ── Success ────────────────────────────────────────────────── +printf 'KVM_OK\n' +exit 0 diff --git a/host/initramfs/log_flush.sh b/host/initramfs/log_flush.sh new file mode 100755 index 0000000..072b560 --- /dev/null +++ b/host/initramfs/log_flush.sh @@ -0,0 +1,66 @@ +#!/bin/sh +# ============================================================ +# log_flush.sh -- Async ring-buffer to USB flush daemon +# +# Runs in the background, calling sync() on the log directory +# every 5 seconds so boot logs survive even on a hard reset. +# Writes a periodic heartbeat so agents can detect hung sessions. +# Exits cleanly when the /tmp/hiperiso_done sentinel appears, +# writing a final flush marker. +# +# Usage: log_flush.sh +# ============================================================ + +LOG_DIR="${1:-}" +INTERVAL=5 +SENTINEL="/tmp/hiperiso_done" +HEARTBEAT_INTERVAL=12 + +if [ -z "$LOG_DIR" ]; then + printf '[log_flush] FATAL: no log directory specified\n' + printf '[log_flush] usage: log_flush.sh \n' + exit 1 +fi + +if [ ! -d "$LOG_DIR" ]; then + printf '[log_flush] FATAL: log directory does not exist: %s\n' "$LOG_DIR" + exit 1 +fi + +printf '[log_flush] Started -- syncing %s every %ds\n' "$LOG_DIR" "$INTERVAL" + +_flush_count=0 + +while true; do + # ── Check sentinel: exit gracefully ────────────────────── + if [ -f "$SENTINEL" ]; then + printf '[log_flush] Sentinel detected, performing final sync...\n' + sync + printf '[log_flush] Exiting after %d sync cycles\n' "$_flush_count" + exit 0 + fi + + # ── Check if log directory still accessible ────────────── + # (USB might have been unplugged) + if [ ! -d "$LOG_DIR" ]; then + printf '[log_flush] WARNING: log dir vanished (USB unplugged?) -- waiting for sentinel\n' + sleep "$INTERVAL" + continue + fi + + # ── Flush dirty pages to the USB device ────────────────── + sync 2>/dev/null + + _flush_count=$(( _flush_count + 1 )) + + # ── Write periodic heartbeat ───────────────────────────── + if [ $(( _flush_count % HEARTBEAT_INTERVAL )) -eq 0 ]; then + _up=$(cut -d' ' -f1 /proc/uptime 2>/dev/null) + printf 'cycle=%d uptime=%s\n' "$_flush_count" "${_up:-0}" \ + > "${LOG_DIR}/HEARTBEAT" 2>/dev/null + printf '[log_flush] cycle %d (%d min) -- OK\n' \ + "$_flush_count" "$(( _flush_count / HEARTBEAT_INTERVAL ))" + fi + + sleep "$INTERVAL" +done diff --git a/host/initramfs/make_floppy.sh b/host/initramfs/make_floppy.sh new file mode 100644 index 0000000..9759c71 --- /dev/null +++ b/host/initramfs/make_floppy.sh @@ -0,0 +1,67 @@ +#!/bin/sh +# make_floppy.sh -- Create a virtual floppy disk image with given files +# Usage: make_floppy.sh [file2] ... +# POSIX sh compatible (busybox/ash/dash) + +set -eu + +OUTPUT="${1:-}" +shift || true + +if [ -z "$OUTPUT" ] || [ $# -eq 0 ]; then + echo "Usage: make_floppy.sh [file2] ..." >&2 + exit 1 +fi + +. /hiperiso-lib.sh + +_total_size=0 +for f in "$@"; do + if [ -f "$f" ]; then + _sz=$(wc -c < "$f" 2>/dev/null || echo 0) + _total_size=$((_total_size + _sz)) + fi +done + +if [ "$_total_size" -gt 2621440 ]; then + _floppy_size=2880 +else + _floppy_size=1440 +fi + +dd if=/dev/zero of="$OUTPUT" bs=1024 count=$_floppy_size 2>/dev/null + +if command -v mkfs.vfat >/dev/null 2>&1; then + mkfs.vfat -F 12 -n HIPERISO "$OUTPUT" >/dev/null 2>&1 +elif command -v mkdosfs >/dev/null 2>&1; then + mkdosfs -F 12 -n HIPERISO "$OUTPUT" >/dev/null 2>&1 +else + hiperiso_log "WARNING: no FAT formatter available, floppy image is blank" + exit 1 +fi + +_mnt=$(mktemp -d /tmp/floppy.XXXXXX 2>/dev/null || echo /tmp/floppy_mnt) +mkdir -p "$_mnt" + +if mount -o loop "$OUTPUT" "$_mnt" 2>/dev/null; then + for f in "$@"; do + if [ -f "$f" ]; then + cp "$f" "$_mnt/" 2>/dev/null || true + fi + done + sync + umount "$_mnt" 2>/dev/null || true +else + if command -v mcopy >/dev/null 2>&1; then + for f in "$@"; do + if [ -f "$f" ]; then + mcopy -i "$OUTPUT" "$f" ::/ 2>/dev/null || true + fi + done + else + hiperiso_log "WARNING: cannot mount loop or use mtools, floppy is empty" + fi +fi + +rmdir "$_mnt" 2>/dev/null || true +exit 0 diff --git a/host/initramfs/qemu_launch.sh b/host/initramfs/qemu_launch.sh new file mode 100755 index 0000000..5777270 --- /dev/null +++ b/host/initramfs/qemu_launch.sh @@ -0,0 +1,296 @@ +#!/bin/sh +# ============================================================ +# qemu_launch.sh -- Build QEMU arguments and launch guest VM +# +# Reads from environment (exported by init): +# ISO_PATH -- absolute path to the ISO on mounted USB +# LOG_DIR -- absolute path to the log directory +# OVMF_FULL_PATH -- absolute path to OVMF firmware on ESP +# EFI_MOUNT -- ESP mount point +# HIPERISO_TRACE_LEVEL -- standard|detailed|full|none +# HIPERISO_GUEST_RAM -- guest RAM in MB +# HIPERISO_GUEST_CPUS -- guest vCPU count +# HIPERISO_DISPLAY -- none|gtk|vnc +# HIPERISO_VGA -- none|std|virtio +# KVM_AVAILABLE -- 1 if KVM detected, 0 otherwise +# +# Exit codes: +# 0 QEMU ran and exited cleanly +# 2 ISO file not found +# 3 OVMF firmware not found +# 4 Insufficient RAM +# 127 QEMU binary missing +# other QEMU's own exit code +# ============================================================ + +. /hiperiso-lib.sh + +QEMU_BIN="/usr/bin/qemu-system-x86_64" + +ISO_PATH="${ISO_PATH:?ISO_PATH not set}" +LOG_DIR="${LOG_DIR:?LOG_DIR not set}" + +trap 'rm -f /tmp/hw_mon.in /tmp/hw_mon.out 2>/dev/null; for _pf in /tmp/hw_mon_reader.pid /tmp/hw_mon_writer.pid; do [ -f "$_pf" ] && kill "$(cat "$_pf" 2>/dev/null)" 2>/dev/null && rm -f "$_pf"; done' INT TERM + +# ── Validate QEMU binary ───────────────────────────────────── +if [ ! -x "$QEMU_BIN" ]; then + hiperiso_log "FATAL: QEMU binary not found at $QEMU_BIN" + exit 127 +fi + +# ── Validate ISO exists ────────────────────────────────────── +if [ ! -f "$ISO_PATH" ]; then + hiperiso_log "FATAL: ISO file not found: $ISO_PATH" + exit 2 +fi + +# ── Resolve OVMF firmware ──────────────────────────────────── +OVMF_FILE="" +for _candidate in \ + "${OVMF_FULL_PATH:-}" \ + "${EFI_MOUNT:-/mnt/efi}${OVMF_PATH}" \ + "/EFI/hiperiso/OVMF.fd" \ + "/usr/share/hiperiso/OVMF.fd" \ + "/usr/share/OVMF/OVMF_CODE.fd"; do + if [ -n "$_candidate" ] && [ -f "$_candidate" ]; then + OVMF_FILE="$_candidate" + break + fi +done + +if [ -z "$OVMF_FILE" ]; then + hiperiso_log "FATAL: OVMF firmware not found" + hiperiso_log " Searched ESP, initramfs, /usr/share/hiperiso/, /usr/share/OVMF/" + exit 3 +fi +hiperiso_log "Using OVMF firmware: $OVMF_FILE" + +# ── Plugin: Secure Boot OVMF ───────────────────────────────── +if [ "${HIPERISO_SECURE_BOOT:-0}" = "1" ]; then + _ovmf_secure="${EFI_MOUNT:-/mnt/efi}/EFI/hiperiso/OVMF_SECURE.fd" + if [ -f "$_ovmf_secure" ]; then + hiperiso_log "Plugin: Secure Boot requested, switching firmware to $_ovmf_secure" + OVMF_FILE="$_ovmf_secure" + else + hiperiso_log "WARNING: Secure Boot requested but firmware not found: $_ovmf_secure" + fi +fi + +# ── Resolve trace events file ──────────────────────────────── +_trace_level="${HIPERISO_TRACE_LEVEL:-standard}" +TRACE_EVENTS_FILE="" + +if [ "$_trace_level" != "none" ]; then + for _trace_file in \ + "${EFI_MOUNT:-/mnt/efi}${TRACE_EVENTS_DIR}/trace-${_trace_level}.events" \ + "/EFI/hiperiso/trace/trace-${_trace_level}.events"; do + if [ -f "$_trace_file" ]; then + TRACE_EVENTS_FILE="$_trace_file" + hiperiso_log "Trace: level=${_trace_level} events=${TRACE_EVENTS_FILE}" + break + fi + done + if [ -z "$TRACE_EVENTS_FILE" ]; then + hiperiso_log "WARNING: trace events file not found for level=${_trace_level}" + hiperiso_log " Tracing disabled for this session" + fi +fi + +# ── Verify sufficient RAM ──────────────────────────────────── +_guest_ram="${HIPERISO_GUEST_RAM:-2048}" + +if ! hiperiso_check_ram "$_guest_ram"; then + hiperiso_log "FATAL: Insufficient RAM for guest" + hiperiso_log " Requested ${_guest_ram}MB guest + 512MB host overhead" + hiperiso_log " Available: $(awk '/^MemTotal:/ {printf "%d", $2/1024}' /proc/meminfo 2>/dev/null)MB" + exit 4 +fi + +# ── Plugin: memdisk boot mode ──────────────────────────────── +if [ "${HIPERISO_BOOT_MODE:-normal}" = "memdisk" ]; then + hiperiso_log "Plugin: memdisk boot mode -- loading ISO into RAM" + cat "$ISO_PATH" > /tmp/iso_memdisk + ISO_PATH="/tmp/iso_memdisk" +fi + +# ── Collect host hardware inventory ────────────────────────── +hiperiso_timing_mark "hw_collect_pre" +/hw_collect.sh pre + +# ── Build QEMU argument list ───────────────────────────────── +hiperiso_timing_mark "qemu_args_built" +_guest_cpus="${HIPERISO_GUEST_CPUS:-2}" +_display="${HIPERISO_DISPLAY:-none}" +_vga="${HIPERISO_VGA:-std}" + +if [ "${KVM_AVAILABLE:-1}" = "1" ]; then + _machine="q35,accel=kvm" + _cpu_model="host" +else + _machine="q35" + _cpu_model="qemu64" + hiperiso_log "WARNING: KVM unavailable -- using TCG software emulation (very slow)" +fi + +_cpu_arg="$_cpu_model" +if [ -n "${HIPERISO_CPU_FEATURES:-}" ]; then + hiperiso_log "Plugin: CPU features ${HIPERISO_CPU_FEATURES}" + _oldifs="$IFS" + IFS=',' + for _feat in $HIPERISO_CPU_FEATURES; do + _cpu_arg="${_cpu_arg},+${_feat}" + done + IFS="$_oldifs" +fi + +set -- -machine "$_machine" -cpu "$_cpu_arg" + +_OVMF_TMP="/tmp/OVMF.fd" +if ! cp "$OVMF_FILE" "$_OVMF_TMP" 2>/dev/null; then + hiperiso_log "FATAL: Failed to copy OVMF firmware ($OVMF_FILE) to $_OVMF_TMP" + exit 3 +fi + +set -- "$@" \ + -m "$_guest_ram" \ + -smp "$_guest_cpus" \ + -drive "file=${ISO_PATH},if=none,id=cd0,format=raw,media=cdrom,readonly=on" \ + -device ahci,id=ahci0 \ + -device ide-cd,drive=cd0,bus=ahci0.0,bootindex=1 \ + -drive "if=pflash,format=raw,readonly=on,file=${OVMF_FILE}" \ + -drive "if=pflash,format=raw,file=${_OVMF_TMP}" \ + -serial "file:${LOG_DIR}/serial.log" \ + -display "$_display" \ + -vga "$_vga" \ + -monitor "unix:${LOG_DIR}/monitor.sock,server,nowait" \ + -nodefaults \ + -no-reboot + +if [ -n "$TRACE_EVENTS_FILE" ]; then + set -- "$@" \ + -trace "events=${TRACE_EVENTS_FILE},file=${LOG_DIR}/trace.bin" +fi + +# ── Plugin: persistence .dat ───────────────────────────────── +if [ -n "${HIPERISO_PERSISTENCE:-}" ]; then + hiperiso_log "Plugin: persistence file ${HIPERISO_PERSISTENCE}" + set -- "$@" \ + -drive "file=${DATA_MOUNT:-}${HIPERISO_PERSISTENCE},if=none,id=persist0,format=raw" \ + -device "virtio-blk-pci,drive=persist0" +fi + +# ── Plugin: auto-install script ────────────────────────────── +_floppy_drives="" +_floppy_devices="" +if [ -n "${HIPERISO_AUTO_INSTALL:-}" ]; then + hiperiso_log "Plugin: auto-install script ${HIPERISO_AUTO_INSTALL}" + /make_floppy.sh /tmp/auto_install.img "${DATA_MOUNT:-}${HIPERISO_AUTO_INSTALL}" + _floppy_drives="$_floppy_drives -drive file=/tmp/auto_install.img,if=none,id=floppy0,format=raw" + _floppy_devices="driveA=floppy0" +fi + +# ── Plugin: Driver Update Disk ─────────────────────────────── +if [ -n "${HIPERISO_DUD:-}" ]; then + hiperiso_log "Plugin: Driver Update Disk ${HIPERISO_DUD}" + set -- "$@" \ + -drive "file=${DATA_MOUNT:-}${HIPERISO_DUD},if=none,id=dud0,format=raw,media=cdrom,readonly=on" \ + -device "ide-cd,drive=dud0,bus=ahci0.1" +fi + +# ── Plugin: injection archive ──────────────────────────────── +if [ -n "${HIPERISO_INJECTION:-}" ]; then + hiperiso_log "Plugin: injection archive ${HIPERISO_INJECTION}" + mkdir -p /tmp/injection + tar xzf "${DATA_MOUNT:-}${HIPERISO_INJECTION}" -C /tmp/injection + /make_floppy.sh /tmp/injection.img /tmp/injection/* + if [ -n "$_floppy_devices" ]; then + _floppy_drives="$_floppy_drives -drive file=/tmp/injection.img,if=none,id=floppy1,format=raw" + _floppy_devices="$_floppy_devices,driveB=floppy1" + else + _floppy_drives="$_floppy_drives -drive file=/tmp/injection.img,if=none,id=floppy1,format=raw" + _floppy_devices="driveA=floppy1" + fi +fi + +# Single isa-fdc controller for all floppies (QEMU allows only one) +if [ -n "$_floppy_devices" ]; then + set -- "$@" $_floppy_drives -device "isa-fdc,$_floppy_devices" +fi + +# ── Plugin: virtual TPM ────────────────────────────────────── +if [ "${HIPERISO_TPM:-0}" = "1" ]; then + if command -v swtpm >/dev/null 2>&1; then + hiperiso_log "Plugin: virtual TPM (swtpm) starting" + mkdir -p /tmp/tpmstate + swtpm socket --tpmstate dir=/tmp/tpmstate \ + --ctrl type=unixio,path=/tmp/swtpm.sock \ + --tpm2 --daemon + set -- "$@" \ + -chardev "socket,id=tpm0,path=/tmp/swtpm.sock" \ + -tpmdev "emulator,id=tpm0,chardev=tpm0" \ + -device "tpm-crb,tpmdev=tpm0" + else + hiperiso_log "WARNING: TPM requested but swtpm binary not found" + fi +fi + +# ── Plugin: network packet capture ─────────────────────────── +if [ "${HIPERISO_NET_DUMP:-0}" = "1" ]; then + hiperiso_log "Plugin: network pcap capture enabled" + set -- "$@" \ + -netdev "user,id=net0" \ + -device "virtio-net-pci,netdev=net0" \ + -object "filter-dump,id=netdump,netdev=net0,file=${LOG_DIR}/network.pcap" +fi + +# ── Hardware inventory: monitor FIFO + pipe chardev ────────── +# Sets up /tmp/hw_mon.{in,out} FIFOs and launches background +# reader/writer processes. The pipe chardev lets us send HMP +# commands (info qtree, info pci, etc.) to QEMU while it runs. +hiperiso_timing_mark "hw_monitor_setup" +/hw_collect.sh monitor +set -- "$@" \ + -chardev "pipe,id=hwmon,path=/tmp/hw_mon" \ + -mon "chardev=hwmon,mode=readline" + +# ── Save the exact command line ────────────────────────────── +{ + printf '%s' "$QEMU_BIN" + for _arg in "$@"; do + printf ' %s' "$_arg" + done + printf '\n' +} > "${LOG_DIR}/qemu.cmdline" + +hiperiso_log "QEMU command line saved to ${LOG_DIR}/qemu.cmdline" + +# ── Launch QEMU ────────────────────────────────────────────── +hiperiso_timing_mark "qemu_started" +hiperiso_log "Starting QEMU..." +hiperiso_log " Guest: ${_guest_ram}MB RAM, ${_guest_cpus} vCPU(s)" +hiperiso_log " ISO: $ISO_PATH" +hiperiso_log " Video: display=${_display} vga=${_vga}" + +"$QEMU_BIN" "$@" +_qemu_exit=$? + +hiperiso_timing_mark "qemu_exited" + +# ── Parse QEMU hardware inventory ──────────────────────────── +/hw_collect.sh post + +hiperiso_log "QEMU exited with code: $_qemu_exit" + +case "$_qemu_exit" in + 0) + hiperiso_log "Guest VM shut down cleanly" + ;; + 1) + hiperiso_log "QEMU reported an error (see serial.log for details)" + ;; + *) + hiperiso_log "QEMU exited with non-zero code: $_qemu_exit" + ;; +esac + +exit "$_qemu_exit" diff --git a/host/kernel/hiperiso_defconfig b/host/kernel/hiperiso_defconfig new file mode 100644 index 0000000..fe6be15 --- /dev/null +++ b/host/kernel/hiperiso_defconfig @@ -0,0 +1,248 @@ +# +# hiperiso host kernel configuration +# ================================ +# Minimal x86_64 Linux kernel for the hiperiso hypervisor host. +# +# Goals: +# - KVM built-in (NOT module) so /dev/kvm is available with no module loader +# - USB mass-storage support (XHCI/EHCI/OHCI/UHCI) to read the USB stick +# - exFAT / NTFS3 / ext4 / FAT filesystems to read ISOs and write logs +# - Everything else stripped: no networking, no modules, no sound/wifi/DRM +# - Small enough to bzImage in ~5-8 MB +# +# Apply with: cp hiperiso_defconfig .config && make olddefconfig +# + +# ── Architecture ──────────────────────────────────────────────────────────── +CONFIG_64BIT=y +CONFIG_X86_64=y +CONFIG_X86=y +CONFIG_GENERIC_CPU=y +CONFIG_SMP=y +CONFIG_NR_CPUS=64 +CONFIG_X86_LOCAL_APIC=y +CONFIG_X86_IO_APIC=y +CONFIG_HPET_TIMER=y + +# ── Local version / hostname ──────────────────────────────────────────────── +CONFIG_LOCALVERSION="-hiperiso" +CONFIG_DEFAULT_HOSTNAME="hiperiso" + +# ── Kernel timing / scheduler (KVM depends on HIGH_RES_TIMERS) ────────────── +CONFIG_HIGH_RES_TIMERS=y +CONFIG_TICK_ONESHOT=y +CONFIG_NO_HZ_IDLE=y +CONFIG_PREEMPT_VOLUNTARY=y + +# ── Virtualization: KVM built-in (NOT module) ─────────────────────────────── +CONFIG_VIRTUALIZATION=y +CONFIG_KVM=y +CONFIG_KVM_INTEL=y +CONFIG_KVM_AMD=y +CONFIG_HAVE_KVM=y +CONFIG_HAVE_KVM_IRQCHIP=y +CONFIG_HAVE_KVM_IRQ_ROUTING=y +CONFIG_HAVE_KVM_EVENTFD=y +CONFIG_IRQ_BYPASS_MANAGER=y +CONFIG_USER_RETURN_NOTIFIER=y +CONFIG_VIRTUALIZATION_HOST=y + +# ── Modules: disabled (everything built-in) ──────────────────────────────── +# CONFIG_MODULES is not set +# CONFIG_MODULE_SIG is not set + +# ── Networking: disabled (host needs no network) ──────────────────────────── +# CONFIG_NET is not set +# CONFIG_WIRELESS is not set +# CONFIG_RFKILL is not set +# CONFIG_NET_9P is not set + +# ── PCI (host USB controllers live on PCI/PCIe) ───────────────────────────── +CONFIG_PCI=y +CONFIG_PCIEPORTBUS=y +CONFIG_PCI_MSI=y +CONFIG_PCI_IOV=y +# CONFIG_PCIEASPM is not set +# CONFIG_PCI_HISI is not set + +# ── AHCI / SATA (storage controllers on real hardware) ────────────────────── +CONFIG_ATA=y +CONFIG_SATA_AHCI=y +CONFIG_ATA_PIIX=y + +# ── Virtio (paravirtualized devices for KVM/QEMU guests) ──────────────────── +CONFIG_VIRTIO=y +CONFIG_VIRTIO_PCI=y +CONFIG_VIRTIO_BLK=y +CONFIG_VIRTIO_CONSOLE=y + +# ── ACPI (host firmware enumeration + power) ──────────────────────────────── +CONFIG_ACPI=y +CONFIG_ACPI_AC=y +CONFIG_ACPI_BUTTON=y +CONFIG_ACPI_FAN=y +CONFIG_ACPI_THERMAL=y +CONFIG_ACPI_TABLE_UPGRADE=y +# CONFIG_ACPI_DEBUG is not set + +# ── USB support (read ISOs/logs from the USB stick) ───────────────────────── +CONFIG_USB_SUPPORT=y +CONFIG_USB=y +CONFIG_USB_ANNOUNCE_NEW_DEVICES=y +CONFIG_USB_XHCI_HCD=y +CONFIG_USB_EHCI_HCD=y +CONFIG_USB_EHCI_HCD_PLATFORM=y +CONFIG_USB_OHCI_HCD=y +CONFIG_USB_OHCI_HCD_PCI=y +CONFIG_USB_UHCI_HCD=y +CONFIG_USB_STORAGE=y +CONFIG_USB_STORAGE_REALTEK=y +CONFIG_USB_STORAGE_DATAFAB=y +CONFIG_USB_DEFAULT_PERSIST=y + +# ── HID (USB keyboards/mice) ───────────────────────────────────────────────── +CONFIG_HID=y +CONFIG_HID_GENERIC=y +CONFIG_USB_HID=y + +# ── SCSI / block layer (USB mass-storage is a SCSI host) ──────────────────── +CONFIG_BLOCK=y +CONFIG_BLK_DEV=y +CONFIG_BLK_DEV_LOOP=y +CONFIG_SCSI=y +CONFIG_BLK_DEV_SD=y +CONFIG_CHR_DEV_SG=y +CONFIG_SCSI_CONSTANTS=y +CONFIG_SCSI_SCAN_ASYNC=y + +# ── Filesystems ───────────────────────────────────────────────────────────── +CONFIG_EXT4_FS=y +CONFIG_EXT4_FS_POSIX_ACL=y +CONFIG_EXT4_FS_SECURITY=y +CONFIG_FAT_FS=y +CONFIG_MSDOS_FS=y +CONFIG_VFAT_FS=y +CONFIG_FAT_DEFAULT_CODEPAGE=437 +CONFIG_FAT_DEFAULT_IOCHARSET="iso8859-1" +CONFIG_FAT_DEFAULT_UTF8=y +CONFIG_EXFAT_FS=y +CONFIG_EXFAT_DEFAULT_IOCHARSET="utf8" +CONFIG_NTFS3_FS=y +CONFIG_NTFS3_64BIT_CLUSTER=y +CONFIG_NTFS3_LZX_XPRESS=y +CONFIG_NTFS3_FS_POSIX_ACL=y +# CONFIG_NTFS_FS is not set +CONFIG_PROC_FS=y +CONFIG_PROC_SYSCTL=y +CONFIG_SYSFS=y +CONFIG_TMPFS=y +CONFIG_TMPFS_POSIX_ACL=y +CONFIG_TMPFS_XATTR=y +CONFIG_HUGETLBFS=y +CONFIG_HUGETLB_PAGE=y +CONFIG_DEVTMPFS=y +CONFIG_DEVTMPFS_MOUNT=y +CONFIG_CONFIGFS_FS=y + +# ── Initramfs (supplied externally as initramfs.cpio.gz) ──────────────────── +CONFIG_BLK_DEV_INITRD=y +CONFIG_INITRAMFS_SOURCE="" +CONFIG_RD_GZIP=y +CONFIG_DECOMPRESS_GZIP=y + +# ── Binary formats ────────────────────────────────────────────────────────── +CONFIG_BINFMT_ELF=y +CONFIG_BINFMT_SCRIPT=y +CONFIG_BINFMT_MISC=y + +# ── Console / TTY / serial (host console + early debug) ───────────────────── +CONFIG_TTY=y +CONFIG_VT=y +CONFIG_VT_CONSOLE=y +CONFIG_HW_CONSOLE=y +CONFIG_UNIX98_PTYS=y +CONFIG_LEGACY_PTYS=y +CONFIG_SERIAL_8250=y +CONFIG_SERIAL_8250_CONSOLE=y +CONFIG_SERIAL_8250_PCI=y +CONFIG_SERIAL_8250_NR_UARTS=4 +CONFIG_SERIAL_8250_RUNTIME_UARTS=4 +CONFIG_PRINTK=y +CONFIG_PRINTK_TIME=y +CONFIG_EARLY_PRINTK=y +CONFIG_DEBUG_INFO_NONE=y + +# CONFIG_EFI is not set +# CONFIG_EFI_STUB is not set + +# ── Kernel features / syscalls (busybox + glibc needs) ────────────────────── +CONFIG_MULTIUSER=y +CONFIG_FHANDLE=y +CONFIG_POSIX_TIMERS=y +CONFIG_FUTEX=y +CONFIG_EPOLL=y +CONFIG_SIGNALFD=y +CONFIG_TIMERFD=y +CONFIG_EVENTFD=y +CONFIG_AIO=y +CONFIG_IO_URING=y +CONFIG_INOTIFY_USER=y +CONFIG_FANOTIFY=y +CONFIG_ADVISE_SYSCALLS=y +CONFIG_MEMBARRIER=y +CONFIG_KALLSYMS=y +CONFIG_BUG=y +CONFIG_ELF_CORE=y +CONFIG_BASE_FULL=y +CONFIG_SGETMASK_SYSCALL=y +CONFIG_KCMP=y +CONFIG_STACKTRACE_SUPPORT=y +CONFIG_HAVE_KERNEL_GZIP=y +CONFIG_KERNEL_GZIP=y + +# ── Crypto (QEMU/KVM + TLS needs a few algos; keep minimal built-in) ──────── +CONFIG_CRYPTO=y +CONFIG_CRYPTO_CRC32C=y +CONFIG_CRYPTO_SHA256=y +CONFIG_CRYPTO_AES=y +CONFIG_CRYPTO_GCM=y + +# ── Input (keyboard for VT console) ───────────────────────────────────────── +CONFIG_INPUT=y +CONFIG_INPUT_KEYBOARD=y +CONFIG_KEYBOARD_ATKBD=y +CONFIG_INPUT_MISC=y + +# ── RTC / timekeeping ─────────────────────────────────────────────────────── +CONFIG_RTC_LIB=y +CONFIG_RTC_CLASS=y +CONFIG_RTC_HCTOSYS=y + +# ── Explicitly stripped (size reduction) ──────────────────────────────────── +# CONFIG_SOUND is not set +# CONFIG_SND is not set +# CONFIG_DRM is not set +# CONFIG_BT is not set +# CONFIG_MEDIA_SUPPORT is not set +# CONFIG_WATCHDOG is not set +# CONFIG_AUDIT is not set +# CONFIG_SECURITY is not set +# CONFIG_SECURITYFS is not set +# CONFIG_QUOTA is not set +# CONFIG_SWAP is not set +# CONFIG_SUSPEND is not set +# CONFIG_HIBERNATION is not set +# CONFIG_CPU_FREQ is not set +# CONFIG_CPU_IDLE is not set +# CONFIG_WLAN is not set +# CONFIG_NLATTR is not set +# CONFIG_COMPAT_BRK is not set + +# ── Debug (lightweight) ───────────────────────────────────────────────────── +# CONFIG_DEBUG_INFO is not set +# CONFIG_KASAN is not set +# CONFIG_UBSAN is not set +CONFIG_PANIC_ON_OOPS=y +CONFIG_PANIC_TIMEOUT=5 +CONFIG_MAGIC_SYSRQ=y +CONFIG_SCHED_DEBUG=y