Compare commits
6 Commits
1479e161ab
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 14cf8332a2 | |||
| 2b7a8290a5 | |||
| 6623d4d1fd | |||
| 4e0ab94ee2 | |||
| ecda528b90 | |||
| 85d460e749 |
+33
@@ -0,0 +1,33 @@
|
||||
# Build artifacts (large binaries, not tracked in git)
|
||||
build/output/hermes-rescue.iso
|
||||
build/output/casper-rw
|
||||
build/output/hermes-data.ext4
|
||||
build/output/fix-line-endings.ps1
|
||||
build/output/python/ # Portable Python (download at build time)
|
||||
build/iso-staging/
|
||||
build/xubuntu-24.04*.iso
|
||||
build/SHA256SUMS
|
||||
build/hermes-build.fs
|
||||
|
||||
# Model and native binaries (large, downloaded at build time)
|
||||
*.gguf
|
||||
*.dll
|
||||
*.exe
|
||||
|
||||
# Python cache
|
||||
__pycache__/
|
||||
*.pyc
|
||||
*.pyo
|
||||
|
||||
# IDE/editor
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Secrets
|
||||
src/hermes/auth.json
|
||||
@@ -78,6 +78,47 @@ check:
|
||||
fi; \
|
||||
done
|
||||
|
||||
## wheels-windows-mcp — Download Windows-MCP wheels for offline install
|
||||
wheels-windows-mcp:
|
||||
@printf "$(BOLD)Downloading Windows-MCP wheels...$(NC)\n"
|
||||
@mkdir -p $(OUTPUT_DIR)/bin/wheels-windows-mcp
|
||||
@pip3 download --platform win_amd64 --python-version 3.13 --only-binary=:all: \
|
||||
--dest $(OUTPUT_DIR)/bin/wheels-windows-mcp --quiet windows-mcp 2>&1
|
||||
@printf "$(GREEN)✓$(NC) Downloaded $$(ls $(OUTPUT_DIR)/bin/wheels-windows-mcp/ | wc -l) wheels\n"
|
||||
@du -sh $(OUTPUT_DIR)/bin/wheels-windows-mcp/
|
||||
|
||||
## wheels-hermes — Download Hermes Agent wheels for offline Windows install
|
||||
wheels-hermes:
|
||||
@printf "$(BOLD)Downloading Hermes Agent wheels...$(NC)\n"
|
||||
@mkdir -p $(OUTPUT_DIR)/bin/wheels
|
||||
@pip3 download --platform win_amd64 --python-version 3.11 --only-binary=:all: \
|
||||
--dest $(OUTPUT_DIR)/bin/wheels --quiet hermes-agent zeroconf ifaddr 2>&1
|
||||
@printf "$(GREEN)✓$(NC) Downloaded $$(ls $(OUTPUT_DIR)/bin/wheels/ | wc -l) wheels\n"
|
||||
@du -sh $(OUTPUT_DIR)/bin/wheels/
|
||||
|
||||
## get-pip — Download get-pip.py bootstrapper
|
||||
get-pip:
|
||||
@printf "$(BOLD)Downloading get-pip.py...$(NC)\n"
|
||||
@curl -sL https://bootstrap.pypa.io/get-pip.py -o $(OUTPUT_DIR)/bin/get-pip.py
|
||||
@printf "$(GREEN)✓$(NC) Downloaded get-pip.py ($$(wc -c < $(OUTPUT_DIR)/bin/get-pip.py) bytes)\n"
|
||||
|
||||
## python-portable — Download portable Python 3.13 for Windows
|
||||
python-portable:
|
||||
@printf "$(BOLD)Downloading portable Python 3.13 for Windows...$(NC)\n"
|
||||
@mkdir -p /tmp/python-portable
|
||||
@cd /tmp/python-portable && curl -sL "https://www.python.org/ftp/python/3.13.4/python-3.13.4-embed-amd64.zip" -o python-embed.zip
|
||||
@mkdir -p $(OUTPUT_DIR)/python
|
||||
@cd /tmp/python-portable && unzip -q -o python-embed.zip -d $(OUTPUT_DIR)/python/
|
||||
@rm -rf /tmp/python-portable
|
||||
@# Remove the _pth file so pip can be installed
|
||||
@rm -f $(OUTPUT_DIR)/python/python*._pth
|
||||
@printf "$(GREEN)✓$(NC) Portable Python 3.13 extracted to $(OUTPUT_DIR)/python/\n"
|
||||
@du -sh $(OUTPUT_DIR)/python/
|
||||
|
||||
## deps — Download all dependencies for offline USB build
|
||||
deps: get-pip wheels-hermes wheels-windows-mcp
|
||||
@printf "$(GREEN)✓$(NC) All dependencies downloaded.\n"
|
||||
|
||||
clean:
|
||||
@printf "$(BOLD)Cleaning build artifacts...$(NC)\n"
|
||||
@rm -rf $(BUILD_DIR)/usb-staging/
|
||||
|
||||
@@ -13,6 +13,7 @@ An **autonomous AI-driven Windows PC diagnostic & repair toolkit** that lives on
|
||||
| ⬆️ **Driver Updates** | Hardware inventory → vendor API lookup → guided download/install |
|
||||
| 📦 **Backup/Restore** | Disk imaging, file-level backup, guided restore with AI oversight |
|
||||
| 💬 **Natural Language** | Describes what it found and what it's doing — no manual tool hunting |
|
||||
| 🖱️ **Windows UI Automation** | Hermes can click, type, take screenshots, and navigate Windows UI via Windows-MCP (option 6) |
|
||||
|
||||
## Why this is different from existing tools
|
||||
|
||||
|
||||
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,50 @@
|
||||
Hermes Portable Rescue USB
|
||||
===========================
|
||||
Multi-boot IT rescue toolkit — boot from ISO or use on a running Windows PC.
|
||||
|
||||
🥾 BOOT MODE (Ventoy)
|
||||
Restart PC -> F12/F2/ESC -> select USB -> pick ISO/hermes-rescue.iso
|
||||
Boots Xubuntu with auto-launching hardware diagnostics + Hermes AI.
|
||||
|
||||
🪟 WINDOWS PORTABLE MODE
|
||||
Plug USB into any Windows PC, open windows-hermes/, double-click:
|
||||
run-hermes.bat
|
||||
|
||||
MENU:
|
||||
1) Hermes CLI — auto: online (opencode-go), falls back to local LLM
|
||||
2) Hermes CLI — force offline (pre-starts local llama.cpp)
|
||||
3) Web Dashboard + AI Chat — http://rescue.local:8080 / http://localhost:9090
|
||||
4) Hardware Diagnostics — CPU, RAM, disks, GPU, BSOD, drivers
|
||||
5) Disk Tools — CrystalDiskInfo (SMART health check on Discrete)
|
||||
6) Windows MCP Server — UI Automation (click, type, screenshot)
|
||||
7) Exit
|
||||
|
||||
📂 USB STRUCTURE
|
||||
/ISO/ — Bootable ISO images (Ventoy)
|
||||
/hermes/ — Config, scripts, auth, web dashboard
|
||||
config.yaml — Online + offline providers (auto-fallback)
|
||||
auth.json — Your opencode-go credentials
|
||||
scripts/diagnose_windows.py — Windows-native hardware diagnostics
|
||||
web/server.py — Hardware dashboard on :8080
|
||||
tools/disk-info/ — CrystalDiskInfo portable
|
||||
/windows-hermes/ — Windows portable agent (embedded Python 3.13)
|
||||
python/ — Embedded Python 3.13 + Hermes Agent 0.15.x
|
||||
bin/
|
||||
wheels/ — 47 pre-downloaded Hermes Agent .whl files
|
||||
wheels-windows-mcp/ — 95 pre-downloaded Windows-MCP .whl files
|
||||
llama/ — llama-server.exe + CPU-optimized builds
|
||||
ollama/ — Qwen2.5-Coder-1.5B GGUF model (1,117 MB)
|
||||
run-hermes.bat — Main launcher (double-click)
|
||||
start-llama-server.bat — Starts local LLM (auto-detects GPU)
|
||||
start-windows-mcp.bat — Starts Windows-MCP UI Automation
|
||||
start-hermes-dashboard.bat — Starts Hermes AI Chat on :9090
|
||||
/ventoy/ — Ventoy bootloader config
|
||||
|
||||
🔑 NOTES
|
||||
- Online mode: opencode-go (deepseek-v4-flash) — uses your credentials
|
||||
- Offline mode: llama.cpp (Qwen2.5-Coder-1.5B Q4_K_M) — auto GPU detection, ~2 GB RAM
|
||||
- All modes share the same memory/session data
|
||||
- Diagnostics and dashboard work fully offline
|
||||
- CrystalDiskInfo requires admin for SMART data on some systems
|
||||
- Windows-MCP (UI Automation) requires admin for screenshots/clicking
|
||||
- Hermes AI Chat at http://localhost:9090 (requires llama-server running)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,9 @@
|
||||
# Diagnostic Tools — Placeholder Directory
|
||||
#
|
||||
# Populate this directory with portable Windows diagnostic executables:
|
||||
#
|
||||
# - smartmontools/ — smartctl.exe (disk health)
|
||||
# - stress-ng/ — stress.exe (CPU/GPU stress)
|
||||
# - hwinfo/ — hardware info tools
|
||||
#
|
||||
# Run: make tools-download
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user