Compare commits
2 Commits
1479e161ab
...
ecda528b90
| Author | SHA1 | Date | |
|---|---|---|---|
| ecda528b90 | |||
| 85d460e749 |
@@ -0,0 +1,347 @@
|
|||||||
|
# LLM Strategy — Hermes Portable Rescue
|
||||||
|
|
||||||
|
**Task:** T2 — research:llm-strat
|
||||||
|
**Author:** Ashley (assistant)
|
||||||
|
**Date:** 2026-07-04
|
||||||
|
**Status:** Complete — ready for T3 (workstream:arch-decision)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Executive Summary
|
||||||
|
|
||||||
|
**Recommended approach: Hybrid Local-First strategy**
|
||||||
|
|
||||||
|
| Layer | Model | When | Storage | RAM |
|
||||||
|
|-------|-------|------|---------|-----|
|
||||||
|
| **Primary** | Qwen2.5-7B-Instruct Q4_K_M (4.4 GB) | Always, offline-first | USB | ~7 GB |
|
||||||
|
| **Lightweight fallback** | Qwen2.5-3B-Instruct Q4_K_M (2.0 GB) | Target < 8 GB RAM | USB | ~4 GB |
|
||||||
|
| **API upgrade** | Cloud API (OpenCode Go / DeepSeek-V4) | Network available, complex reasoning | — | — |
|
||||||
|
|
||||||
|
**Total USB storage for models:** 4.4 GB (primary) + 2.0 GB (fallback) = 6.4 GB
|
||||||
|
**Recommended USB drive size:** 64 GB+
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Options Evaluated
|
||||||
|
|
||||||
|
### 1A — Local GGUF Model (Offline-only)
|
||||||
|
|
||||||
|
**How it works:** Ship a pre-quantized GGUF model on the USB drive. On boot, launch `llama-server` (single binary, ~12 MB statically compiled) which provides an OpenAI-compatible API on localhost. Hermes agent connects to it as its provider.
|
||||||
|
|
||||||
|
**Best for:**
|
||||||
|
- PCs with no working network (broken WiFi driver, dead NIC, paywalled captive portal)
|
||||||
|
- Air-gapped / sensitive environments
|
||||||
|
- Consistent, predictable performance
|
||||||
|
|
||||||
|
**LLM backpack (llama-server):** 12 MB static binary, no Python deps, no shared libraries.
|
||||||
|
|
||||||
|
**Risks:**
|
||||||
|
- Slower than API (10-40 tok/s on CPU vs 50-200+ tok/s via API)
|
||||||
|
- Model size matters — must fit in available RAM after OS overhead
|
||||||
|
- Cannot use large models (70B is out of reach on consumer hardware)
|
||||||
|
|
||||||
|
### 1B — API-based (Cloud-only)
|
||||||
|
|
||||||
|
**How it works:** The rescue agent requires a working internet connection and calls a cloud API (OpenCode Go, OpenRouter, Anthropic, etc.) for every inference.
|
||||||
|
|
||||||
|
**Best for:**
|
||||||
|
- Complex multi-factor reasoning (BSOD chain analysis, rare hardware issues)
|
||||||
|
- Access to state-of-the-art models (Claude, GPT-4, DeepSeek-V4-Pro)
|
||||||
|
- Minimal USB storage overhead (no model files)
|
||||||
|
|
||||||
|
**Risks:**
|
||||||
|
- **Crippling failure mode** — The most common reason for a rescue USB is a PC with broken network. If the agent can't think without the API, it's useless when you need it most.
|
||||||
|
- Latency on spotty/paywalled connections
|
||||||
|
- Privacy — diagnostic data leaves the target machine
|
||||||
|
- API cost per session
|
||||||
|
|
||||||
|
### 1C — Hybrid (Local-First, API-Enhanced)
|
||||||
|
|
||||||
|
**How it works:** Ship both a local GGUF model and an API config. Default to local inference. If a working internet connection is detected AND the local model's output is low-confidence (or the task is complex enough), fall through to a cloud API call for the specific step.
|
||||||
|
|
||||||
|
**Best for:**
|
||||||
|
- Everything — offline-capable by default, enhanced when possible
|
||||||
|
- Graceful degradation: works on a PC with a fried NIC, gets *better* on a PC with working WiFi
|
||||||
|
|
||||||
|
**Risks:**
|
||||||
|
- Slightly larger USB image (model files + API config)
|
||||||
|
- Two code paths to test and maintain
|
||||||
|
- Need a heuristic for "when to escalate to API"
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Model Comparison
|
||||||
|
|
||||||
|
### Candidate Models for Local Inference
|
||||||
|
|
||||||
|
| Model | Size (params) | Q4_K_M Size | RAM Needed | Speed (CPU) | Technical Reasoning |
|
||||||
|
|-------|--------------|-------------|------------|-------------|-------------------|
|
||||||
|
| **Qwen2.5-7B-Instruct** | 7.6B | **4.4 GB** | ~7 GB | 15-25 tok/s | Excellent — strong instruction following |
|
||||||
|
| Qwen2.5-Coder-7B-Instruct | 7.6B | 4.4 GB | ~7 GB | 15-25 tok/s | Great for code, overfit for general repair |
|
||||||
|
| Llama-3.2-3B-Instruct | 3.2B | **1.9 GB** | ~4 GB | 25-40 tok/s | Good for simple diagnostics, weak at chains |
|
||||||
|
| Qwen2.5-3B-Instruct | 3.1B | **2.0 GB** | ~4 GB | 25-40 tok/s | Good-small model, decent reasoning |
|
||||||
|
| Phi-3.5-mini-Instruct | 3.8B | 2.2 GB | ~5 GB | 20-35 tok/s | Decent technical, smaller tokenizer |
|
||||||
|
| Mistral-7B-v0.3 | 7.3B | 4.4 GB | ~7 GB | 13-23 tok/s | Strong general, slower than Qwen |
|
||||||
|
| Gemma-2-9B | 9.2B | 5.5 GB | ~8 GB | 10-18 tok/s | Very strong, but needs more RAM |
|
||||||
|
|
||||||
|
### Recommended: Qwen2.5-7B-Instruct Q4_K_M
|
||||||
|
|
||||||
|
**Why this model:**
|
||||||
|
1. **Best quality-to-size ratio** — Tops the Open LLM Leaderboard in the 7B class, with strong instruction following and reasoning. Perfect for parsing minidump stop codes, SMART attribute values, and multi-step diagnostic chains.
|
||||||
|
2. **Qwen2.5 tokenizer** handles technical English and hex dumps efficiently.
|
||||||
|
3. **Q4_K_M** is the sweet spot — only 1.7% perplexity loss vs FP16, 4x smaller than FP16.
|
||||||
|
4. **Single file** — 4.36 GB for Q4_K_M, no shards to manage.
|
||||||
|
|
||||||
|
**Why NOT Qwen2.5-Coder-7B:**
|
||||||
|
- Overfitted for code generation. A rescue agent needs to reason about stop codes, hardware specs, and repair procedures — not generate code. The general instruct variant handles these better.
|
||||||
|
|
||||||
|
### Lightweight Fallback: Qwen2.5-3B-Instruct Q4_K_M
|
||||||
|
|
||||||
|
- 1.96 GB on disk, runs in ~4 GB RAM
|
||||||
|
- Covers PCs with only 8 GB total RAM (OS overhead + Hermes leaves ~4-5 GB for the model)
|
||||||
|
- Speedy: 25-40 tok/s on modern CPU
|
||||||
|
|
||||||
|
### RAM Calculation for Target PCs
|
||||||
|
|
||||||
|
| Target PC RAM | OS Overhead (Live Linux) | Available for Model | Can Run 7B Q4_K_M? | Can Run 3B Q4_K_M? |
|
||||||
|
|--------------|--------------------------|-------------------|--------------------|--------------------|
|
||||||
|
| 4 GB | ~1-1.5 GB | ~2.5-3 GB | No | **Tight** |
|
||||||
|
| 8 GB | ~1-1.5 GB | ~6.5-7 GB | **Yes** | Yes |
|
||||||
|
| 16 GB | ~1-1.5 GB | ~14.5-15 GB | Yes | Yes |
|
||||||
|
| 32 GB | ~1-1.5 GB | ~30 GB | Yes | Yes |
|
||||||
|
|
||||||
|
**Conclusion:** The 7B Q4_K_M model requires at least 8 GB of target PC RAM. This covers the vast majority of consumer and gaming PCs from the last 7 years. For low-RAM machines (< 8 GB), fall back to the 3B model.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Boot Environment Implications
|
||||||
|
|
||||||
|
The LLM strategy is **tightly coupled** with the boot environment (T1 — research:boot-env).
|
||||||
|
|
||||||
|
| Boot Env | LLM Viability | Notes |
|
||||||
|
|----------|--------------|-------|
|
||||||
|
| **Linux live** (Alpine, Arch, custom) | ✅ **Excellent** | Native llama.cpp binary, full RAM access, Python with pip, simple process management |
|
||||||
|
| **WinPE** | ❌ **Impractical** | No native llama.cpp without MSVC runtime. No Python unless embedded. WinPE is heavily stripped (no .NET, no VC++ redist). Running LLM inference requires significant hacks. |
|
||||||
|
| **Ventoy + Linux ISO** | ✅ **Excellent** | Same as Linux live — llama.cpp + Python work natively |
|
||||||
|
|
||||||
|
**The LLM strategy effectively requires a Linux-based boot environment.** If the boot-env research (T1) concludes WinPE must be used, this strategy must be revised significantly (likely API-only with offline fallback being extremely limited diagnostic scripts without LLM).
|
||||||
|
|
||||||
|
**Assuming Linux live environment:**
|
||||||
|
|
||||||
|
```
|
||||||
|
Boot → Start llama-server in background → Start Hermes agent → Hermes connects to localhost:8080
|
||||||
|
|
||||||
|
[llama-server] [Hermes Agent]
|
||||||
|
┌──────────────┐ ┌──────────────┐
|
||||||
|
│ llama-server │←──localhost──│ Hermes core │
|
||||||
|
│ --port 8080 │ :8080/v1 │ provider: │
|
||||||
|
│ --ctx-size 4096│ │ base_url: │
|
||||||
|
│ model.gguf │ │ http:// │
|
||||||
|
└──────────────┘ │ localhost │
|
||||||
|
│ :8080/v1 │
|
||||||
|
└──────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Hermes Provider Configuration
|
||||||
|
|
||||||
|
Hermes supports **any OpenAI-compatible API** as a provider by setting `model.base_url` in config.yaml. For the rescue environment, the portable config would be:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# ~/.hermes/config.yaml (rescue profile)
|
||||||
|
model:
|
||||||
|
# If local model is running:
|
||||||
|
base_url: http://localhost:8080/v1
|
||||||
|
api_key: "" # or "sk-no-key-required"
|
||||||
|
default: qwen2.5-7b-instruct-q4_k_m
|
||||||
|
provider: "" # base_url overrides provider
|
||||||
|
|
||||||
|
# If API fallback (network available):
|
||||||
|
# base_url: https://opencode.ai/zen/go/v1/
|
||||||
|
# api_key: ${OPENCODE_GO_API_KEY}
|
||||||
|
# default: deepseek-v4-flash
|
||||||
|
```
|
||||||
|
|
||||||
|
**Local inference stack:**
|
||||||
|
1. `llama-server` — single static binary (~12 MB), started as systemd service or background process
|
||||||
|
2. Launched with: `llama-server -m /usb/hermes/models/qwen2.5-7b-instruct-q4_k_m.gguf --host 127.0.0.1 --port 8080 --ctx-size 4096 --n-gpu-layers 0 --no-mmap`
|
||||||
|
3. Hermes agent detects the API is available, sets base_url, and works normally
|
||||||
|
|
||||||
|
**Key flag: `--no-mmap`** — Prevents memory-mapping the model file, important when running from a USB stick (mmap on slow USB can cause stuttering). Falls back to read-ahead loading.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Storage Budget on USB
|
||||||
|
|
||||||
|
| Component | Size | Notes |
|
||||||
|
|-----------|------|-------|
|
||||||
|
| Linux live OS + kernel | ~500 MB-1.5 GB | Alpine minimal ~200 MB, Arch ~800 MB, custom ~1 GB |
|
||||||
|
| llama-server binary | ~12 MB | Static build, no deps |
|
||||||
|
| Qwen2.5-7B Q4_K_M model | **4.4 GB** | Primary model |
|
||||||
|
| Qwen2.5-3B Q4_K_M model | **2.0 GB** | Fallback (optional) |
|
||||||
|
| Hermes agent (Python + venv) | ~200-400 MB | Minimal venv, hermes core |
|
||||||
|
| Diagnostic tools | ~100-300 MB | smartmontools, dmidecode, stress-ng, MemTest86, dd, etc. |
|
||||||
|
| Docker image cache | ~0 MB optional | Running native binaries preferred |
|
||||||
|
| **Total (with both models)** | **~7.5-9 GB** | |
|
||||||
|
|
||||||
|
**All models fit comfortably on a 32 GB USB stick.** For 64 GB+ sticks, consider adding additional quantizations or a larger model (e.g., Qwen2.5-14B Q4_K_M at ~8 GB for extra reasoning capability).
|
||||||
|
|
||||||
|
### USB Speed Impact
|
||||||
|
|
||||||
|
- USB 3.0 (5 Gbps) → ~500 MB/s theoretical
|
||||||
|
- USB 3.1 Gen 2 (10 Gbps) → ~1 GB/s
|
||||||
|
- USB 2.0 (480 Mbps) → ~60 MB/s practical
|
||||||
|
|
||||||
|
Model loading time at USB 3.0: ~9 seconds for 4.4 GB
|
||||||
|
Model loading at USB 2.0: ~75 seconds (annoying but acceptable — boot once per session)
|
||||||
|
|
||||||
|
**Recommendation:** Use `--no-mmap` flag with llama-server to avoid live USB I/O during inference. The model loads into RAM once at startup and operates entirely from RAM afterwards.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Speed & Latency Analysis
|
||||||
|
|
||||||
|
| Scenario | Task | Local 7B Q4_K_M | API (DeepSeek-V4-Flash) |
|
||||||
|
|----------|------|-----------------|------------------------|
|
||||||
|
| First token | Cold start | ~1-3 sec | ~0.5-2 sec |
|
||||||
|
| BSOD analysis | 200 tokens | ~10 sec | ~3-5 sec |
|
||||||
|
| SMART interpretation | 400 tokens | ~20 sec | ~5-8 sec |
|
||||||
|
| Repair plan | 800 tokens | ~35 sec | ~8-12 sec |
|
||||||
|
|
||||||
|
**Local inference is 2-5x slower than API** but still fast enough for a diagnostic workflow where the user waits for results anyway. BSOD analysis at 10 seconds is perfectly acceptable — the alternative is the user manually Googling stop codes.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Offline Capability Assessment
|
||||||
|
|
||||||
|
| Feature | Local GGUF | API-only | Hybrid |
|
||||||
|
|---------|-----------|----------|--------|
|
||||||
|
| Boot with dead NIC | ✅ | ❌ | ✅ |
|
||||||
|
| Boot in paywalled WiFi | ✅ | ❌ | ✅ |
|
||||||
|
| Boot in air-gapped env | ✅ | ❌ | ✅ |
|
||||||
|
| Complex reasoning | ⚠️ (7B is decent) | ✅ | ✅ (API when available) |
|
||||||
|
| BSOD analysis | ✅ | ✅ | ✅ |
|
||||||
|
| SMART disk parsing | ✅ | ✅ | ✅ |
|
||||||
|
| Driver download | ❌ (needs net) | ✅ | ⚠️ (net required anyway) |
|
||||||
|
| Backup to cloud | ❌ (needs net) | ✅ | ⚠️ (net required anyway) |
|
||||||
|
|
||||||
|
**Key insight:** The hybrid strategy handles the critical "PC won't boot / broken NIC" scenario that's the most common use case for a rescue USB. Both local and hybrid work offline for diagnostics. The only features that genuinely need a network (driver downloads, cloud backup) require it regardless of LLM strategy.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Model Download & Preparation
|
||||||
|
|
||||||
|
Models are downloaded during the build phase (not at runtime):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Build script: fetch models
|
||||||
|
mkdir -p build/models/
|
||||||
|
|
||||||
|
# Primary model — Qwen2.5-7B-Instruct Q4_K_M
|
||||||
|
wget https://huggingface.co/Qwen/Qwen2.5-7B-Instruct-GGUF/resolve/main/qwen2.5-7b-instruct-q4_k_m.gguf \
|
||||||
|
-O build/models/qwen2.5-7b-instruct-q4_k_m.gguf
|
||||||
|
|
||||||
|
# Fallback model — Qwen2.5-3B-Instruct Q4_K_M
|
||||||
|
wget https://huggingface.co/Qwen/Qwen2.5-3B-Instruct-GGUF/resolve/main/qwen2.5-3b-instruct-q4_k_m.gguf \
|
||||||
|
-O build/models/qwen2.5-3b-instruct-q4_k_m.gguf
|
||||||
|
|
||||||
|
# llama-server binary (Linux x86_64 static)
|
||||||
|
wget https://github.com/ggml-org/llama.cpp/releases/latest/download/llama-server \
|
||||||
|
-O build/tools/llama-server
|
||||||
|
chmod +x build/tools/llama-server
|
||||||
|
```
|
||||||
|
|
||||||
|
**Licensing:** Qwen2.5 models use the Apache 2.0 license — free for commercial and personal use.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Implementation Recommendation
|
||||||
|
|
||||||
|
### Phase 1: Prototype (this sprint)
|
||||||
|
|
||||||
|
1. **Download** Qwen2.5-7B-Instruct Q4_K_M (4.4 GB) to the NAS project directory
|
||||||
|
2. **Build** a minimal Hermes profile ("rescue") with local provider config
|
||||||
|
3. **Package** llama-server binary
|
||||||
|
4. **Write** a startup script (launch-llm.sh) that:
|
||||||
|
- Probes available RAM
|
||||||
|
- Decides 7B vs 3B model based on free RAM
|
||||||
|
- Launches llama-server with appropriate flags
|
||||||
|
- Waits for API readiness
|
||||||
|
- Launches Hermes agent with local provider config
|
||||||
|
5. **Test** locally on Shawn's host
|
||||||
|
|
||||||
|
### Phase 2: Integration (post-arch-decision)
|
||||||
|
|
||||||
|
1. Integrate startup script into the bootable USB image
|
||||||
|
2. Wire model selection into the hardware inventory module (T5)
|
||||||
|
3. Add API config template for fallback provider
|
||||||
|
|
||||||
|
### Phase 3: Polish
|
||||||
|
|
||||||
|
1. Benchmark model quality on real BSOD dumps
|
||||||
|
2. Tune prompt templates for diagnostic tasks
|
||||||
|
3. Optimize context window usage (shorter contexts = faster on local)
|
||||||
|
4. Consider Knowledge Distillation: fine-tune a smaller model on repair-centric data
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Risk Register
|
||||||
|
|
||||||
|
| Risk | Likelihood | Impact | Mitigation |
|
||||||
|
|------|-----------|--------|------------|
|
||||||
|
| Target PC has < 8 GB RAM | Medium | High (7B won't load) | Ship 3B fallback model |
|
||||||
|
| USB 2.0 port (slow loading) | High (old PCs) | Medium (75s load time) | Show progress indicator, use --no-mmap |
|
||||||
|
| Qwen2.5 underperforms on diagnostics | Low | Medium | Test on real dumps, fall back to API |
|
||||||
|
| Boot env is WinPE (no llama.cpp) | TBD from T1 | Critical | Pivot to API-only or switch boot env |
|
||||||
|
| Network available but slow | Medium | Low | Local handles all diagnostics; API only for complex |
|
||||||
|
| Model licensing changes | Low | Medium | Apache 2.0 is permissive and stable |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Appendix A: Quick-Reference Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Start local LLM server
|
||||||
|
llama-server \
|
||||||
|
-m /usb/hermes/models/qwen2.5-7b-instruct-q4_k_m.gguf \
|
||||||
|
--host 127.0.0.1 \
|
||||||
|
--port 8080 \
|
||||||
|
--ctx-size 4096 \
|
||||||
|
--n-gpu-layers 0 \
|
||||||
|
--no-mmap \
|
||||||
|
--flash-attn \
|
||||||
|
--threads $(nproc)
|
||||||
|
|
||||||
|
# Verify it's running
|
||||||
|
curl http://localhost:8080/v1/models
|
||||||
|
|
||||||
|
# Test inference
|
||||||
|
curl http://localhost:8080/v1/completions \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"prompt": "What does BSOD 0x0000001A (MEMORY_MANAGEMENT) indicate?",
|
||||||
|
"model": "qwen2.5-7b-instruct-q4_k_m",
|
||||||
|
"max_tokens": 200,
|
||||||
|
"temperature": 0.1
|
||||||
|
}'
|
||||||
|
|
||||||
|
# Hermes provider config (for rescue profile)
|
||||||
|
# ~/.hermes/profiles/rescue/config.yaml:
|
||||||
|
# model:
|
||||||
|
# base_url: http://localhost:8080/v1
|
||||||
|
# api_key: ""
|
||||||
|
# default: qwen2.5-7b-instruct-q4_k_m
|
||||||
|
```
|
||||||
|
|
||||||
|
## Appendix B: Automatic Model Selection Script (pseudocode)
|
||||||
|
|
||||||
|
```
|
||||||
|
1. Check target PC RAM: free -g | grep Mem
|
||||||
|
2. If total RAM >= 8 GB → use 7B Q4_K_M (~7 GB needed)
|
||||||
|
3. If total RAM >= 4 GB → use 3B Q4_K_M (~4 GB needed)
|
||||||
|
4. If total RAM < 4 GB → fallback mode: API-only IF network, else limited scripted diagnostics
|
||||||
|
5. Launch llama-server with selected model
|
||||||
|
6. Wait for healthy endpoint
|
||||||
|
7. Launch Hermes agent with local provider config
|
||||||
|
```
|
||||||
@@ -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`.
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
"""
|
||||||
|
Hermes Portable Rescue — diagnostic tool package.
|
||||||
|
"""
|
||||||
@@ -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
|
||||||
Executable
+122
@@ -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"
|
||||||
Regular → Executable
+21
@@ -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
|
find "$VENV_DIR" -type f -name '*.pyc' -delete 2>/dev/null || true
|
||||||
rm -rf "$VENV_DIR/share" 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 -------------------------------
|
# --- Step 4: Collect portable diagnostic tools -------------------------------
|
||||||
if [[ -d "$TOOL_CACHE" ]]; then
|
if [[ -d "$TOOL_CACHE" ]]; then
|
||||||
info "Copying prebuilt diagnostic tools from $TOOL_CACHE…"
|
info "Copying prebuilt diagnostic tools from $TOOL_CACHE…"
|
||||||
|
|||||||
@@ -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.
|
||||||
Executable
+34
@@ -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
|
||||||
+58
-87
@@ -1,94 +1,65 @@
|
|||||||
# Hermes Portable Rescue — Agent Configuration
|
# Hermes Portable Rescue — Agent Configuration
|
||||||
# =============================================
|
# This file is deployed onto the USB and read by Hermes Agent at startup.
|
||||||
# This is the **source** config for the portable Hermes agent.
|
# Generated by src/build-hermes-runtime.sh
|
||||||
# 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).
|
|
||||||
|
|
||||||
agent:
|
agent:
|
||||||
name: "Hermes Portable Rescue"
|
name: "hermes-portable-rescue"
|
||||||
version: "1.0.0"
|
version: "1.0.0"
|
||||||
mode: "rescue"
|
mode: "rescue" # rescue mode: diagnostic-first, no persistent chat
|
||||||
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
|
|
||||||
|
|
||||||
logging:
|
logging:
|
||||||
file: "/hermes/reports/session.log"
|
level: "INFO"
|
||||||
level: info # debug | info | warn | error
|
file: "/tmp/hermes-rescue.log"
|
||||||
max_size_mb: 10
|
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
|
||||||
|
|||||||
Regular → Executable
Regular → Executable
+10
-1
@@ -60,4 +60,13 @@ echo ""
|
|||||||
echo "[*] Starting Hermes Agent in rescue mode…"
|
echo "[*] Starting Hermes Agent in rescue mode…"
|
||||||
echo ""
|
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
|
||||||
|
|||||||
Executable
+115
@@ -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
|
||||||
Executable
+85
@@ -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"
|
||||||
Reference in New Issue
Block a user