1479e161ab
- 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.
805 lines
27 KiB
Bash
Executable File
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 "$@"
|