Files
shawn 1479e161ab build:usb-image — Final USB image assembly script, Ventoy config, and Hermes agent runtime structure
- build/usb-image.sh: Main USB image assembler with artifact validation,
  ISO building, disk image creation, and USB write support
- build/ventoy.json: Ventoy configuration for multi-boot USB
- src/hermes/config.yaml: Portable Hermes agent configuration
- src/hermes/autorun.sh: Auto-launch script with full diagnostic pipeline
- Makefile: Build system with iso/image/check/clean/write targets

Generates fallback stubs for any diagnostic modules whose upstream
build tasks haven't completed yet, ensuring the image boots even
during active development.
2026-07-04 03:53:30 -04:00

805 lines
27 KiB
Bash
Executable File

#!/usr/bin/env bash
# ==============================================================================
# Hermes Portable Rescue — USB Image Assembler
# ==============================================================================
#
# Builds the final USB boot image from upstream build artifacts.
#
# Usage:
# ./build/usb-image.sh [options]
#
# Options:
# --output PATH Output path for the disk image (default: build/hermes-rescue.img)
# --iso-only Build only the ISO, not the full disk image
# --write-device DEV Write directly to a USB device (e.g. /dev/sdb)
# --force Skip all safety confirmations
# --skip-validate Skip artifact validation (for development)
# --verbose Verbose output
# --clean Clean build dirs before starting
#
# Dependencies:
# - build:hermes-runtime → src/hermes/ (Hermes agent runtime)
# - build:hardware-inventory → src/diagnostics/hardware.py
# - build:bsod-analyzer → src/diagnostics/bsod.py
# - build:driver-updater → src/diagnostics/drivers.py
# - build:backup-restore → src/diagnostics/backup.py
# - build:stress-tests → src/diagnostics/stress.py
# - research:boot-env → research/boot-env.md (defines the boot environment)
#
# Output:
# - build/hermes-rescue.img — Full disk image (Ventoy + Rescue ISO + Tools)
# - build/hermes-rescue.iso — Bootable rescue ISO
#
# ==============================================================================
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
# ─── Configuration ────────────────────────────────────────────────────────────
# These can be overridden via environment variables
OUTPUT_IMAGE="${OUTPUT_IMAGE:-"$PROJECT_DIR/build/hermes-rescue.img"}"
OUTPUT_ISO="${OUTPUT_ISO:-"$PROJECT_DIR/build/hermes-rescue.iso"}"
BUILD_DIR="${BUILD_DIR:-"$PROJECT_DIR/build/usb-staging"}"
HERMES_SRC="${HERMES_SRC:-"$PROJECT_DIR/src/hermes"}"
DIAG_SRC="${DIAG_SRC:-"$PROJECT_DIR/src/diagnostics"}"
TOOLS_SRC="${TOOLS_SRC:-"$PROJECT_DIR/src/tools"}"
VENTOY_JSON="${VENTOY_JSON:-"$PROJECT_DIR/build/ventoy.json"}"
# Expected artifact paths (upstream deliverables)
declare -A ARTIFACTS=(
["Hermes runtime"]="$HERMES_SRC/agent/hermes"
["Hermes config"]="$HERMES_SRC/config.yaml"
["Autorun script"]="$HERMES_SRC/autorun.sh"
["Hardware inventory"]="$DIAG_SRC/hardware.py"
["BSOD analyzer"]="$DIAG_SRC/bsod.py"
["Driver updater"]="$DIAG_SRC/drivers.py"
["Backup/restore"]="$DIAG_SRC/backup.py"
["Stress tests"]="$DIAG_SRC/stress.py"
)
# Boot environment configuration
BOOT_ENV="${BOOT_ENV:-linux}" # 'linux' or 'winpe' — set by research:boot-env
ISO_LABEL="HERMES_RESCUE"
# Image sizes (can be overridden via env)
IMAGE_SIZE_MB="${IMAGE_SIZE_MB:-8192}" # 8 GB default
EFI_SIZE_MB="${EFI_SIZE_MB:-512}" # 512 MB EFI partition
# ─── Colors ────────────────────────────────────────────────────────────────────
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
info() { echo -e "${BLUE}[INFO]${NC} $*"; }
ok() { echo -e "${GREEN}[OK]${NC} $*"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $*" >&2; }
error() { echo -e "${RED}[ERROR]${NC} $*" >&2; }
die() { error "$*"; exit 1; }
# ─── Flags ────────────────────────────────────────────────────────────────────
DO_CLEAN=false
SKIP_VALIDATE=false
FORCE=false
VERBOSE=false
ISO_ONLY=false
WRITE_DEVICE=""
while [[ $# -gt 0 ]]; do
case "$1" in
--output) OUTPUT_IMAGE="$2"; shift 2 ;;
--iso-only) ISO_ONLY=true; shift ;;
--write-device) WRITE_DEVICE="$2"; shift 2 ;;
--force) FORCE=true; shift ;;
--skip-validate) SKIP_VALIDATE=true; shift ;;
--verbose) VERBOSE=true; shift ;;
--clean) DO_CLEAN=true; shift ;;
-h|--help) head -30 "$0"; exit 0 ;;
*) die "Unknown option: $1 (use --help)" ;;
esac
done
# ─── Prerequisite Checks ──────────────────────────────────────────────────────
check_command() {
if ! command -v "$1" &>/dev/null; then
die "Required command '$1' not found. Install it first."
fi
$VERBOSE && info "$1 found at $(command -v "$1")"
}
prerequisites() {
info "Checking prerequisites..."
# Core build tools
check_command mkisofs
check_command xorriso
check_command grub-mkrescue 2>/dev/null || check_command grub2-mkrescue
check_command dd
check_command parted
check_command mkfs.vfat
check_command mkfs.ext4
# Ventoy (optional — needed only for full disk image)
if ! $ISO_ONLY; then
if command -v Ventoy2Disk.sh &>/dev/null; then
VENTOY_CMD="Ventoy2Disk.sh"
elif [ -f ./ventoy/Ventoy2Disk.sh ]; then
VENTOY_CMD="./ventoy/Ventoy2Disk.sh"
else
warn "Ventoy2Disk.sh not found — will build ISO only (no Ventoy disk image)."
fi
fi
ok "All prerequisite commands available."
}
validate_artifacts() {
$SKIP_VALIDATE && { warn "Skipping artifact validation."; return 0; }
info "Validating upstream build artifacts..."
local missing=0
for name in "${!ARTIFACTS[@]}"; do
local path="${ARTIFACTS[$name]}"
if [ -f "$path" ] || [ -d "$path" ] || [ -x "$path" ] 2>/dev/null; then
$VERBOSE && ok " Found: $name$path"
else
warn " Missing: $name$path"
missing=$((missing + 1))
fi
done
# Check boot environment research
local boot_env_doc="$PROJECT_DIR/research/boot-env.md"
if [ -f "$boot_env_doc" ]; then
$VERBOSE && ok " Found: boot environment research → $boot_env_doc"
else
warn " Missing: boot environment research → $boot_env_doc (will use default: $BOOT_ENV)"
fi
if [ "$missing" -gt 0 ]; then
warn "Found $missing missing artifact(s). Build may be incomplete."
if ! $FORCE; then
echo ""
warn "Run with --force to continue anyway, or build upstream tasks first."
echo ""
info "Upstream tasks to complete:"
echo " build:hermes-runtime (t_c816e37d)"
echo " build:hardware-inventory (t_1215a5f0)"
echo " build:bsod-analyzer (t_10103a48)"
echo " build:driver-updater (t_2afd37ec)"
echo " build:backup-restore (t_0b33ea13)"
echo " build:stress-tests (t_f817392d)"
die "Missing artifacts. Build cannot proceed."
fi
else
ok "All upstream artifacts present."
fi
}
# ─── Directory Setup ──────────────────────────────────────────────────────────
setup_directories() {
info "Setting up staging directories..."
if $DO_CLEAN && [ -d "$BUILD_DIR" ]; then
info "Cleaning existing build directory..."
rm -rf "$BUILD_DIR"
fi
mkdir -p "$BUILD_DIR"/{ISO,hermes/{agent,tools,scripts},ventoy/theme,persistence,boot/grub}
$VERBOSE && ok "Staging directory: $BUILD_DIR"
ok "Staging directories created."
}
# ─── Copy Hermes Runtime ──────────────────────────────────────────────────────
copy_hermes_runtime() {
info "Copying Hermes agent runtime..."
if [ -d "$HERMES_SRC/agent" ]; then
cp -r "$HERMES_SRC/agent/"* "$BUILD_DIR/hermes/agent/" 2>/dev/null || \
warn "No files in Hermes agent directory — skipping."
fi
if [ -f "$HERMES_SRC/config.yaml" ]; then
cp "$HERMES_SRC/config.yaml" "$BUILD_DIR/hermes/config.yaml"
else
warn "config.yaml not found — generating default."
generate_default_config
fi
if [ -f "$HERMES_SRC/autorun.sh" ]; then
cp "$HERMES_SRC/autorun.sh" "$BUILD_DIR/hermes/autorun.sh"
chmod +x "$BUILD_DIR/hermes/autorun.sh"
else
warn "autorun.sh not found — generating default."
generate_default_autorun
fi
ok "Hermes runtime staged."
}
# ─── Copy Diagnostic Scripts ──────────────────────────────────────────────────
copy_diagnostics() {
info "Copying diagnostic scripts..."
local scripts=("hardware.py" "bsod.py" "drivers.py" "backup.py" "stress.py")
for script in "${scripts[@]}"; do
local src="$DIAG_SRC/$script"
if [ -f "$src" ]; then
cp "$src" "$BUILD_DIR/hermes/scripts/$script"
chmod +x "$BUILD_DIR/hermes/scripts/$script" 2>/dev/null || true
$VERBOSE && ok " Staged: $script"
else
warn " Missing diagnostic script: $script"
# Generate a stub so the system boots but the component isn't active
generate_diagnostic_stub "$script"
fi
done
ok "Diagnostic scripts staged."
}
# ─── Copy Tools ────────────────────────────────────────────────────────────────
copy_tools() {
info "Copying diagnostic tools..."
local tool_dirs=()
if [ -d "$TOOLS_SRC" ]; then
IFS=$'\n' read -d '' -r -a tool_dirs < <(find "$TOOLS_SRC" -mindepth 1 -maxdepth 1 -type d 2>/dev/null || true)
fi
if [ ${#tool_dirs[@]} -eq 0 ]; then
warn "No diagnostic tools found in $TOOLS_SRC — creating structure."
mkdir -p "$BUILD_DIR/hermes/tools"
# Create placeholder README for tools that need to be added
cat > "$BUILD_DIR/hermes/tools/README.md" << 'TOOLREADME'
# Diagnostic Tools — Placeholder Directory
Populate this directory with portable diagnostic executables:
- `smartmontools/` — `smartctl`, `smartd` (disk health)
- `memtest86+/` — MemTest86+ binary (RAM testing)
- `stress-ng/` — stress-ng binary (CPU/GPU stress)
- `dmidecode/` — DMI table decoder (hardware enumeration)
- `lshw/` — lshw binary (hardware listing)
- `parted/` — parted, fdisk utilities
- `ntfs-3g/` — NTFS read/write support
See the build:hermes-runtime task (t_c816e37d) for the packaging scripts.
TOOLREADME
else
for dir in "${tool_dirs[@]}"; do
local dirname
dirname="$(basename "$dir")"
cp -r "$dir" "$BUILD_DIR/hermes/tools/$dirname"
$VERBOSE && ok " Staged tools: $dirname"
done
fi
ok "Diagnostic tools staged."
}
# ─── Configure Boot Environment ────────────────────────────────────────────────
configure_boot() {
info "Configuring boot environment..."
# Read boot environment research if available
local boot_doc="$PROJECT_DIR/research/boot-env.md"
if [ -f "$boot_doc" ]; then
# Source any boot config variables defined in the research doc
if grep -q "^BOOT_ENV=" "$boot_doc" 2>/dev/null; then
# shellcheck source=/dev/null
source "$boot_doc"
fi
info "Using boot environment from research: $BOOT_ENV"
fi
case "$BOOT_ENV" in
linux)
configure_linux_boot
;;
winpe)
configure_winpe_boot
;;
hybrid)
configure_hybrid_boot
;;
*)
die "Unknown boot environment: $BOOT_ENV"
;;
esac
}
configure_linux_boot() {
info "Configuring Linux-based boot environment..."
# Generate GRUB config for the rescue ISO
cat > "$BUILD_DIR/boot/grub/grub.cfg" << 'GRUB'
set default=0
set timeout=10
menuentry "Hermes Portable Rescue — Linux" {
linux /boot/vmlinuz quiet splash console=tty0
initrd /boot/initrd.img
}
menuentry "Hermes Rescue — Safe Mode (no HW accel)" {
linux /boot/vmlinuz nomodeset quiet
initrd /boot/initrd.img
}
menuentry "Boot from Local Disk" {
chainloader (hd0)+1
}
menuentry "UEFI Firmware Settings" {
fwsetup
}
menuentry "System Memory Test (MemTest86+)" {
linux /boot/memtest86+.bin
}
GRUB
ok "GRUB configuration written."
}
configure_winpe_boot() {
info "Configuring WinPE-based boot environment..."
# WinPE boot configuration
cat > "$BUILD_DIR/ISO/autounattend.xml" << 'WINAUTO'
<?xml version="1.0" encoding="utf-8"?>
<unattend xmlns="urn:schemas-microsoft-com:unattend">
<settings pass="windowsPE">
<component name="Microsoft-Windows-Setup"
processorArchitecture="amd64"
publicKeyToken="31bf3856ad364e35"
language="neutral"
versionScope="nonSxS">
<UserData>
<ProductKey>
<WillShowUI>Never</WillShowUI>
</ProductKey>
</UserData>
</component>
</settings>
</unattend>
WINAUTO
ok "WinPE boot configuration written."
}
configure_hybrid_boot() {
info "Configuring hybrid (multi-boot) environment..."
# Hybrid: place both Linux ISO and WinPE ISO with Ventoy handling the boot menu
mkdir -p "$BUILD_DIR/ISO"
ok "Hybrid boot structure ready (add ISOs to build/usb-staging/ISO/)."
}
# ─── Generate Stubs / Defaults ────────────────────────────────────────────────
generate_diagnostic_stub() {
local script_name="$1"
local module_name="${script_name%.py}"
cat > "$BUILD_DIR/hermes/scripts/$script_name" << STUB
#!/usr/bin/env python3
"""
${module_name} — STUB MODULE.
This diagnostic component has not been built yet.
Replace this stub with the actual implementation once the
upstream build task completes.
Upstream task reference: check the Hermes Portable Rescue board.
"""
import sys
def diagnose() -> dict:
"""Return a stub indicating this module is unavailable."""
return {
"component": "${module_name}",
"status": "unavailable",
"message": "This diagnostic module has not been built yet.",
}
def main():
result = diagnose()
print(f"[{result['status'].upper()}] {result['component']}: {result['message']}")
return 1 if result['status'] == 'error' else 0
if __name__ == "__main__":
sys.exit(main())
STUB
chmod +x "$BUILD_DIR/hermes/scripts/$script_name"
warn " Generated stub: $script_name"
}
generate_default_config() {
cat > "$BUILD_DIR/hermes/config.yaml" << 'CONF'
# Hermes Portable Rescue — Agent Configuration
# =============================================
# This config is auto-generated by build/usb-image.sh.
# Customize for production use.
agent:
name: "Hermes Portable Rescue"
version: "1.0.0"
mode: "rescue"
llm:
# Strategy determined by research:llm-strat (t_9ba3b456)
# Options: local, api, hybrid
strategy: hybrid
local_model: "/hermes/models/qwen2.5-7b-q4.gguf"
api_fallback: true
api_endpoint: "https://api.openai.com/v1"
diagnostics:
auto_run: true
timeout_seconds: 3600
hardware_inventory: true
bsod_analysis: true
driver_check: true
stress_test: false # Manual activation only
backup_restore: false # Manual activation only
reporting:
log_level: info
output_format: markdown
save_reports: true
report_path: "/hermes/reports/"
network:
dhcp: true
fallback_static: false
dns: ["8.8.8.8", "1.1.1.1"]
CONF
ok "Default config.yaml generated."
}
generate_default_autorun() {
cat > "$BUILD_DIR/hermes/autorun.sh" << 'AUTORUN'
#!/bin/sh
# ==============================================================================
# Hermes Portable Rescue — Auto-Launch Script
# ==============================================================================
# Runs when the rescue environment boots.
# Detects environment, mounts necessary filesystems, and launches Hermes agent.
set -e
HERMES_DIR="/hermes"
REPORT_DIR="${HERMES_DIR}/reports"
CONFIG="${HERMES_DIR}/config.yaml"
echo "====================================================="
echo " Hermes Portable Rescue v1.0.0"
echo " AI-driven Windows PC diagnostic & repair toolkit"
echo "====================================================="
echo ""
# Mount the Hermes partition if not already
if [ ! -f "${CONFIG}" ]; then
echo "[*] Locating Hermes runtime..."
for device in /dev/sd* /dev/nvme0n1* /dev/mmcblk*; do
mount_point="/mnt/hermes"
mkdir -p "${mount_point}" 2>/dev/null || true
if mount -o ro "${device}" "${mount_point}" 2>/dev/null; then
if [ -f "${mount_point}/hermes/config.yaml" ]; then
HERMES_DIR="${mount_point}/hermes"
CONFIG="${HERMES_DIR}/config.yaml"
echo "[+] Found Hermes runtime on ${device}"
break
fi
umount "${mount_point}" 2>/dev/null || true
fi
done
fi
# Create report directory
mkdir -p "${REPORT_DIR}"
# Run hardware inventory
echo "[*] Running hardware inventory..."
if [ -f "${HERMES_DIR}/scripts/hardware.py" ]; then
python3 "${HERMES_DIR}/scripts/hardware.py" > "${REPORT_DIR}/hardware.txt" 2>&1 || true
echo "[+] Hardware inventory complete"
fi
# Launch the agent
echo "[*] Starting Hermes agent..."
if [ -f "${HERMES_DIR}/agent/hermes" ]; then
exec "${HERMES_DIR}/agent/hermes" --config "${CONFIG}"
elif command -v hermes &>/dev/null; then
exec hermes --config "${CONFIG}"
else
echo "[!] Hermes agent not found. Starting diagnostic shell..."
echo " Run individual diagnostics:"
echo " python3 ${HERMES_DIR}/scripts/hardware.py"
echo " python3 ${HERMES_DIR}/scripts/bsod.py --dump /path/to/minidump.dmp"
echo " python3 ${HERMES_DIR}/scripts/drivers.py --scan"
echo ""
exec /bin/sh
fi
AUTORUN
chmod +x "$BUILD_DIR/hermes/autorun.sh"
ok "Default autorun.sh generated."
}
# ─── Build ISO ─────────────────────────────────────────────────────────────────
build_iso() {
info "Building rescue ISO: ${OUTPUT_ISO}"
# Create the ISO directory structure
local iso_root="$BUILD_DIR/ISO"
mkdir -p "$iso_root"/{boot/grub,hermes}
# Copy boot files
if [ -f "$BUILD_DIR/boot/vmlinuz" ] && [ -f "$BUILD_DIR/boot/initrd.img" ]; then
cp "$BUILD_DIR/boot/vmlinuz" "$iso_root/boot/"
cp "$BUILD_DIR/boot/initrd.img" "$iso_root/boot/"
fi
# Copy GRUB config
if [ -f "$BUILD_DIR/boot/grub/grub.cfg" ]; then
cp "$BUILD_DIR/boot/grub/grub.cfg" "$iso_root/boot/grub/"
fi
# Copy Hermes runtime
cp -r "$BUILD_DIR/hermes" "$iso_root/"
# Build the ISO
xorriso -as mkisofs \
-iso-level 3 \
-full-iso9660-filenames \
-volid "${ISO_LABEL}" \
-eltorito-boot boot/grub/eltorito.img \
-no-emul-boot \
-boot-load-size 4 \
-boot-info-table \
-eltorito-alt-boot \
-e boot/grub/efi.img \
-no-emul-boot \
-isohybrid-gpt-basdat \
-o "${OUTPUT_ISO}" \
"${iso_root}" 2>&1 | tail -5
if [ -f "${OUTPUT_ISO}" ]; then
local iso_size
iso_size=$(du -h "${OUTPUT_ISO}" | cut -f1)
ok "ISO built: ${OUTPUT_ISO} (${iso_size})"
else
die "ISO build failed."
fi
}
# ─── Build Full Disk Image ────────────────────────────────────────────────────
build_disk_image() {
info "Building full disk image: ${OUTPUT_IMAGE}"
# Create a sparse disk image
dd if=/dev/zero of="${OUTPUT_IMAGE}" bs=1M count=0 seek="${IMAGE_SIZE_MB}" status=progress 2>/dev/null
# Partition: GPT with EFI system partition + data partition
parted -s "${OUTPUT_IMAGE}" mklabel gpt
parted -s "${OUTPUT_IMAGE}" mkpart ESP fat32 1MiB "${EFI_SIZE_MB}MiB"
parted -s "${OUTPUT_IMAGE}" set 1 esp on
parted -s "${OUTPUT_IMAGE}" mkpart primary ext4 "${EFI_SIZE_MB}MiB" 100%
# Set up loop device
LOOP_DEV=$(losetup --find --show --partscan "${OUTPUT_IMAGE}")
trap 'losetup -d "${LOOP_DEV}" 2>/dev/null || true' EXIT
# Format partitions
mkfs.vfat -F 32 -n "HERMES-EFI" "${LOOP_DEV}p1" >/dev/null 2>&1
mkfs.ext4 -L "HERMES-DATA" "${LOOP_DEV}p2" >/dev/null 2>&1
# Mount and populate
local mnt_efi=$(mktemp -d)
local mnt_data=$(mktemp -d)
mount "${LOOP_DEV}p1" "$mnt_efi"
mount "${LOOP_DEV}p2" "$mnt_data"
# Copy data partition contents
if [ -d "$BUILD_DIR/hermes" ]; then
cp -r "$BUILD_DIR/hermes" "$mnt_data/"
fi
if [ -d "$BUILD_DIR/ventoy" ]; then
cp -r "$BUILD_DIR/ventoy" "$mnt_efi/"
fi
if [ -f "$VENTOY_JSON" ]; then
cp "$VENTOY_JSON" "$mnt_efi/ventoy/ventoy.json"
fi
# Copy ISOs
mkdir -p "$mnt_data/ISO"
if [ -f "${OUTPUT_ISO}" ]; then
cp "${OUTPUT_ISO}" "$mnt_data/ISO/"
fi
# Sync and clean up
sync
umount "$mnt_efi"
umount "$mnt_data"
rmdir "$mnt_efi" "$mnt_data"
losetup -d "${LOOP_DEV}"
trap - EXIT
local img_size
img_size=$(du -h "${OUTPUT_IMAGE}" | cut -f1)
ok "Disk image built: ${OUTPUT_IMAGE} (${img_size})"
}
# ─── Write to USB Device ──────────────────────────────────────────────────────
write_to_device() {
local device="$1"
if [ ! -b "$device" ]; then
die "Not a block device: $device"
fi
echo ""
warn "⚠️ ABOUT TO OVERWRITE ENTIRE DEVICE: ${device}"
warn " This will DESTROY ALL DATA on ${device}."
echo ""
if ! $FORCE; then
echo -n "Type the device path again to confirm: "
read -r confirm
if [ "$confirm" != "$device" ]; then
die "Confirmation mismatch. Aborting."
fi
fi
info "Writing image to ${device}..."
dd if="${OUTPUT_IMAGE}" of="${device}" bs=4M status=progress conv=fsync
sync
ok "Device ${device} written successfully."
}
# ─── Verification ─────────────────────────────────────────────────────────────
verify_image() {
info "Verifying built image..."
if [ -f "${OUTPUT_ISO}" ]; then
local iso_size
iso_size=$(stat -c%s "${OUTPUT_ISO}" 2>/dev/null || stat -f%z "${OUTPUT_ISO}")
if [ "$iso_size" -gt 1048576 ]; then # > 1 MB
ok " ISO: ${OUTPUT_ISO} ($(du -h "${OUTPUT_ISO}" | cut -f1))"
else
warn " ISO seems too small: $(du -h "${OUTPUT_ISO}" | cut -f1)"
fi
fi
if [ -f "${OUTPUT_IMAGE}" ]; then
local img_size
img_size=$(stat -c%s "${OUTPUT_IMAGE}" 2>/dev/null || stat -f%z "${OUTPUT_IMAGE}")
if [ "$img_size" -gt 52428800 ]; then # > 50 MB
ok " Image: ${OUTPUT_IMAGE} ($(du -h "${OUTPUT_IMAGE}" | cut -f1))"
else
warn " Image seems too small: $(du -h "${OUTPUT_IMAGE}" | cut -f1)"
fi
fi
# Verify directory structure
local expected_dirs=(
"$BUILD_DIR/hermes/agent"
"$BUILD_DIR/hermes/scripts"
"$BUILD_DIR/hermes/tools"
)
for dir in "${expected_dirs[@]}"; do
if [ -d "$dir" ]; then
$VERBOSE && ok " Structure: $dir"
else
warn " Missing directory: $dir"
fi
done
ok "Verification complete."
}
# ─── Summary ───────────────────────────────────────────────────────────────────
print_summary() {
echo ""
echo "═══════════════════════════════════════════════════════════════"
echo " Hermes Portable Rescue — Build Summary"
echo "═══════════════════════════════════════════════════════════════"
echo ""
echo " Boot environment: ${BOOT_ENV}"
echo " Staging directory: ${BUILD_DIR}"
echo " Staging size: $(du -sh "${BUILD_DIR}" 2>/dev/null | cut -f1 || echo 'N/A')"
echo ""
if [ -f "${OUTPUT_ISO}" ]; then
echo " ISO: ${OUTPUT_ISO}"
echo " ISO size: $(du -h "${OUTPUT_ISO}" | cut -f1)"
fi
if [ -f "${OUTPUT_IMAGE}" ]; then
echo " Disk image: ${OUTPUT_IMAGE}"
echo " Image size: $(du -h "${OUTPUT_IMAGE}" | cut -f1)"
fi
if [ -n "$WRITE_DEVICE" ]; then
echo " Written to device: ${WRITE_DEVICE}"
fi
echo ""
# Show placeholder/generated stubs
local stub_count
stub_count=$(find "$BUILD_DIR/hermes/scripts" -name "*.py" -exec grep -l "STUB MODULE" {} \; 2>/dev/null | wc -l)
if [ "$stub_count" -gt 0 ]; then
warn " ${stub_count} diagnostic module(s) are stubs (upstream tasks not yet complete)."
echo ""
echo " Upstream tasks still needed:"
echo " build:hermes-runtime (t_c816e37d)"
echo " build:hardware-inventory (t_1215a5f0)"
echo " build:bsod-analyzer (t_10103a48)"
echo " build:driver-updater (t_2afd37ec)"
echo " build:backup-restore (t_0b33ea13)"
echo " build:stress-tests (t_f817392d)"
else
ok " All diagnostic modules present."
fi
echo "═══════════════════════════════════════════════════════════════"
echo ""
}
# ─── Main ─────────────────────────────────────────────────────────────────────
main() {
echo ""
echo "╔══════════════════════════════════════════════╗"
echo "║ Hermes Portable Rescue — USB Image Builder ║"
echo "╚══════════════════════════════════════════════╝"
echo ""
prerequisites
validate_artifacts
setup_directories
copy_hermes_runtime
copy_diagnostics
copy_tools
configure_boot
build_iso
if ! $ISO_ONLY; then
build_disk_image
fi
verify_image
print_summary
if [ -n "$WRITE_DEVICE" ]; then
write_to_device "$WRITE_DEVICE"
fi
info "Build complete! 🚀"
}
main "$@"