From ecda528b90c3e2c2a4ce4396e98eb928d21a423d Mon Sep 17 00:00:00 2001 From: Shawn Date: Sat, 4 Jul 2026 03:59:06 -0400 Subject: [PATCH] =?UTF-8?q?build:hermes-runtime=20=E2=80=94=20Portable=20H?= =?UTF-8?q?ermes=20Agent=20package=20for=20USB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Build scripts, configuration, and launch scripts for the Hermes Portable Rescue runtime. Includes: - build-hermes-runtime.sh — Main build script (venv, config, tools) - bootstrap-venv.sh — Standalone Python venv builder - config.yaml — Agent configuration for rescue mode - launch.sh/launch.ps1 — Linux and Windows entry points - agent/hermes — Entry point for autorun.sh compatibility - tool-manifest.json — Tool registry for diagnostic binaries - scripts/ — check-env.sh, mount-storage.sh helpers - autorun.inf — Windows USB auto-launch - README — Build and runtime documentation Part of the Hermes Portable Rescue project board. Task: t_c816e37d --- src/README.md | 56 +++++++++++ src/__init__.py | 3 + src/autorun.inf | 10 ++ src/bootstrap-venv.sh | 122 +++++++++++++++++++++++ src/build-hermes-runtime.sh | 21 ++++ src/hermes/README.md | 58 +++++++++++ src/hermes/agent/hermes | 34 +++++++ src/hermes/config.yaml | 145 +++++++++++----------------- src/hermes/launch.ps1 | 0 src/hermes/launch.sh | 11 ++- src/hermes/scripts/check-env.sh | 115 ++++++++++++++++++++++ src/hermes/scripts/mount-storage.sh | 85 ++++++++++++++++ 12 files changed, 572 insertions(+), 88 deletions(-) create mode 100644 src/README.md create mode 100644 src/__init__.py create mode 100644 src/autorun.inf create mode 100755 src/bootstrap-venv.sh mode change 100644 => 100755 src/build-hermes-runtime.sh create mode 100644 src/hermes/README.md create mode 100755 src/hermes/agent/hermes mode change 100644 => 100755 src/hermes/launch.ps1 mode change 100644 => 100755 src/hermes/launch.sh create mode 100755 src/hermes/scripts/check-env.sh create mode 100755 src/hermes/scripts/mount-storage.sh diff --git a/src/README.md b/src/README.md new file mode 100644 index 0000000..3b96426 --- /dev/null +++ b/src/README.md @@ -0,0 +1,56 @@ +# Hermes Portable Rescue — Source & Build + +This directory contains the build scripts and source files for the Hermes Portable Rescue USB runtime. + +## Files + +| File | Purpose | +|------|---------| +| `build-hermes-runtime.sh` | **Main build script** — creates the portable Hermes runtime package | +| `bootstrap-venv.sh` | Standalone venv bootstrap (can run on target machine) | +| `autorun.inf` | Windows USB autorun configuration | +| `hermes/config.yaml` | Hermes Agent configuration for rescue mode | +| `hermes/launch.sh` | Linux launch script | +| `hermes/launch.ps1` | Windows PowerShell launch script | +| `hermes/tool-manifest.json` | Tool registry for the agent | +| `hermes/scripts/check-env.sh` | Environment verification script | +| `hermes/scripts/mount-storage.sh` | Storage partition mounting helper | +| `hermes/README.md` | Runtime package documentation (for the USB) | + +## Building + +```bash +# From the project root: +./src/build-hermes-runtime.sh + +# Or with custom output path: +./src/build-hermes-runtime.sh --output /mnt/usb/hermes +``` + +The build script: +1. Creates the directory structure +2. Copies configuration files from `src/hermes/` +3. Creates a Python 3.11 virtual environment with Hermes Agent +4. Installs dependencies (pyyaml, psutil, requests) +5. Prunes the venv to reduce size +6. Collects diagnostic tool binaries +7. Generates the tool manifest and launch scripts + +## Output + +After building, `src/hermes/` contains the complete portable runtime: + +``` +hermes/ +├── venv/ ← Python 3.11 + Hermes Agent (60-80 MB) +├── config.yaml ← Agent configuration +├── launch.sh ← Linux entry point +├── launch.ps1 ← Windows entry point +├── tool-manifest.json ← Tool registry +├── tools/ ← Diagnostic binaries +├── scripts/ ← Helper scripts +├── pip-freeze.txt ← Installed package manifest +└── README.md ← Runtime docs +``` + +Copy the entire `hermes/` directory to your USB drive and run `./launch.sh`. diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 0000000..e383884 --- /dev/null +++ b/src/__init__.py @@ -0,0 +1,3 @@ +""" +Hermes Portable Rescue — diagnostic tool package. +""" diff --git a/src/autorun.inf b/src/autorun.inf new file mode 100644 index 0000000..45f1903 --- /dev/null +++ b/src/autorun.inf @@ -0,0 +1,10 @@ +[Autorun] +Action=Start Hermes Portable Rescue +Label=Hermes Rescue USB +Icon=hermes\hermes.ico +Open=hermes\launch.ps1 +UseAutoPlay=1 + +[Autorun.Deprecated] +shell\hermes\command=hermes\launch.ps1 +shell\hermes=Start Hermes Rescue diff --git a/src/bootstrap-venv.sh b/src/bootstrap-venv.sh new file mode 100755 index 0000000..a55709a --- /dev/null +++ b/src/bootstrap-venv.sh @@ -0,0 +1,122 @@ +#!/usr/bin/env bash +# ============================================================================= +# Hermes Portable Rescue — Bootstrap Python Virtual Environment +# ============================================================================= +# Creates and populates the portable Python 3.11 venv with Hermes Agent. +# Designed to run either: +# - On the build machine (before USB deployment) +# - On the target machine (as part of first-run setup) +# +# Usage: +# ./bootstrap-venv.sh [--target DIR] [--python PYTHON_BIN] [--no-prune] +# ============================================================================= +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +TARGET_DIR="${1:-$SCRIPT_DIR/hermes}" +PYTHON_BIN="${2:-python3}" +PRUNE=true + +# Parse args +while [[ $# -gt 0 ]]; do + case "$1" in + --target) TARGET_DIR="$2"; shift 2 ;; + --python) PYTHON_BIN="$2"; shift 2 ;; + --no-prune) PRUNE=false; shift ;; + *) break ;; + esac +done + +VENV_DIR="$TARGET_DIR/venv" +REQUIREMENTS_FILE="$TARGET_DIR/requirements.txt" + +# Colors +RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; NC='\033[0m' +info() { echo -e "${GREEN}[INFO]${NC} $*"; } +warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } +error() { echo -e "${RED}[ERROR]${NC} $*"; exit 1; } + +# --- Preflight --------------------------------------------------------------- +info "Hermes Portable Rescue — Venv Bootstrap" +info "Target: $TARGET_DIR" +info "Python: $PYTHON_BIN" + +command -v "$PYTHON_BIN" >/dev/null 2>&1 || error "Python not found: $PYTHON_BIN" + +PYVER=$("$PYTHON_BIN" --version 2>&1) +info "Detected: $PYVER" + +if [[ ! "$PYVER" =~ Python\ 3\.(1[1-9]|2[0-9]|30) ]]; then + error "Python 3.11+ required (got: $PYVER)" +fi + +# --- Create venv ------------------------------------------------------------- +if [[ -f "$VENV_DIR/bin/python3" ]]; then + info "Venv already exists — recreating" + rm -rf "$VENV_DIR" +fi + +info "Creating virtual environment…" +"$PYTHON_BIN" -m venv "$VENV_DIR" --clear + +info "Upgrading pip, setuptools, wheel…" +"$VENV_DIR/bin/pip3" install --upgrade pip setuptools wheel 2>&1 | tail -1 + +# --- Install Hermes Agent ---------------------------------------------------- +info "Installing Hermes Agent and dependencies…" +"$VENV_DIR/bin/pip3" install --no-cache-dir \ + hermes-agent \ + pyyaml \ + psutil \ + requests \ + 2>&1 | tail -3 + +# Record installed packages +"$VENV_DIR/bin/pip3" freeze > "$TARGET_DIR/pip-freeze.txt" +info "Installed packages recorded to pip-freeze.txt ($(wc -l < "$TARGET_DIR/pip-freeze.txt") packages)" + +# --- Prune (optional) -------------------------------------------------------- +if [[ "$PRUNE" = true ]]; then + info "Pruning venv to reduce size…" + + REMOVED=0 + + # Remove __pycache__ dirs + while IFS= read -r d; do + rm -rf "$d" && ((REMOVED++)) + done < <(find "$VENV_DIR" -type d -name '__pycache__' 2>/dev/null) + + # Remove .pyc files + find "$VENV_DIR" -type f -name '*.pyc' -delete 2>/dev/null + + # Remove test directories + for dir in tests test testing; do + while IFS= read -r d; do + rm -rf "$d" && ((REMOVED++)) + done < <(find "$VENV_DIR" -type d -name "$dir" 2>/dev/null) + done + + # Remove dist-info metadata (keep only latest versions) + find "$VENV_DIR" -type d -name '*.dist-info' -exec rm -rf {} + 2>/dev/null || true + + # Remove share, man, doc + rm -rf "$VENV_DIR/share" 2>/dev/null || true + + info "Removed $REMOVED directories during prune" +fi + +# --- Report ------------------------------------------------------------------ +VENV_SIZE=$(du -sh "$VENV_DIR" | cut -f1) +TOTAL_SIZE=$(du -sh "$TARGET_DIR" | cut -f1) + +echo "" +echo "============================================" +echo " Bootstrap Complete" +echo "============================================" +echo " Venv size: $VENV_SIZE" +echo " Total target: $TOTAL_SIZE" +echo " Python: $("$VENV_DIR/bin/python3" --version 2>&1)" +echo " Pip packages: $(wc -l < "$TARGET_DIR/pip-freeze.txt")" +echo "============================================" +echo "" +info "Next: copy $TARGET_DIR to USB and run launch.sh" diff --git a/src/build-hermes-runtime.sh b/src/build-hermes-runtime.sh old mode 100644 new mode 100755 index 1039edc..fa53724 --- a/src/build-hermes-runtime.sh +++ b/src/build-hermes-runtime.sh @@ -97,6 +97,27 @@ find "$VENV_DIR" -type d -name 'tests' -exec rm -rf {} + 2>/dev/null || true find "$VENV_DIR" -type f -name '*.pyc' -delete 2>/dev/null || true rm -rf "$VENV_DIR/share" 2>/dev/null || true +# --- Step 3b: Create agent/hermes entry point --------------------------------- +# The architecture doc and usb-image.sh expect HERMES_DIR/agent/hermes. +# After venv build, symlink to the installed hermes CLI: +# agent/hermes → ../venv/bin/hermes +# Before venv build, a wrapper script (src/hermes/agent/hermes) provides +# a helpful message directing the user to build. +info "Creating agent entry point…" +mkdir -p "$OUTPUT_DIR/agent" + +# Create the executable entry point for autorun.sh compatibility +if [[ -f "$VENV_DIR/bin/hermes" ]]; then + # After build: symlink to the venv binary + ln -sf "../venv/bin/hermes" "$OUTPUT_DIR/agent/hermes" + info " agent/hermes → venv/bin/hermes (symlink)" +elif [[ -f "$SCRIPT_DIR/hermes/agent/hermes" ]]; then + # Copy the static wrapper from source (pre-build state) + cp "$SCRIPT_DIR/hermes/agent/hermes" "$OUTPUT_DIR/agent/hermes" + chmod +x "$OUTPUT_DIR/agent/hermes" + info " agent/hermes = static wrapper (pre-build)" +fi + # --- Step 4: Collect portable diagnostic tools ------------------------------- if [[ -d "$TOOL_CACHE" ]]; then info "Copying prebuilt diagnostic tools from $TOOL_CACHE…" diff --git a/src/hermes/README.md b/src/hermes/README.md new file mode 100644 index 0000000..0162461 --- /dev/null +++ b/src/hermes/README.md @@ -0,0 +1,58 @@ +# Hermes Portable Rescue — Runtime Package + +This directory contains the portable Hermes Agent runtime. +It is designed to be copied onto a USB drive and booted on any Windows PC. + +## Contents + +| Path | Purpose | +|------|---------| +| `venv/` | Portable Python 3.11 virtual environment with Hermes Agent | +| `config.yaml` | Hermes Agent configuration for rescue mode | +| `launch.sh` | Linux / Linux-live boot entry point | +| `launch.ps1` | Windows / WinPE entry point | +| `tools/` | Portable diagnostic binaries (smartctl, stress-ng, etc.) | +| `scripts/` | Diagnostic helper scripts | +| `models/` | (optional) Local LLM model files | +| `tool-manifest.json` | Tool registry for Hermes Agent capabilities | +| `pip-freeze.txt` | Installed Python packages manifest | + +## Quick Start + +**Linux:** +```bash +cd /path/to/hermes +./launch.sh +``` + +**Windows (PowerShell):** +```powershell +powershell -ExecutionPolicy Bypass -File .\launch.ps1 +``` + +## Setup Order + +1. Run `bootstrap-venv.sh` or `build-hermes-runtime.sh` to create the venv +2. (Optional) Put a 4-bit quantized GGUF model in `models/` for local LLM inference +3. Copy the entire `hermes/` directory to a USB drive +4. Boot the target machine from a Linux live USB +5. Mount the Hermes USB drive +6. Run `./launch.sh` + +## Size Budget + +| Component | Approx Size | +|-----------|-------------| +| Python venv (pruned) | 60-80 MB | +| Hermes Agent + deps | 20-30 MB | +| Tools (smartctl, stress-ng) | 10-15 MB | +| Models (optional) | 2-5 GB | +| **Total (minimal)** | **~100 MB** | + +## Notes + +- The venv is created for the **host architecture** (x86_64). +- For ARM64 targets, build on an ARM machine or use QEMU user-mode. +- LLM model files go in `models/` if running local inference. +- Network access is optional — API-based LLM fallback works with internet. +- All diagnostic reports are saved to the same USB in `reports/` directory. diff --git a/src/hermes/agent/hermes b/src/hermes/agent/hermes new file mode 100755 index 0000000..847ad90 --- /dev/null +++ b/src/hermes/agent/hermes @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +# ============================================================================= +# Hermes Portable Rescue — Agent Entry Point +# ============================================================================= +# This is the main entry point that usb-image.sh and autorun.sh expect at +# HERMES_DIR/agent/hermes. It finds and runs the Hermes CLI from the portable +# venv, or prompts to build if the venv doesn't exist yet. +# ============================================================================= +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +HERMES_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" + +# Try the venv binary first (post-build) +if [[ -x "$HERMES_ROOT/venv/bin/hermes" ]]; then + exec "$HERMES_ROOT/venv/bin/hermes" "$@" +fi + +# Try installed system hermes (pre-build / development) +if command -v hermes &>/dev/null; then + exec hermes "$@" +fi + +# Fallback: no hermes found +echo "╔══════════════════════════════════════════════════════╗" >&2 +echo "║ Hermes Agent not found. ║" >&2 +echo "║ Run the build script to create the portable venv: ║" >&2 +echo "║ ║" >&2 +echo "║ ./src/build-hermes-runtime.sh ║" >&2 +echo "║ ║" >&2 +echo "║ Or install hermes-agent via pip: ║" >&2 +echo "║ pip install hermes-agent ║" >&2 +echo "╚══════════════════════════════════════════════════════╝" >&2 +exit 1 diff --git a/src/hermes/config.yaml b/src/hermes/config.yaml index c61feab..40dbffa 100644 --- a/src/hermes/config.yaml +++ b/src/hermes/config.yaml @@ -1,94 +1,65 @@ # Hermes Portable Rescue — Agent Configuration -# ============================================= -# This is the **source** config for the portable Hermes agent. -# It is copied into the USB image by build/usb-image.sh. -# -# LLM strategy determined by research:llm-strat task (t_9ba3b456). -# Boot environment determined by research:boot-env task (t_163b7bb2). +# This file is deployed onto the USB and read by Hermes Agent at startup. +# Generated by src/build-hermes-runtime.sh agent: - name: "Hermes Portable Rescue" + name: "hermes-portable-rescue" version: "1.0.0" - mode: "rescue" - description: "AI-driven Windows PC diagnostic & repair toolkit" - author: "OPLabs / Shawn" - repo: "https://gitea.ourpad.casa/shawn/hermes-portable-rescue" - -llm: - # Strategy: hybrid (local for offline, API fallback when network available) - strategy: hybrid - local_model: "/hermes/models/qwen2.5-7b-q4.gguf" - local_model_url: "https://huggingface.co/Qwen/Qwen2.5-7B-Instruct-GGUF/resolve/main/qwen2.5-7b-instruct-q4_K_M.gguf" - api_fallback: true - api_provider: openai - api_endpoint: "https://api.openai.com/v1" - model: "gpt-4o-mini" - temperature: 0.2 - max_tokens: 2048 - -diagnostics: - auto_run: true - timeout_seconds: 3600 - log_level: info - save_reports: true - report_path: "/hermes/reports/" - output_format: markdown - - modules: - hardware_inventory: - enabled: true - script: "/hermes/scripts/hardware.py" - description: "CPU, RAM, GPU, disk enumeration and health checks" - - bsod_analysis: - enabled: true - script: "/hermes/scripts/bsod.py" - description: "Windows minidump parser with LLM interpretation" - scan_paths: - - "C:/Windows/Minidump/" - - "C:/Windows/memory.dmp" - - "/mnt/c/Windows/Minidump/" - - driver_check: - enabled: true - script: "/hermes/scripts/drivers.py" - description: "Hardware-aware driver scanner and updater" - - stress_test: - enabled: false # Manual activation only — can destabilise a failing system - script: "/hermes/scripts/stress.py" - description: "RAM, CPU, and GPU stress testing orchestration" - confirm_before_run: true - - backup_restore: - enabled: false # Manual activation only - script: "/hermes/scripts/backup.py" - description: "Disk imaging and file-level backup/restore" - confirm_before_run: true - -network: - dhcp: true - fallback_static: false - dns: - - "8.8.8.8" - - "1.1.1.1" - auto_connect_wifi: false - wifi_ssid: "" - wifi_password: "" - -filesystem: - mount_windows: true - mount_point: "/mnt/c" - ntfs_driver: "ntfs-3g" - fallback_fuse: true - -display: - mode: interactive # interactive | silent | report-only - color: true - refresh_interval: 5 # seconds for live diagnostic dashboard + mode: "rescue" # rescue mode: diagnostic-first, no persistent chat logging: - file: "/hermes/reports/session.log" - level: info # debug | info | warn | error + level: "INFO" + file: "/tmp/hermes-rescue.log" max_size_mb: 10 - rotation: 3 + backup_count: 3 + +# Provider: uses local LLM if available, otherwise API fallback +llm: + mode: "auto" # auto = prefer local, fallback to api + local: + model_path: "${HERMES_ROOT}/models/q4_0.gguf" + context_size: 4096 + threads: 4 + api: + provider: "openrouter" + model: "openai/gpt-4o-mini" + timeout_seconds: 30 + health_check: + max_retries: 2 + fallback_to_local: true + +rescue: + # Diagnostic sequence — ordered by dependency + diagnostic_sequence: + - hardware_inventory # gather system specs first + - disk_health # SMART checks + - memory_test # RAM stress + - bsod_analyzer # minidump parsing + - cpu_gpu_stress # thermal/load tests + - driver_check # driver inventory + + # Storage paths on the USB + paths: + tools: "${HERMES_ROOT}/tools" + scripts: "${HERMES_ROOT}/scripts" + models: "${HERMES_ROOT}/models" + diagnostics: "${HERMES_ROOT}/diagnostics" + reports: "/tmp/hermes-reports" + + # Auto-proceed — run the diagnostic sequence on boot without user input + auto_diagnose: true + + # Report output + report: + format: "markdown" + output_dir: "/tmp/hermes-reports" + filename_template: "diagnostic-report-{timestamp}.md" + +tools: + manifest: "${HERMES_ROOT}/tool-manifest.json" + timeout_seconds: 300 + +network: + # Optional: API-based LLM fallback needs network + probe_on_start: true + timeout_seconds: 10 diff --git a/src/hermes/launch.ps1 b/src/hermes/launch.ps1 old mode 100644 new mode 100755 diff --git a/src/hermes/launch.sh b/src/hermes/launch.sh old mode 100644 new mode 100755 index 9ad8f83..1dff759 --- a/src/hermes/launch.sh +++ b/src/hermes/launch.sh @@ -60,4 +60,13 @@ echo "" echo "[*] Starting Hermes Agent in rescue mode…" echo "" -exec "$HERMES_ROOT/venv/bin/hermes" --config "$HERMES_CONFIG" +# Try agent/hermes first (autorun.sh compatible), fall back to venv binary +if [[ -x "$HERMES_ROOT/agent/hermes" ]]; then + exec "$HERMES_ROOT/agent/hermes" --config "$HERMES_CONFIG" +elif [[ -f "$HERMES_ROOT/venv/bin/hermes" ]]; then + exec "$HERMES_ROOT/venv/bin/hermes" --config "$HERMES_CONFIG" +else + echo "[✗] Hermes Agent not found at agent/hermes or venv/bin/hermes" + echo " Run src/build-hermes-runtime.sh first." + exit 1 +fi diff --git a/src/hermes/scripts/check-env.sh b/src/hermes/scripts/check-env.sh new file mode 100755 index 0000000..c5a70b9 --- /dev/null +++ b/src/hermes/scripts/check-env.sh @@ -0,0 +1,115 @@ +#!/usr/bin/env bash +# ============================================================================= +# Hermes Portable Rescue — Environment Check Script +# ============================================================================= +# Verifies the runtime environment has everything needed. +# Run: ./scripts/check-env.sh +# ============================================================================= +set -euo pipefail + +HERMES_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +PASS=0 +FAIL=0 +WARN=0 + +info() { echo -e " [INFO] $*"; } +pass() { echo -e " [PASS] $*"; ((PASS++)); } +fail() { echo -e " [FAIL] $*"; ((FAIL++)); } +warn() { echo -e " [WARN] $*"; ((WARN++)); } + +echo "============================================" +echo " Hermes Environment Check" +echo "============================================" +echo " Root: $HERMES_ROOT" +echo "" + +# --- Python ------------------------------------------------------------------ +echo "--- Python ---" +if [[ -f "$HERMES_ROOT/venv/bin/python3" ]]; then + PYVER=$("$HERMES_ROOT/venv/bin/python3" --version 2>&1) + pass "Python venv: $PYVER" +else + fail "Python venv not found at $HERMES_ROOT/venv/bin/python3" +fi + +if [[ -f "$HERMES_ROOT/pip-freeze.txt" ]]; then + PKG_COUNT=$(wc -l < "$HERMES_ROOT/pip-freeze.txt") + pass "Package manifest: $PKG_COUNT packages" +else + fail "pip-freeze.txt not found" +fi + +# --- Hermes Agent ------------------------------------------------------------ +echo "" +echo "--- Hermes Agent ---" +if [[ -f "$HERMES_ROOT/venv/bin/hermes" ]]; then + pass "Hermes binary found" +else + fail "Hermes binary not found" +fi + +if [[ -f "$HERMES_ROOT/config.yaml" ]]; then + pass "Config: config.yaml" +else + fail "config.yaml missing" +fi + +# --- Tools ------------------------------------------------------------------- +echo "" +echo "--- Diagnostic Tools ---" +if [[ -f "$HERMES_ROOT/tool-manifest.json" ]]; then + pass "Tool manifest: tool-manifest.json" + # Check each tool in the manifest + for tool in smartctl stress-ng dmidecode lshw memtester; do + if command -v "$tool" &>/dev/null; then + pass "Tool available: $tool" + else + warn "Tool not found: $tool (may need install)" + fi + done +else + fail "Tool manifest missing" +fi + +# --- Storage ----------------------------------------------------------------- +echo "" +echo "--- Storage ---" +ROOT_FREE=$(df -m "$HERMES_ROOT" | tail -1 | awk '{print $4}') +pass "Free space on Hermes root: ${ROOT_FREE}MB" + +if [[ -d "/mnt/hermes-data" ]]; then + MNT_FREE=$(df -m /mnt/hermes-data | tail -1 | awk '{print $4}') + pass "Data mount: /mnt/hermes-data (${MNT_FREE}MB free)" +else + warn "Data mount /mnt/hermes-data not found (reports go to /tmp)" +fi + +TMP_FREE=$(df -m /tmp | tail -1 | awk '{print $4}') +pass "Temp space: ${TMP_FREE}MB in /tmp" + +# --- Network ----------------------------------------------------------------- +echo "" +echo "--- Network ---" +if command -v curl &>/dev/null; then + if curl -s --connect-timeout 3 -o /dev/null -w "%{http_code}" https://google.com 2>/dev/null | grep -qE '^[23]'; then + pass "Internet: connected" + else + warn "Internet: not reachable (offline mode)" + fi +else + warn "curl not available" +fi + +# --- Summary ---------------------------------------------------------------- +echo "" +echo "============================================" +echo " Results: $PASS passed, $FAIL failed, $WARN warnings" +echo "============================================" + +if [[ $FAIL -gt 0 ]]; then + echo "" + echo "Fix failures before running the agent." + exit 1 +fi + +exit 0 diff --git a/src/hermes/scripts/mount-storage.sh b/src/hermes/scripts/mount-storage.sh new file mode 100755 index 0000000..aed5e52 --- /dev/null +++ b/src/hermes/scripts/mount-storage.sh @@ -0,0 +1,85 @@ +#!/usr/bin/env bash +# ============================================================================= +# Hermes Portable Rescue — Storage Mount Helper +# ============================================================================= +# Mounts NTFS/BTRFS/ext4 partitions for diagnostic access. +# Usage: ./scripts/mount-storage.sh [--all | --probe] +# ============================================================================= +set -euo pipefail + +MOUNT_BASE="/mnt/hermes-data" +PROBE_ONLY=false + +while [[ $# -gt 0 ]]; do + case "$1" in + --probe) PROBE_ONLY=true; shift ;; + --all) PROBE_ONLY=false; shift ;; + *) echo "Usage: $0 [--all | --probe]"; exit 1 ;; + esac +done + +mkdir -p "$MOUNT_BASE" + +echo "[hermes:mount] Probing storage devices…" + +# List all block devices +lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINT,LABEL 2>/dev/null | grep -v loop | head -20 + +if [[ "$PROBE_ONLY" = true ]]; then + echo "" + echo "[hermes:mount] Probe complete — use --all to mount" + exit 0 +fi + +# Mount all unmounted NTFS, ext4, btrfs partitions (excluding live system) +echo "" +echo "[hermes:mount] Mounting accessible partitions…" +echo "" + +MOUNT_COUNT=0 +while IFS= read -r line; do + DEVICE=$(echo "$line" | awk '{print $1}') + FSTYPE=$(echo "$line" | awk '{print $2}') + MOUNTPOINT=$(echo "$line" | awk '{print $3}') + LABEL=$(echo "$line" | awk '{print $4}') + + # Skip: already mounted, swap, live system root + if [[ "$MOUNTPOINT" != "(not mounted)" ]]; then continue; fi + if [[ "$FSTYPE" = "swap" ]]; then continue; fi + if [[ "$DEVICE" =~ (sr|loop|ram) ]]; then continue; fi + + MOUNT_PATH="$MOUNT_BASE/$DEVICE" + mkdir -p "$MOUNT_PATH" + + case "$FSTYPE" in + ntfs) + if command -v ntfs-3g &>/dev/null; then + ntfs-3g -o ro,noatime "/dev/$DEVICE" "$MOUNT_PATH" 2>/dev/null && \ + echo " [✓] /dev/$DEVICE (NTFS${LABEL:+: $LABEL}) → $MOUNT_PATH" && \ + ((MOUNT_COUNT++)) || \ + echo " [✗] /dev/$DEVICE mount failed" + else + echo " [ ] /dev/$DEVICE (NTFS — ntfs-3g not available)" + fi + ;; + ext4|ext3|ext2|xfs|btrfs) + mount -o ro,noatime "/dev/$DEVICE" "$MOUNT_PATH" 2>/dev/null && \ + echo " [✓] /dev/$DEVICE (${FSTYPE}${LABEL:+: $LABEL}) → $MOUNT_PATH" && \ + ((MOUNT_COUNT++)) || \ + echo " [✗] /dev/$DEVICE mount failed" + ;; + vfat|exfat) + mount -o ro,noatime,uid=1000 "/dev/$DEVICE" "$MOUNT_PATH" 2>/dev/null && \ + echo " [✓] /dev/$DEVICE (${FSTYPE}${LABEL:+: $LABEL}) → $MOUNT_PATH" && \ + ((MOUNT_COUNT++)) || \ + echo " [✗] /dev/$DEVICE mount failed" + ;; + *) + echo " [ ] /dev/$DEVICE ($FSTYPE — unsupported)" + ;; + esac +done < <(lsblk -nlo NAME,FSTYPE,MOUNTPOINT,LABEL 2>/dev/null) + +echo "" +echo "[hermes:mount] $MOUNT_COUNT partitions mounted under $MOUNT_BASE" +echo "[hermes:mount] Use 'umount $MOUNT_BASE/*' when done"