feat: GPU detection for LLM server (NVIDIA/AMD/Intel/Vulkan + CPU fallback)
- Added start-llama-server.bat with cross-brand GPU auto-detection: * Detection 1: nvidia-smi for NVIDIA * Detection 2: WMIC for AMD, Intel Arc, Intel HD/Iris/UHD * Detection 3: vulkaninfo as final fallback probe * Falls back to CPU-only build when no GPU found - Downloaded llama-server b9947 builds: * Vulkan build (91 MB) - supports all GPU brands * CPU build (45 MB) - fallback in cpu/ subdir - Downloaded Qwen2.5-Coder-1.5B Q4_K_M model (1.1 GB) - Fixed run-hermes.bat polling: replaces fixed 5s timeout with curl-based readiness polling (up to 30 attempts, 1s apart) - All .bat files verified: ASCII text, CRLF line terminators - Added .gitignore for build artifacts and large binaries
This commit is contained in:
Executable
+734
@@ -0,0 +1,734 @@
|
||||
#!/usr/bin/env bash
|
||||
# ==============================================================================
|
||||
# Hermes Portable Rescue — Custom Live ISO Builder
|
||||
# ==============================================================================
|
||||
#
|
||||
# Builds a custom Xubuntu 24.04 LTS live ISO with Hermes Agent pre-installed,
|
||||
# diagnostic tools, autorun setup, and persistent storage support.
|
||||
#
|
||||
# Usage:
|
||||
# sudo ./build/build-custom-iso.sh [options]
|
||||
#
|
||||
# Options:
|
||||
# --input-iso PATH Path to Xubuntu 24.04 ISO (default: auto-detect)
|
||||
# --output-dir DIR Output directory (default: ./build/output)
|
||||
# --work-dir DIR Working directory for build (default: /tmp/hermes-iso-build)
|
||||
# --skip-download Don't download ISO if missing
|
||||
# --keep-workdir Don't clean up workdir after build
|
||||
# --help Show this message
|
||||
#
|
||||
# Requirements:
|
||||
# - squashfs-tools (mksquashfs, unsquashfs)
|
||||
# - xorriso
|
||||
# - syslinux-common, isolinux
|
||||
# - sudo access for mount operations
|
||||
#
|
||||
# ==============================================================================
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
BUILD_DIR="$PROJECT_DIR/build"
|
||||
|
||||
# ─── Defaults ──────────────────────────────────────────────────────────────────
|
||||
|
||||
DEFAULT_INPUT_ISO="$BUILD_DIR/xubuntu-24.04.4-desktop-amd64.iso"
|
||||
OUTPUT_DIR="${OUTPUT_DIR:-$BUILD_DIR/output}"
|
||||
WORKDIR="${WORKDIR:-/tmp/hermes-iso-build}"
|
||||
SKIP_DOWNLOAD=false
|
||||
KEEP_WORKDIR=false
|
||||
|
||||
# ─── Colors ────────────────────────────────────────────────────────────────────
|
||||
|
||||
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BLUE='\033[0;34m'; NC='\033[0m'
|
||||
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; }
|
||||
|
||||
# ─── Parse Arguments ───────────────────────────────────────────────────────────
|
||||
|
||||
INPUT_ISO="$DEFAULT_INPUT_ISO"
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--input-iso) INPUT_ISO="$2"; shift 2 ;;
|
||||
--output-dir) OUTPUT_DIR="$2"; shift 2 ;;
|
||||
--work-dir) WORKDIR="$2"; shift 2 ;;
|
||||
--skip-download) SKIP_DOWNLOAD=true; shift ;;
|
||||
--keep-workdir) KEEP_WORKDIR=true; shift ;;
|
||||
-h|--help) head -50 "$0"; exit 0 ;;
|
||||
*) die "Unknown option: $1" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# ─── Prerequisites ─────────────────────────────────────────────────────────────
|
||||
|
||||
check_command() {
|
||||
if ! command -v "$1" &>/dev/null; then
|
||||
die "Required command '$1' not found"
|
||||
fi
|
||||
}
|
||||
|
||||
prerequisites() {
|
||||
info "Checking prerequisites..."
|
||||
check_command unsquashfs
|
||||
check_command mksquashfs
|
||||
check_command xorriso
|
||||
check_command dd
|
||||
check_command mount
|
||||
check_command rsync
|
||||
check_command chroot
|
||||
ok "All prerequisites available."
|
||||
}
|
||||
|
||||
# ─── Phase 1: Download / Verify ISO ────────────────────────────────────────────
|
||||
|
||||
phase_download_iso() {
|
||||
if [[ -f "$INPUT_ISO" ]]; then
|
||||
local iso_size
|
||||
iso_size=$(stat -c%s "$INPUT_ISO" 2>/dev/null || echo 0)
|
||||
if [[ "$iso_size" -gt 1000000000 ]]; then
|
||||
ok "Input ISO found: $INPUT_ISO ($(du -h "$INPUT_ISO" | cut -f1))"
|
||||
# Verify SHA256
|
||||
local expected_sha="fc2e995bb05c41ea19f1dbfd91f6deea7b2aed7a83b9934d98fc9d9cac527d97"
|
||||
local actual_sha
|
||||
actual_sha=$(sha256sum "$INPUT_ISO" | awk '{print $1}')
|
||||
if [[ "$actual_sha" == "$expected_sha" ]]; then
|
||||
ok "SHA256 verified."
|
||||
else
|
||||
warn "SHA256 mismatch! Expected $expected_sha, got $actual_sha"
|
||||
warn "Continuing anyway — ISO may be incomplete or different build."
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
if $SKIP_DOWNLOAD; then
|
||||
die "ISO not found at $INPUT_ISO and --skip-download is set."
|
||||
fi
|
||||
|
||||
info "Downloading Xubuntu 24.04.4 LTS Desktop ISO (~4.1 GB)..."
|
||||
wget -c "https://cdimage.ubuntu.com/xubuntu/releases/24.04/release/xubuntu-24.04.4-desktop-amd64.iso" \
|
||||
-O "$INPUT_ISO" 2>&1
|
||||
ok "Download complete."
|
||||
}
|
||||
|
||||
# ─── Phase 2: Extract ISO ─────────────────────────────────────────────────────
|
||||
|
||||
phase_extract_iso() {
|
||||
info "Extracting ISO contents..."
|
||||
|
||||
local iso_mount="/mnt/hermes-iso-source"
|
||||
mkdir -p "$iso_mount" "$WORKDIR/iso-contents"
|
||||
|
||||
# Mount ISO (handle both .iso and .img)
|
||||
mount -o loop "$INPUT_ISO" "$iso_mount" 2>/dev/null || \
|
||||
die "Failed to mount ISO. Is it a valid ISO file?"
|
||||
|
||||
# List contents for debugging
|
||||
info "ISO contents:"
|
||||
ls -la "$iso_mount/" 2>/dev/null
|
||||
echo ""
|
||||
|
||||
# Determine boot structure
|
||||
if [[ -d "$iso_mount/isolinux" ]]; then
|
||||
ok "BIOS boot: isolinux detected"
|
||||
fi
|
||||
if [[ -f "$iso_mount/boot/grub/efi.img" ]] || [[ -f "$iso_mount/boot/grub/eltorito.img" ]]; then
|
||||
ok "UEFI boot: GRUB efi.img detected"
|
||||
fi
|
||||
|
||||
# Check squashfs location
|
||||
local squashfs_src=""
|
||||
if [[ -f "$iso_mount/casper/filesystem.squashfs" ]]; then
|
||||
squashfs_src="$iso_mount/casper/filesystem.squashfs"
|
||||
ok "Squashfs: casper/filesystem.squashfs"
|
||||
elif [[ -f "$iso_mount/casper/filesystem.squashfs.xz" ]]; then
|
||||
squashfs_src="$iso_mount/casper/filesystem.squashfs.xz"
|
||||
ok "Squashfs: casper/filesystem.squashfs.xz (compressed)"
|
||||
else
|
||||
ls -la "$iso_mount/" "$iso_mount/casper/" 2>/dev/null
|
||||
umount "$iso_mount"
|
||||
die "Cannot locate filesystem.squashfs in the ISO."
|
||||
fi
|
||||
|
||||
# Copy everything EXCEPT the squashfs (we'll replace it)
|
||||
info "Copying ISO contents (excluding squashfs)..."
|
||||
rsync -a --exclude="casper/filesystem.squashfs*" \
|
||||
"$iso_mount/" "$WORKDIR/iso-contents/" 2>&1 | tail -5
|
||||
|
||||
# Copy the squashfs for extraction
|
||||
cp "$squashfs_src" "$WORKDIR/filesystem.squashfs"
|
||||
|
||||
# Extract the MBR from the ISO for hybrid boot
|
||||
info "Extracting MBR boot sector..."
|
||||
dd if="$INPUT_ISO" of="$WORKDIR/iso-contents/isohdpfx.bin" bs=440 count=1 2>/dev/null
|
||||
ok "MBR extracted."
|
||||
|
||||
umount "$iso_mount"
|
||||
rmdir "$iso_mount"
|
||||
ok "ISO contents extracted to $WORKDIR/iso-contents"
|
||||
}
|
||||
|
||||
# ─── Phase 3: Unsquash Filesystem ──────────────────────────────────────────────
|
||||
|
||||
phase_unsquash() {
|
||||
info "Unsquashing filesystem (this may take a few minutes)..."
|
||||
|
||||
mkdir -p "$WORKDIR/squashfs-root"
|
||||
|
||||
if [[ "$WORKDIR/filesystem.squashfs" == *.xz ]]; then
|
||||
xz -d "$WORKDIR/filesystem.squashfs" -c > "$WORKDIR/filesystem.squashfs.unxz" 2>/dev/null
|
||||
unsquashfs -d "$WORKDIR/squashfs-root" -f "$WORKDIR/filesystem.squashfs.unxz" 2>&1 | tail -3
|
||||
else
|
||||
unsquashfs -d "$WORKDIR/squashfs-root" -f "$WORKDIR/filesystem.squashfs" 2>&1 | tail -3
|
||||
fi
|
||||
|
||||
local root_size
|
||||
root_size=$(du -sh "$WORKDIR/squashfs-root" | cut -f1)
|
||||
ok "Filesystem unsquashed: $root_size"
|
||||
}
|
||||
|
||||
# ─── Phase 4: Customize Filesystem (Chroot) ────────────────────────────────────
|
||||
|
||||
# This function writes the customization script that runs INSIDE the chroot
|
||||
generate_chroot_script() {
|
||||
cat > "$WORKDIR/chroot-customize.sh" << 'CHROOTSCRIPT'
|
||||
#!/bin/bash
|
||||
# ==============================================================================
|
||||
# Hermes Portable Rescue — Chroot Customization Script
|
||||
# ==============================================================================
|
||||
# Runs inside the live filesystem chroot to install everything.
|
||||
# ==============================================================================
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Redirect output to log
|
||||
exec > /tmp/chroot-build.log 2>&1
|
||||
|
||||
echo "=== Hermes Portable Rescue — Chroot Customization ==="
|
||||
echo "Date: $(date)"
|
||||
echo ""
|
||||
|
||||
# ─── 1. Mount virtual filesystems ──────────────────────────────────────────────
|
||||
mount -t proc none /proc
|
||||
mount -t sysfs none /sys
|
||||
mount -t devtmpfs none /dev || mount -t tmpfs none /dev
|
||||
mkdir -p /dev/pts
|
||||
mount -t devpts none /dev/pts
|
||||
|
||||
# ─── 2. Configure package sources ──────────────────────────────────────────────
|
||||
echo "=== Configuring package sources ==="
|
||||
apt-get update -qq 2>/dev/null || true
|
||||
|
||||
# ─── 3. Install essential packages ─────────────────────────────────────────────
|
||||
echo "=== Installing packages ==="
|
||||
|
||||
PACKAGES=(
|
||||
# Python + dev
|
||||
python3-pip
|
||||
python3-venv
|
||||
python3-setuptools
|
||||
python3-wheel
|
||||
git
|
||||
|
||||
# Diagnostic tools
|
||||
smartmontools
|
||||
dmidecode
|
||||
lshw
|
||||
testdisk
|
||||
ddrescue
|
||||
ntfs-3g
|
||||
stress-ng
|
||||
|
||||
# Network tools
|
||||
nmap
|
||||
net-tools
|
||||
curl
|
||||
wget
|
||||
openssh-client
|
||||
iperf3
|
||||
traceroute
|
||||
|
||||
# Storage tools
|
||||
parted
|
||||
rsync
|
||||
htop
|
||||
iotop
|
||||
exfatprogs
|
||||
btrfs-progs
|
||||
xfsprogs
|
||||
|
||||
# Filesystem tools
|
||||
dosfstools
|
||||
e2fsprogs
|
||||
|
||||
# Windows-specific tools
|
||||
hivex # Windows registry access
|
||||
)
|
||||
|
||||
apt-get install -y -qq "${PACKAGES[@]}" 2>&1 | tail -5
|
||||
echo "Package installation complete."
|
||||
|
||||
# ─── 4. Install Hermes Agent ───────────────────────────────────────────────────
|
||||
echo "=== Installing Hermes Agent ==="
|
||||
|
||||
# Create Hermes directory structure
|
||||
mkdir -p /opt/hermes/{agent,tools,scripts,models,diagnostics,config}
|
||||
|
||||
# Install from pip
|
||||
pip3 install --no-cache-dir \
|
||||
hermes-agent \
|
||||
pyyaml \
|
||||
psutil \
|
||||
requests \
|
||||
2>&1 | tail -3
|
||||
|
||||
# Freeze package list for provenance
|
||||
pip3 freeze > /opt/hermes/pip-freeze.txt
|
||||
|
||||
echo "Hermes Agent installed."
|
||||
|
||||
# ─── 5. Configure Hermes autorun service ───────────────────────────────────────
|
||||
echo "=== Configuring autorun ==="
|
||||
|
||||
# Systemd service that runs the Hermes rescue autorun on boot
|
||||
cat > /etc/systemd/system/hermes-autorun.service << 'SERVICEEOF'
|
||||
[Unit]
|
||||
Description=Hermes Portable Rescue — Auto-Diagnostic Agent
|
||||
After=network.target multi-user.target
|
||||
ConditionPathExists=/opt/hermes/autorun.sh
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/opt/hermes/autorun.sh
|
||||
RemainAfterExit=yes
|
||||
StandardOutput=journal+console
|
||||
StandardError=journal+console
|
||||
Environment=HERMES_DIR=/opt/hermes
|
||||
Environment=CONFIG=/opt/hermes/config.yaml
|
||||
Environment=REPORT_DIR=/opt/hermes/reports
|
||||
Environment=BOOT_MODE=live
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
SERVICEEOF
|
||||
|
||||
# Desktop autostart for GUI mode — opens a terminal running Hermes
|
||||
mkdir -p /etc/xdg/autostart
|
||||
cat > /etc/xdg/autostart/hermes-rescue.desktop << 'DESKTOPDF'
|
||||
[Desktop Entry]
|
||||
Type=Application
|
||||
Name=Hermes Portable Rescue
|
||||
Comment=AI-driven PC diagnostic & repair toolkit
|
||||
Exec=xfce4-terminal --title="Hermes Rescue" -e "/opt/hermes/autorun.sh" --hold
|
||||
Terminal=false
|
||||
Categories=System;Utility;
|
||||
StartupNotify=true
|
||||
X-GNOME-Autostart-enabled=true
|
||||
DESKTOPDF
|
||||
|
||||
# Also create a desktop shortcut on the user desktop for manual launch
|
||||
mkdir -p /etc/skel/Desktop
|
||||
cat > /etc/skel/Desktop/hermes-rescue.desktop << 'DESKTOPEOF'
|
||||
[Desktop Entry]
|
||||
Type=Application
|
||||
Name=Hermes Rescue
|
||||
Comment=AI-driven PC diagnostic & repair toolkit
|
||||
Exec=xfce4-terminal --title="Hermes Rescue" -e "/opt/hermes/autorun.sh" --hold
|
||||
Terminal=false
|
||||
Icon=system-run
|
||||
Categories=System;Utility;
|
||||
StartupNotify=true
|
||||
DESKTOPEOF
|
||||
chmod +x /etc/skel/Desktop/hermes-rescue.desktop
|
||||
|
||||
# Enable the service for live boot
|
||||
systemctl enable hermes-autorun.service 2>/dev/null || true
|
||||
|
||||
echo "Autorun service configured."
|
||||
|
||||
# ─── 6. Create symlinks for diagnostic scripts ─────────────────────────────────
|
||||
echo "=== Setting up diagnostic scripts ==="
|
||||
|
||||
# The scripts will be copied from the project source via bind mount.
|
||||
# Create placeholder structure here — actual files come from the build host.
|
||||
mkdir -p /opt/hermes/scripts
|
||||
cat > /opt/hermes/scripts/README.txt << 'README'
|
||||
Diagnostic scripts are copied from the build host at ISO build time.
|
||||
Place your scripts in /opt/hermes/scripts/ on the running system.
|
||||
README
|
||||
|
||||
echo "Diagnostic scripts directory ready."
|
||||
|
||||
# ─── 7. Configure auto-login (lightdm for XFCE) ────────────────────────────────
|
||||
echo "=== Configuring auto-login ==="
|
||||
|
||||
# LightDM auto-login for the live session user (xubuntu)
|
||||
mkdir -p /etc/lightdm/lightdm.conf.d
|
||||
cat > /etc/lightdm/lightdm.conf.d/50-hermes-autologin.conf << 'LIGHTDMEOF'
|
||||
[Seat:*]
|
||||
autologin-user=xubuntu
|
||||
autologin-user-timeout=0
|
||||
autologin-session=xubuntu
|
||||
LIGHTDMEOF
|
||||
|
||||
echo "Auto-login configured for 'xubuntu'."
|
||||
|
||||
# ─── 8. Create persistent storage hook ─────────────────────────────────────────
|
||||
echo "=== Setting up persistent storage hook ==="
|
||||
|
||||
# Script to mount persistent storage if available
|
||||
mkdir -p /opt/hermes/scripts
|
||||
cat > /opt/hermes/scripts/mount-persistent.sh << 'MOUNTEOF'
|
||||
#!/bin/bash
|
||||
# Mount persistent storage partition (NTFS/exFAT labeled HERMES_DATA)
|
||||
set -e
|
||||
|
||||
PERSISTENT_LABEL="HERMES_DATA"
|
||||
MOUNT_POINT="/mnt/hermes-data"
|
||||
|
||||
mkdir -p "$MOUNT_POINT"
|
||||
|
||||
# Find and mount by label
|
||||
for dev in /dev/sd* /dev/nvme* /dev/mmcblk*; do
|
||||
if [ -b "$dev" ]; then
|
||||
label=$(blkid -o value -s LABEL "$dev" 2>/dev/null || echo "")
|
||||
if [ "$label" = "$PERSISTENT_LABEL" ]; then
|
||||
fstype=$(blkid -o value -s TYPE "$dev" 2>/dev/null || echo "")
|
||||
case "$fstype" in
|
||||
ntfs) mount -t ntfs-3g "$dev" "$MOUNT_POINT" 2>/dev/null && break ;;
|
||||
ext4) mount "$dev" "$MOUNT_POINT" 2>/dev/null && break ;;
|
||||
vfat|exfat) mount "$dev" "$MOUNT_POINT" 2>/dev/null && break ;;
|
||||
esac
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
if mountpoint -q "$MOUNT_POINT"; then
|
||||
echo "Persistent storage mounted at $MOUNT_POINT"
|
||||
# Symlink persistent config
|
||||
if [ -f "$MOUNT_POINT/hermes/config.yaml" ]; then
|
||||
ln -sf "$MOUNT_POINT/hermes/config.yaml" /opt/hermes/config.yaml
|
||||
fi
|
||||
else
|
||||
echo "No persistent storage found. Running in temporary mode."
|
||||
fi
|
||||
MOUNTEOF
|
||||
chmod +x /opt/hermes/scripts/mount-persistent.sh
|
||||
|
||||
echo "Persistent storage hook created."
|
||||
|
||||
# ─── 9. Clean up package caches ─────────────────────────────────────────────────
|
||||
echo "=== Cleaning up ==="
|
||||
apt-get clean -qq 2>/dev/null || true
|
||||
apt-get autoremove --purge -qq 2>/dev/null || true
|
||||
rm -rf /var/lib/apt/lists/* 2>/dev/null || true
|
||||
rm -rf /root/.cache 2>/dev/null || true
|
||||
rm -rf /tmp/* 2>/dev/null || true
|
||||
|
||||
# Clean Python caches
|
||||
find /usr -type d -name '__pycache__' -exec rm -rf {} + 2>/dev/null || true
|
||||
find /usr -type f -name '*.pyc' -delete 2>/dev/null || true
|
||||
find /opt/hermes -type d -name '__pycache__' -exec rm -rf {} + 2>/dev/null || true
|
||||
find /opt/hermes -type f -name '*.pyc' -delete 2>/dev/null || true
|
||||
|
||||
echo ""
|
||||
echo "=== Chroot customization complete! ==="
|
||||
echo "Packages installed: $(dpkg-query -W -f='${Package}\n' 2>/dev/null | wc -l)"
|
||||
echo "Hermes config at: /opt/hermes/"
|
||||
echo "Autorun service: hermes-autorun.service"
|
||||
echo ""
|
||||
|
||||
# Unmount virtual filesystems
|
||||
umount /dev/pts 2>/dev/null || true
|
||||
umount /sys 2>/dev/null || true
|
||||
umount /proc 2>/dev/null || true
|
||||
|
||||
exit 0
|
||||
CHROOTSCRIPT
|
||||
|
||||
chmod +x "$WORKDIR/chroot-customize.sh"
|
||||
}
|
||||
|
||||
phase_customize() {
|
||||
info "Preparing chroot environment..."
|
||||
|
||||
SQUASHFS_ROOT="$WORKDIR/squashfs-root"
|
||||
|
||||
# Bind mount necessary filesystems
|
||||
mount --bind /dev "$SQUASHFS_ROOT/dev"
|
||||
mount --bind /proc "$SQUASHFS_ROOT/proc"
|
||||
mount --bind /sys "$SQUASHFS_ROOT/sys"
|
||||
mount -t devpts none "$SQUASHFS_ROOT/dev/pts" 2>/dev/null || true
|
||||
|
||||
# Network connectivity inside chroot
|
||||
cp /etc/resolv.conf "$SQUASHFS_ROOT/etc/resolv.conf"
|
||||
|
||||
# Copy the project's diagnostic scripts and config into the chroot
|
||||
mkdir -p "$SQUASHFS_ROOT/tmp/hermes-project"
|
||||
if [[ -d "$PROJECT_DIR/src/diagnostics" ]]; then
|
||||
cp -r "$PROJECT_DIR/src/diagnostics/"* "$SQUASHFS_ROOT/tmp/hermes-project/" 2>/dev/null || true
|
||||
fi
|
||||
if [[ -f "$PROJECT_DIR/src/hermes/config.yaml" ]]; then
|
||||
cp "$PROJECT_DIR/src/hermes/config.yaml" "$SQUASHFS_ROOT/tmp/hermes-project/" 2>/dev/null || true
|
||||
fi
|
||||
if [[ -f "$PROJECT_DIR/src/hermes/autorun.sh" ]]; then
|
||||
cp "$PROJECT_DIR/src/hermes/autorun.sh" "$SQUASHFS_ROOT/tmp/hermes-project/" 2>/dev/null || true
|
||||
fi
|
||||
if [[ -f "$PROJECT_DIR/src/hermes/launch.sh" ]]; then
|
||||
cp "$PROJECT_DIR/src/hermes/launch.sh" "$SQUASHFS_ROOT/tmp/hermes-project/" 2>/dev/null || true
|
||||
fi
|
||||
if [[ -d "$PROJECT_DIR/src/hermes/scripts" ]]; then
|
||||
cp -r "$PROJECT_DIR/src/hermes/scripts/"* "$SQUASHFS_ROOT/tmp/hermes-project/scripts/" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Copy the chroot script in
|
||||
cp "$WORKDIR/chroot-customize.sh" "$SQUASHFS_ROOT/tmp/chroot-customize.sh"
|
||||
chmod +x "$SQUASHFS_ROOT/tmp/chroot-customize.sh"
|
||||
|
||||
info "Running chroot customization (this may take 10-20 minutes)..."
|
||||
echo ""
|
||||
|
||||
if chroot "$SQUASHFS_ROOT" /bin/bash /tmp/chroot-customize.sh; then
|
||||
ok "Chroot customization completed successfully."
|
||||
else
|
||||
warn "Chroot script exited with code $? — checking log..."
|
||||
fi
|
||||
|
||||
# Check the build log
|
||||
if [[ -f "$SQUASHFS_ROOT/tmp/chroot-build.log" ]]; then
|
||||
echo ""
|
||||
info "Chroot build log (last 30 lines):"
|
||||
tail -30 "$SQUASHFS_ROOT/tmp/chroot-build.log"
|
||||
fi
|
||||
|
||||
# Copy project files from temp to /opt/hermes
|
||||
info "Installing project files..."
|
||||
if [[ -f "$SQUASHFS_ROOT/tmp/hermes-project/config.yaml" ]]; then
|
||||
cp "$SQUASHFS_ROOT/tmp/hermes-project/config.yaml" "$SQUASHFS_ROOT/opt/hermes/config.yaml"
|
||||
fi
|
||||
if [[ -f "$SQUASHFS_ROOT/tmp/hermes-project/autorun.sh" ]]; then
|
||||
cp "$SQUASHFS_ROOT/tmp/hermes-project/autorun.sh" "$SQUASHFS_ROOT/opt/hermes/autorun.sh"
|
||||
chmod +x "$SQUASHFS_ROOT/opt/hermes/autorun.sh"
|
||||
fi
|
||||
if [[ -f "$SQUASHFS_ROOT/tmp/hermes-project/launch.sh" ]]; then
|
||||
cp "$SQUASHFS_ROOT/tmp/hermes-project/launch.sh" "$SQUASHFS_ROOT/opt/hermes/launch.sh"
|
||||
chmod +x "$SQUASHFS_ROOT/opt/hermes/launch.sh"
|
||||
fi
|
||||
if [[ -d "$SQUASHFS_ROOT/tmp/hermes-project/scripts" ]]; then
|
||||
cp -r "$SQUASHFS_ROOT/tmp/hermes-project/scripts/"* "$SQUASHFS_ROOT/opt/hermes/scripts/" 2>/dev/null || true
|
||||
fi
|
||||
if [[ -d "$SQUASHFS_ROOT/tmp/hermes-project/diagnostics" ]]; then
|
||||
cp -r "$SQUASHFS_ROOT/tmp/hermes-project/diagnostics/"* "$SQUASHFS_ROOT/opt/hermes/scripts/" 2>/dev/null || true
|
||||
fi
|
||||
if [[ -d "$SQUASHFS_ROOT/tmp/hermes-project/__init__.py" ]]; then
|
||||
cp "$SQUASHFS_ROOT/tmp/hermes-project/__init__.py" "$SQUASHFS_ROOT/opt/hermes/scripts/" 2>/dev/null || true
|
||||
fi
|
||||
if [[ -f "$SQUASHFS_ROOT/tmp/hermes-project/__main__.py" ]]; then
|
||||
cp "$SQUASHFS_ROOT/tmp/hermes-project/__main__.py" "$SQUASHFS_ROOT/opt/hermes/scripts/" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Clean up
|
||||
rm -rf "$SQUASHFS_ROOT/tmp/hermes-project" "$SQUASHFS_ROOT/tmp/chroot-customize.sh" 2>/dev/null || true
|
||||
}
|
||||
|
||||
# ─── Phase 5: Clean Chroot ─────────────────────────────────────────────────────
|
||||
|
||||
phase_clean_chroot() {
|
||||
info "Cleaning chroot..."
|
||||
|
||||
SQUASHFS_ROOT="$WORKDIR/squashfs-root"
|
||||
|
||||
# Remove resolv.conf
|
||||
rm -f "$SQUASHFS_ROOT/etc/resolv.conf"
|
||||
|
||||
# Unmount virtual filesystems
|
||||
umount "$SQUASHFS_ROOT/dev/pts" 2>/dev/null || true
|
||||
umount "$SQUASHFS_ROOT/sys" 2>/dev/null || true
|
||||
umount "$SQUASHFS_ROOT/proc" 2>/dev/null || true
|
||||
umount "$SQUASHFS_ROOT/dev" 2>/dev/null || true
|
||||
|
||||
ok "Chroot cleaned."
|
||||
}
|
||||
|
||||
# ─── Phase 6: Re-pack Squashfs ─────────────────────────────────────────────────
|
||||
|
||||
phase_repack_squashfs() {
|
||||
info "Re-packing squashfs filesystem (this takes a while)..."
|
||||
|
||||
local target_squashfs="$WORKDIR/iso-contents/casper/filesystem.squashfs"
|
||||
mkdir -p "$(dirname "$target_squashfs")"
|
||||
|
||||
# Rebuild with high compression
|
||||
mksquashfs "$WORKDIR/squashfs-root" "$target_squashfs" \
|
||||
-comp xz -b 1M -Xdict-size 100% \
|
||||
-noappend \
|
||||
2>&1 | tail -5
|
||||
|
||||
local squash_size
|
||||
squash_size=$(du -h "$target_squashfs" | cut -f1)
|
||||
ok "Squashfs re-packed: $squash_size"
|
||||
|
||||
# Update filesystem.size for casper
|
||||
info "Updating filesystem.size..."
|
||||
local fs_size
|
||||
fs_size=$(du -sx --block-size=1 "$WORKDIR/squashfs-root" 2>/dev/null | cut -f1)
|
||||
echo "$fs_size" > "$WORKDIR/iso-contents/casper/filesystem.size"
|
||||
ok "filesystem.size updated: $fs_size bytes"
|
||||
}
|
||||
|
||||
# ─── Phase 7: Generate ISO ─────────────────────────────────────────────────────
|
||||
|
||||
phase_generate_iso() {
|
||||
info "Generating bootable ISO..."
|
||||
|
||||
mkdir -p "$OUTPUT_DIR"
|
||||
local output_iso="$OUTPUT_DIR/hermes-rescue.iso"
|
||||
local iso_contents="$WORKDIR/iso-contents"
|
||||
|
||||
# Build the ISO with hybrid MBR (BIOS + UEFI)
|
||||
cd "$iso_contents"
|
||||
|
||||
# Determine which efi.img to use
|
||||
local efi_img=""
|
||||
if [[ -f "boot/grub/efi.img" ]]; then
|
||||
efi_img="boot/grub/efi.img"
|
||||
elif [[ -f "boot/grub/eltorito.img" ]]; then
|
||||
efi_img="boot/grub/eltorito.img"
|
||||
else
|
||||
# Search for any efi boot image
|
||||
efi_img=$(find . -name "efi.img" -o -name "eltorito.img" 2>/dev/null | head -1)
|
||||
if [[ -z "$efi_img" ]]; then
|
||||
warn "No EFI boot image found — ISO may not boot on UEFI systems."
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -n "$efi_img" ]]; then
|
||||
info "Using EFI image: $efi_img"
|
||||
xorriso -as mkisofs -r -V "HERMES_RESCUE" \
|
||||
-J -joliet-long \
|
||||
-isohybrid-mbr "$iso_contents/isohdpfx.bin" \
|
||||
-b isolinux/isolinux.bin \
|
||||
-c isolinux/boot.cat \
|
||||
-no-emul-boot -boot-load-size 4 -boot-info-table \
|
||||
-eltorito-alt-boot -e "$efi_img" -no-emul-boot \
|
||||
-isohybrid-gpt-basdat \
|
||||
-isohybrid-apm-hfsplus \
|
||||
-o "$output_iso" \
|
||||
"$iso_contents" 2>&1 | tail -10
|
||||
else
|
||||
# BIOS-only fallback
|
||||
warn "Building BIOS-only ISO (no UEFI support)."
|
||||
xorriso -as mkisofs -r -V "HERMES_RESCUE" \
|
||||
-J -joliet-long \
|
||||
-isohybrid-mbr "$iso_contents/isohdpfx.bin" \
|
||||
-b isolinux/isolinux.bin \
|
||||
-c isolinux/boot.cat \
|
||||
-no-emul-boot -boot-load-size 4 -boot-info-table \
|
||||
-o "$output_iso" \
|
||||
"$iso_contents" 2>&1 | tail -10
|
||||
fi
|
||||
|
||||
local iso_size
|
||||
iso_size=$(du -h "$output_iso" | cut -f1)
|
||||
ok "ISO generated: $output_iso ($iso_size)"
|
||||
|
||||
cd "$PROJECT_DIR"
|
||||
}
|
||||
|
||||
# ─── Phase 8: Create Persistent Storage Overlay ────────────────────────────────
|
||||
|
||||
phase_create_persistence() {
|
||||
info "Creating persistent storage files..."
|
||||
|
||||
# Create a casper-rw file for persistence (1GB by default)
|
||||
local persist_size="${PERSIST_SIZE:-1024}"
|
||||
local persist_file="$OUTPUT_DIR/casper-rw"
|
||||
|
||||
if [[ -f "$persist_file" ]]; then
|
||||
warn "casper-rw already exists — skipping."
|
||||
return 0
|
||||
fi
|
||||
|
||||
dd if=/dev/zero of="$persist_file" bs=1M count="$persist_size" status=progress 2>/dev/null
|
||||
mkfs.ext4 -F -L casper-rw "$persist_file" 2>/dev/null
|
||||
ok "Persistent storage created: $persist_file (${persist_size}MB)"
|
||||
|
||||
# Create a writeable partition image for Windows-accessible data
|
||||
local data_img="$OUTPUT_DIR/hermes-data.ext4"
|
||||
if [[ ! -f "$data_img" ]]; then
|
||||
dd if=/dev/zero of="$data_img" bs=1M count=512 status=progress 2>/dev/null
|
||||
mkfs.ext4 -F -L HERMES_DATA "$data_img" 2>/dev/null
|
||||
ok "Data partition image: $data_img (512MB)"
|
||||
fi
|
||||
}
|
||||
|
||||
# ─── Phase 9: Cleanup ──────────────────────────────────────────────────────────
|
||||
|
||||
phase_cleanup() {
|
||||
if $KEEP_WORKDIR; then
|
||||
info "Keeping workdir at $WORKDIR"
|
||||
return 0
|
||||
fi
|
||||
|
||||
info "Cleaning up build artifacts..."
|
||||
rm -rf "$WORKDIR/squashfs-root" 2>/dev/null || true
|
||||
rm -f "$WORKDIR/filesystem.squashfs" "$WORKDIR/filesystem.squashfs.unxz" 2>/dev/null || true
|
||||
rm -rf "$WORKDIR/iso-contents" 2>/dev/null || true
|
||||
rm -f "$WORKDIR/chroot-customize.sh" 2>/dev/null || true
|
||||
ok "Cleanup complete."
|
||||
}
|
||||
|
||||
# ─── Main ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
main() {
|
||||
echo ""
|
||||
echo "╔════════════════════════════════════════════════════════╗"
|
||||
echo "║ Hermes Portable Rescue — Live ISO Builder ║"
|
||||
echo "╚════════════════════════════════════════════════════════╝"
|
||||
echo ""
|
||||
echo " Input ISO: $INPUT_ISO"
|
||||
echo " Output: $OUTPUT_DIR/hermes-rescue.iso"
|
||||
echo " Workdir: $WORKDIR"
|
||||
echo " Project: $PROJECT_DIR"
|
||||
echo ""
|
||||
|
||||
# Phase count
|
||||
TOTAL=8
|
||||
CURRENT=0
|
||||
|
||||
CURRENT=$((CURRENT + 1)); echo ""; echo "[${CURRENT}/${TOTAL}] Prerequisites"; prerequisites
|
||||
CURRENT=$((CURRENT + 1)); echo ""; echo "[${CURRENT}/${TOTAL}] Download/Verify ISO"; phase_download_iso
|
||||
CURRENT=$((CURRENT + 1)); echo ""; echo "[${CURRENT}/${TOTAL}] Extract ISO"; phase_extract_iso
|
||||
CURRENT=$((CURRENT + 1)); echo ""; echo "[${CURRENT}/${TOTAL}] Unsquash filesystem"; phase_unsquash
|
||||
CURRENT=$((CURRENT + 1)); echo ""; echo "[${CURRENT}/${TOTAL}] Generate chroot script"; generate_chroot_script
|
||||
CURRENT=$((CURRENT + 1)); echo ""; echo "[${CURRENT}/${TOTAL}] Customize (chroot)"; phase_customize
|
||||
CURRENT=$((CURRENT + 1)); echo ""; echo "[${CURRENT}/${TOTAL}] Clean chroot"; phase_clean_chroot
|
||||
CURRENT=$((CURRENT + 1)); echo ""; echo "[${CURRENT}/${TOTAL}] Re-pack squashfs"; phase_repack_squashfs
|
||||
CURRENT=$((CURRENT + 1)); echo ""; echo "[${CURRENT}/${TOTAL}] Generate ISO"; phase_generate_iso
|
||||
CURRENT=$((CURRENT + 1)); echo ""; echo "[${CURRENT}/${TOTAL}] Create persistence"; phase_create_persistence
|
||||
CURRENT=$((CURRENT + 1)); echo ""; echo "[${CURRENT}/${TOTAL}] Cleanup"; phase_cleanup
|
||||
|
||||
echo ""
|
||||
echo "╔════════════════════════════════════════════════════════╗"
|
||||
echo "║ BUILD COMPLETE ║"
|
||||
echo "╚════════════════════════════════════════════════════════╝"
|
||||
echo ""
|
||||
echo " ISO: $OUTPUT_DIR/hermes-rescue.iso"
|
||||
echo " Persist: $OUTPUT_DIR/casper-rw"
|
||||
echo " Data img: $OUTPUT_DIR/hermes-data.ext4"
|
||||
echo ""
|
||||
echo "Write to USB:"
|
||||
echo " # Copy ISO to a Ventoy USB, or write directly:"
|
||||
echo " sudo dd if=$OUTPUT_DIR/hermes-rescue.iso of=/dev/sdX bs=4M status=progress"
|
||||
echo ""
|
||||
echo "For persistence, copy casper-rw to the USB root alongside the ISO,"
|
||||
echo "or the live system will auto-detect a partition labeled 'HERMES_DATA'."
|
||||
echo ""
|
||||
}
|
||||
|
||||
main "$@"
|
||||
@@ -0,0 +1,165 @@
|
||||
@echo off
|
||||
title Hermes Portable Rescue - Windows Agent
|
||||
setlocal enabledelayedexpansion
|
||||
|
||||
cd /d "%~dp0"
|
||||
|
||||
set HERMES_HOME=%~dp0..\hermes
|
||||
set HERMES_ROOT=%~dp0..\hermes
|
||||
set HERMES_CONFIG=%HERMES_ROOT%\config.yaml
|
||||
set PYTHON=%~dp0python\python.exe
|
||||
set PIP=%~dp0python\Scripts\pip.exe
|
||||
set WHEELS_DIR=%~dp0bin\wheels
|
||||
set MARKER=%~dp0\.installed
|
||||
set WEB_SERVER=%~dp0..\hermes\web\server.py
|
||||
set OLLAMA_DIR=%~dp0bin\ollama
|
||||
|
||||
echo.
|
||||
echo ============================================
|
||||
echo Hermes Portable Rescue - Windows Agent
|
||||
echo ============================================
|
||||
echo.
|
||||
echo Python: !PYTHON!
|
||||
echo Config: !HERMES_CONFIG!
|
||||
echo.
|
||||
|
||||
:: Check Python
|
||||
if not exist "!PYTHON!" (
|
||||
echo [ERROR] Python not found at !PYTHON!
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
:: First-run: bootstrap pip + install hermes + zeroconf
|
||||
if not exist "!MARKER!" (
|
||||
echo [*] First run -- installing Hermes Agent...
|
||||
echo.
|
||||
"!PYTHON!" "%~dp0bin\get-pip.py" --quiet --no-warn-script-location 2>&1
|
||||
if errorlevel 1 (
|
||||
echo [ERROR] Failed to bootstrap pip.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
"!PYTHON!" -m pip install --no-index --find-links "!WHEELS_DIR!" --quiet hermes-agent zeroconf ifaddr 2>&1
|
||||
if errorlevel 1 (
|
||||
echo [ERROR] Failed to install Hermes Agent.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
echo. > "!MARKER!"
|
||||
echo [OK] Hermes Agent installed.
|
||||
echo.
|
||||
)
|
||||
|
||||
:: Launch menu
|
||||
:menu
|
||||
cls
|
||||
echo ============================================
|
||||
echo Hermes Portable Rescue - Windows Agent
|
||||
echo ============================================
|
||||
echo.
|
||||
echo Python: !PYTHON!
|
||||
echo Config: !HERMES_CONFIG!
|
||||
echo.
|
||||
echo What would you like to do?
|
||||
echo.
|
||||
echo 1) Hermes CLI (ONLINE - opencode-go)
|
||||
echo 2) Hermes CLI (OFFLINE - local LLM)
|
||||
echo 3) Web Dashboard (http://rescue.local:8080)
|
||||
echo 4) Full Diagnostics (hardware, disk, mem, stress)
|
||||
echo 5) Start Local LLM Server (llama.cpp)
|
||||
echo 6) Exit
|
||||
echo.
|
||||
set /p choice="Choice (1-6): "
|
||||
|
||||
if "!choice!"=="1" goto run_online
|
||||
if "!choice!"=="2" goto run_offline
|
||||
if "!choice!"=="3" goto run_dashboard
|
||||
if "!choice!"=="4" goto run_diag
|
||||
if "!choice!"=="5" goto run_llm
|
||||
if "!choice!"=="6" exit /b
|
||||
echo Invalid choice.
|
||||
pause
|
||||
goto menu
|
||||
|
||||
:run_online
|
||||
echo.
|
||||
echo [*] Starting Hermes CLI (online mode - opencode-go)...
|
||||
echo.
|
||||
set HERMES_HOME=%~dp0..\hermes
|
||||
"!PYTHON!" -m hermes_cli --config "!HERMES_CONFIG!"
|
||||
echo.
|
||||
pause
|
||||
goto menu
|
||||
|
||||
:run_offline
|
||||
if not exist "!OLLAMA_DIR!\qwen2.5-coder-1.5b-q4_k_m.gguf" (
|
||||
echo.
|
||||
echo [ERROR] Offline model not found at !OLLAMA_DIR!
|
||||
echo Run option 5 first to start the LLM server.
|
||||
echo.
|
||||
pause
|
||||
goto menu
|
||||
)
|
||||
echo.
|
||||
echo [*] Starting local LLM server (llama.cpp)...
|
||||
start "Hermes Local LLM" "%~dp0start-llama-server.bat"
|
||||
echo [*] Waiting for LLM server to be ready (polling)...
|
||||
set POLL_COUNT=0
|
||||
:wait_llm
|
||||
set /a POLL_COUNT+=1
|
||||
if !POLL_COUNT! gtr 30 (
|
||||
echo [WARN] LLM server not ready after 30s - starting anyway...
|
||||
goto skip_wait
|
||||
)
|
||||
>nul 2>&1 curl -s http://127.0.0.1:11434/v1/models && (
|
||||
echo [OK] LLM server is ready!
|
||||
goto skip_wait
|
||||
)
|
||||
>nul 2>&1 timeout /t 1 /nobreak
|
||||
goto wait_llm
|
||||
:skip_wait
|
||||
echo.
|
||||
echo [*] Starting Hermes CLI (offline mode - local LLM)...
|
||||
set HERMES_HOME=%~dp0..\hermes\offline
|
||||
if not exist "!HERMES_HOME!" mkdir "!HERMES_HOME!"
|
||||
"!PYTHON!" -m hermes_cli --config "!HERMES_ROOT!\config.offline.yaml"
|
||||
echo.
|
||||
pause
|
||||
goto menu
|
||||
|
||||
:run_dashboard
|
||||
echo.
|
||||
echo [*] Starting web dashboard at http://localhost:8080
|
||||
echo [*] On other devices, use http://rescue.local:8080
|
||||
echo.
|
||||
start "Hermes Dashboard" "http://localhost:8080"
|
||||
"!PYTHON!" "!WEB_SERVER!"
|
||||
pause
|
||||
goto menu
|
||||
|
||||
:run_diag
|
||||
echo.
|
||||
echo [*] Running full diagnostics...
|
||||
echo.
|
||||
for %%s in (hardware bsod drivers stress) do (
|
||||
echo.
|
||||
echo --- %%s ---
|
||||
"!PYTHON!" "!HERMES_ROOT!\scripts\%%s.py"
|
||||
)
|
||||
echo.
|
||||
pause
|
||||
goto menu
|
||||
|
||||
:run_llm
|
||||
echo.
|
||||
echo [*] Starting llama-server (local LLM backend)...
|
||||
echo [*] Model: qwen2.5-coder-1.5b-q4_k_m.gguf
|
||||
echo [*] Server: http://127.0.0.1:11434/v1
|
||||
echo [*] Press Ctrl+C to stop when done.
|
||||
echo.
|
||||
start "Hermes LLM Server" "%~dp0start-llama-server.bat"
|
||||
echo [OK] Server starting in new window.
|
||||
echo.
|
||||
pause
|
||||
goto menu
|
||||
@@ -0,0 +1,125 @@
|
||||
@echo off
|
||||
:: ==============================================================================
|
||||
:: Hermes Portable Rescue - llama-server launcher with GPU detection
|
||||
:: ==============================================================================
|
||||
:: Detects GPU brand (NVIDIA, AMD, Intel, integrated) and launches the
|
||||
:: appropriate llama-server build (Vulkan for GPU, CPU-only fallback).
|
||||
:: ==============================================================================
|
||||
setlocal enabledelayedexpansion
|
||||
|
||||
cd /d "%~dp0"
|
||||
|
||||
set LLAMA_DIR=%~dp0bin\llama
|
||||
set MODEL_DIR=%~dp0bin\ollama
|
||||
set MODEL=%MODEL_DIR%\qwen2.5-coder-1.5b-q4_k_m.gguf
|
||||
set PORT=11434
|
||||
set GPU_FOUND=0
|
||||
|
||||
echo.
|
||||
echo ============================================
|
||||
echo Hermes LLM Server - GPU Detection
|
||||
echo ============================================
|
||||
echo.
|
||||
|
||||
:: --- Detection 1: nvidia-smi (NVIDIA) --------------------------------------
|
||||
nvidia-smi >nul 2>&1
|
||||
if !errorlevel! equ 0 (
|
||||
for /f "tokens=*" %%a in ('nvidia-smi --query-gpu=name --format=csv,noheader 2^>nul') do (
|
||||
set GPU_NAME=%%a
|
||||
)
|
||||
echo [OK] NVIDIA GPU detected: !GPU_NAME!
|
||||
set GPU_FOUND=1
|
||||
)
|
||||
|
||||
:: Detection 2: WMIC - check for AMD/Intel GPUs
|
||||
if !GPU_FOUND! equ 0 (
|
||||
wmic path win32_videocontroller get name 2>nul | findstr /i "nvidia" >nul
|
||||
if !errorlevel! equ 0 (
|
||||
echo [OK] NVIDIA GPU detected via WMI
|
||||
set GPU_FOUND=1
|
||||
) else (
|
||||
wmic path win32_videocontroller get name 2>nul | findstr /i "amd radeon" >nul
|
||||
if !errorlevel! equ 0 (
|
||||
echo [OK] AMD GPU detected via WMI
|
||||
set GPU_FOUND=1
|
||||
) else (
|
||||
wmic path win32_videocontroller get name 2>nul | findstr /i "intel arc" >nul
|
||||
if !errorlevel! equ 0 (
|
||||
echo [OK] Intel GPU detected via WMI
|
||||
set GPU_FOUND=1
|
||||
) else (
|
||||
wmic path win32_videocontroller get name 2>nul | findstr /i "intel hd intel iris intel uhd" >nul
|
||||
if !errorlevel! equ 0 (
|
||||
echo [OK] Intel integrated graphics detected via WMI
|
||||
set GPU_FOUND=1
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
:: Detection 3: vulkaninfo (direct Vulkan probe)
|
||||
if !GPU_FOUND! equ 0 (
|
||||
where vulkaninfo >nul 2>&1
|
||||
if !errorlevel! equ 0 (
|
||||
vulkaninfo --summary 2>nul | findstr /c:"Vulkan Instance" >nul
|
||||
if !errorlevel! equ 0 (
|
||||
echo [OK] Vulkan-capable device detected
|
||||
set GPU_FOUND=1
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
:: Check model exists
|
||||
if not exist "!MODEL!" (
|
||||
echo [ERROR] Model not found: !MODEL!
|
||||
echo.
|
||||
echo Please download a GGUF model and place it at:
|
||||
echo !MODEL!
|
||||
echo.
|
||||
echo Recommended: Qwen 2.5 Coder 1.5B Q4_K_M
|
||||
echo https://huggingface.co/Qwen/Qwen2.5-Coder-1.5B-Instruct-GGUF
|
||||
echo.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
:: Launch
|
||||
if !GPU_FOUND! equ 1 (
|
||||
echo.
|
||||
echo [*] Starting llama-server with GPU acceleration (Vulkan)...
|
||||
echo [*] Model: qwen2.5-coder-1.5b-q4_k_m.gguf
|
||||
echo [*] Server: http://127.0.0.1:!PORT!/v1
|
||||
echo [*] Flags: --n-gpu-layers 99 --mlock --cont-batching -c 4096
|
||||
echo.
|
||||
echo [*] Press Ctrl+C to stop the server.
|
||||
echo.
|
||||
|
||||
"!LLAMA_DIR!\llama-server.exe" ^
|
||||
--host 127.0.0.1 --port !PORT! ^
|
||||
-m "!MODEL!" ^
|
||||
--n-gpu-layers 99 ^
|
||||
--mlock ^
|
||||
--cont-batching ^
|
||||
-c 4096
|
||||
) else (
|
||||
echo.
|
||||
echo [*] No compatible GPU found. Starting CPU-only mode.
|
||||
echo [*] Model: qwen2.5-coder-1.5b-q4_k_m.gguf
|
||||
echo [*] Server: http://127.0.0.1:!PORT!/v1
|
||||
echo [*] Flags: --mlock --cont-batching -c 4096
|
||||
echo.
|
||||
echo [*] Press Ctrl+C to stop the server.
|
||||
echo.
|
||||
|
||||
"!LLAMA_DIR!\cpu\llama-server-cpu.exe" ^
|
||||
--host 127.0.0.1 --port !PORT! ^
|
||||
-m "!MODEL!" ^
|
||||
--mlock ^
|
||||
--cont-batching ^
|
||||
-c 4096
|
||||
)
|
||||
|
||||
echo.
|
||||
echo [*] Server exited with code !errorlevel!
|
||||
pause
|
||||
Executable
+96
@@ -0,0 +1,96 @@
|
||||
#!/usr/bin/env bash
|
||||
# Quick rebuild: extract existing ISO, update files, repack
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
BUILD_DIR="$PROJECT_DIR/build"
|
||||
EXISTING_ISO="$BUILD_DIR/output/hermes-rescue.iso"
|
||||
OUTPUT_DIR="$BUILD_DIR/output"
|
||||
WORKDIR="/tmp/hermes-iso-quick"
|
||||
SOURCE_DIR="$PROJECT_DIR/src"
|
||||
|
||||
info() { echo -e "\033[0;34m[INFO]\033[0m $*"; }
|
||||
ok() { echo -e "\033[0;32m[OK]\033[0m $*"; }
|
||||
die() { echo -e "\033[0;31m[ERROR]\033[0m $*"; exit 1; }
|
||||
|
||||
# Check prerequisites
|
||||
for cmd in unsquashfs mksquashfs xorriso rsync; do
|
||||
command -v "$cmd" &>/dev/null || die "$cmd not found"
|
||||
done
|
||||
|
||||
[[ -f "$EXISTING_ISO" ]] || die "Existing ISO not found: $EXISTING_ISO"
|
||||
|
||||
rm -rf "$WORKDIR"
|
||||
mkdir -p "$WORKDIR/iso-contents" "$WORKDIR/squashfs-root"
|
||||
|
||||
info "Mounting existing ISO..."
|
||||
mkdir -p /mnt/hermes-iso-source
|
||||
mount -o loop "$EXISTING_ISO" /mnt/hermes-iso-source 2>/dev/null || die "Failed to mount ISO"
|
||||
|
||||
info "Copying ISO contents (excluding squashfs)..."
|
||||
rsync -a --exclude="casper/filesystem.squashfs" \
|
||||
/mnt/hermes-iso-source/ "$WORKDIR/iso-contents/" 2>/dev/null
|
||||
|
||||
info "Extracting squashfs..."
|
||||
cp /mnt/hermes-iso-source/casper/filesystem.squashfs "$WORKDIR/filesystem.squashfs"
|
||||
umount /mnt/hermes-iso-source 2>/dev/null || true
|
||||
|
||||
unsquashfs -d "$WORKDIR/squashfs-root" -f "$WORKDIR/filesystem.squashfs" 2>&1 | tail -3
|
||||
ok "Squashfs extracted ($(du -sh "$WORKDIR/squashfs-root" | cut -f1))"
|
||||
|
||||
info "Updating config.yaml..."
|
||||
cp "$SOURCE_DIR/hermes/config.yaml" "$WORKDIR/squashfs-root/hermes/config.yaml"
|
||||
ok "config.yaml updated"
|
||||
|
||||
info "Updating auth.json..."
|
||||
cp "$SOURCE_DIR/hermes/auth.json" "$WORKDIR/squashfs-root/hermes/auth.json"
|
||||
ok "auth.json updated"
|
||||
|
||||
info "Updating autorun.sh..."
|
||||
cp "$SOURCE_DIR/hermes/autorun.sh" "$WORKDIR/squashfs-root/hermes/autorun.sh"
|
||||
ok "autorun.sh updated"
|
||||
|
||||
info "Re-packing squashfs..."
|
||||
mksquashfs "$WORKDIR/squashfs-root" "$WORKDIR/iso-contents/casper/filesystem.squashfs" \
|
||||
-comp lz4 -b 1M -noappend -processors 1 2>&1 | tail -5
|
||||
ok "Squashfs repacked ($(du -sh "$WORKDIR/iso-contents/casper/filesystem.squashfs" | cut -f1))"
|
||||
|
||||
# Update filesystem.size
|
||||
fs_size=$(du -sx --block-size=1 "$WORKDIR/squashfs-root" 2>/dev/null | cut -f1)
|
||||
echo "$fs_size" > "$WORKDIR/iso-contents/casper/filesystem.size"
|
||||
ok "filesystem.size updated"
|
||||
|
||||
info "Generating ISO..."
|
||||
OUTPUT_ISO="$OUTPUT_DIR/hermes-rescue.iso"
|
||||
EFI_IMG=$(find "$WORKDIR/iso-contents" -name "efi.img" -o -name "eltorito.img" 2>/dev/null | head -1 | sed "s|$WORKDIR/iso-contents/||")
|
||||
|
||||
if [[ -n "$EFI_IMG" ]]; then
|
||||
xorriso -as mkisofs -r -V "HERMES_RESCUE" \
|
||||
-J -joliet-long \
|
||||
-isohybrid-mbr "$WORKDIR/iso-contents/isohdpfx.bin" \
|
||||
-b isolinux/isolinux.bin \
|
||||
-c isolinux/boot.cat \
|
||||
-no-emul-boot -boot-load-size 4 -boot-info-table \
|
||||
-eltorito-alt-boot -e "$EFI_IMG" -no-emul-boot \
|
||||
-isohybrid-gpt-basdat \
|
||||
-isohybrid-apm-hfsplus \
|
||||
-o "$OUTPUT_ISO" \
|
||||
"$WORKDIR/iso-contents" 2>&1 | tail -5
|
||||
else
|
||||
xorriso -as mkisofs -r -V "HERMES_RESCUE" \
|
||||
-J -joliet-long \
|
||||
-isohybrid-mbr "$WORKDIR/iso-contents/isohdpfx.bin" \
|
||||
-b isolinux/isolinux.bin \
|
||||
-c isolinux/boot.cat \
|
||||
-no-emul-boot -boot-load-size 4 -boot-info-table \
|
||||
-o "$OUTPUT_ISO" \
|
||||
"$WORKDIR/iso-contents" 2>&1 | tail -5
|
||||
fi
|
||||
|
||||
iso_size=$(du -h "$OUTPUT_ISO" | cut -f1)
|
||||
ok "ISO generated: $OUTPUT_ISO ($iso_size)"
|
||||
|
||||
info "Cleaning up..."
|
||||
rm -rf "$WORKDIR"
|
||||
ok "Done!"
|
||||
Reference in New Issue
Block a user