Commit f7f01954 authored by Lisa's avatar Lisa

Add Windows support: GUI installer and system tray manager

Added complete Windows deployment capability:

  - windows/agent-manager.py: System tray GUI application
    * Configuration editor (no manual JSON editing)
    * Log viewer with refresh/tail
    * Service management (start/stop/restart)
    * Status monitoring dashboard
    * Windows toast notifications

  - windows/installer.iss: Inno Setup installer script
    * Graphical one-click installer
    * Service registration via NSSM
    * Start menu shortcuts
    * Uninstaller support

  - install-windows.ps1: PowerShell installer (legacy/cli)
    * Automated installation without GUI
    * Service registration (NSSM or Task Scheduler)

  - windows/build.py: Automated build script
    * PyInstaller builds for agent and manager
    * Inno Setup compilation
    * Dependency checking

  - Modified hermes_node_agent.py:
    * Fixed duplicate PosixComputerController class
    * Cleaned up code structure

  - Modified install.sh:
    * Simplified user-friendly output
    * Better error handling

The Windows agent includes:
  * PowerShell-based command execution
  * sexec.ps1 permission system (matches Linux)
  * Windows Service integration (NSSM)
  * Same WebSocket protocol (WSS)

Documentation: WINDOWS_DEPLOYMENT.md, WINDOWS_INSTALL.md

See COMPLETE_DEPLOYMENT.md for full overview.

Hermes Agent integration:
  * Works with plugins/node_gateway.py
  * Same API as Linux nodes
  * hermes node windows-pc exec "command"
parent 29a308f6
This diff is collapsed.
# Hermes Node Agent — Windows Installer (PowerShell)
# Installs the Hermes Node Agent as a Windows service using NSSM
# Requires: PowerShell 5+, Python 3.7+, Administrator rights
$ErrorActionPreference = "Stop"
Write-Host "=== Hermes Node Agent Windows Installer ===" -ForegroundColor Cyan
Write-Host ""
# ── 1. Verify running as Administrator ───────────────────────────────────────
if (-NOT ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
Write-Host "ERROR: This installer must be run as Administrator." -ForegroundColor Red
Write-Host "Right-click PowerShell → 'Run as Administrator'" -ForegroundColor Yellow
exit 1
}
# ── 2. Locate or install Python ───────────────────────────────────────────────
Write-Host "[1/7] Checking Python installation..." -ForegroundColor Green
$pythonCmd = Get-Command python -ErrorAction SilentlyContinue
if (-not $pythonCmd) {
Write-Host "Python not found in PATH. Attempting to locate..." -ForegroundColor Yellow
$possiblePaths = @(
"$env:ProgramFiles\Python39\python.exe",
"$env:ProgramFiles\Python310\python.exe",
"$env:ProgramFiles\Python311\python.exe",
"$env:ProgramFiles(x86)\Python39\python.exe",
"$env:USERPROFILE\AppData\Local\Programs\Python\Python39\python.exe"
)
$found = $false
foreach ($p in $possiblePaths) {
if (Test-Path $p) {
$pythonCmd = $p
$found = $true
break
}
}
if (-not $found) {
Write-Host "ERROR: Python not found. Install from https://www.python.org/downloads/" -ForegroundColor Red
exit 1
}
}
Write-Host " Found Python at: $($pythonCmd.Source ?? $pythonCmd)" -ForegroundColor Gray
# ── 3. Install websockets library ─────────────────────────────────────────────
Write-Host "[2/7] Installing Python dependencies..." -ForegroundColor Green
& $pythonCmd -m pip install --upgrade pip | Out-Null
& $pythonCmd -m pip install websockets | Out-Null
if ($LASTEXITCODE -ne 0) {
Write-Host "ERROR: Failed to install websockets. Check pip output above." -ForegroundColor Red
exit 1
}
Write-Host " websockets installed" -ForegroundColor Gray
# ── 4. Create config directory ───────────────────────────────────────────────
Write-Host "[3/7] Creating configuration directories..." -ForegroundColor Green
$configDir = "$env:ProgramData\hermes-node"
$agentDir = "C:\Program Files\Hermes Node"
New-Item -ItemType Directory -Force -Path $configDir | Out-Null
New-Item -ItemType Directory -Force -Path $agentDir | Out-Null
Write-Host " Config: $configDir" -ForegroundColor Gray
Write-Host " Agent: $agentDir" -ForegroundColor Gray
# ── 5. Deploy agent script ────────────────────────────────────────────────────
Write-Host "[4/7] Copying agent script..." -ForegroundColor Green
$sourceScript = "$PSScriptRoot\..\node-agent\hermes_node_agent.py"
if (-not (Test-Path $sourceScript)) {
Write-Host "ERROR: Agent script not found at $sourceScript" -ForegroundColor Red
Write-Host "Make sure you run this installer from the project root or adjust paths." -ForegroundColor Yellow
exit 1
}
Copy-Item $sourceScript "$agentDir\hermes-node-agent.py" -Force
Write-Host " Installed to: $agentDir\hermes-node-agent.py" -ForegroundColor Gray
# ── 6. Create config.json if missing ─────────────────────────────────────────
Write-Host "[5/7] Checking configuration..." -ForegroundColor Green
$configPath = "$configDir\config.json"
if (-not (Test-Path $configPath)) {
Write-Host " Creating example config (YOU MUST EDIT THIS!)" -ForegroundColor Yellow
# Generate a random token
$tokenBytes = New-Object byte[] 16
(New-Object System.Security.Cryptography.RNGCryptoServiceProvider).GetBytes($tokenBytes)
$token = ($tokenBytes | ForEach-Object { $_.ToString("x2") }) -join ''
$config = @{
gateway_url = "wss://YOUR-GATEWAY-HOST:8765"
node_name = $env:COMPUTERNAME
token = $token
sexec_path = "$env:USERPROFILE\.openclaw\skills\sexec\sexec.ps1"
reconnect_interval = 5
heartbeat_interval = 30
} | ConvertTo-Json -Depth 3
$config | Out-File -FilePath $configPath -Encoding UTF8
Write-Host " Config written to: $configPath" -ForegroundColor Yellow
Write-Host " ⚠️ EDIT THIS FILE: Set gateway_url and verify node_name/token" -ForegroundColor Yellow
} else {
Write-Host " Config already exists, skipping." -ForegroundColor Gray
}
# ── 7. Check for NSSM and offer service registration ──────────────────────────
Write-Host "[6/7] Service registration..." -ForegroundColor Green
$nssmPath = Get-Command nssm -ErrorAction SilentlyContinue
if ($nssmPath) {
Write-Host " NSSM found — installing as Windows service..." -ForegroundColor Green
& nssm install HermesNodeAgent "`"$pythonCmd`"" "`"$agentDir\hermes-node-agent.py`" --config `"$configPath`""
if ($LASTEXITCODE -ne 0) {
Write-Host " NSSM install failed, will use Task Scheduler instead" -ForegroundColor Yellow
$nssmPath = $null
} else {
nssm set HermesNodeAgent AppDirectory "`"$agentDir`""
nssm set HermesNodeAgent Start SERVICE_AUTO_START
nssm set HermesNodeAgent AppRestartDelay 5000
Write-Host " Service 'HermesNodeAgent' registered with NSSM" -ForegroundColor Green
}
}
if (-not $nssmPath) {
Write-Host " NSSM not found — registering via Task Scheduler..." -ForegroundColor Yellow
Write-Host " (Download NSSM from https://nssm.cc/ for better service management)" -ForegroundColor Gray
$action = New-ScheduledTaskAction -Execute "`"$pythonCmd`"" -Argument "`"$agentDir\hermes-node-agent.py`" --config `"$configPath`""
$trigger = New-ScheduledTaskTrigger -AtStartup
$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable -ExecutionTimeLimit 0
$principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest
Register-ScheduledTask -TaskName "HermesNodeAgent" -Action $action -Trigger $trigger -Settings $settings -Principal $principal -Force | Out-Null
Write-Host " Scheduled task 'HermesNodeAgent' created" -ForegroundColor Green
}
# ── 8. Summary ────────────────────────────────────────────────────────────────
Write-Host ""
Write-Host "✅ Installation complete!" -ForegroundColor Green
Write-Host ""
Write-Host "=== Next Steps ===" -ForegroundColor Cyan
Write-Host "1. Edit config: notepad $configPath" -ForegroundColor White
Write-Host " → Set gateway_url to your gateway's address (wss://host:8765)" -ForegroundColor Gray
Write-Host " → Verify token matches the entry in gateway's config.json" -ForegroundColor Gray
Write-Host ""
Write-Host "2. Verify service:" -ForegroundColor White
if ($nssmPath) {
Write-Host " nssm status HermesNodeAgent" -ForegroundColor Gray
Write-Host " nssm start HermesNodeAgent" -ForegroundColor Gray
} else {
Write-Host " Get-ScheduledTask HermesNodeAgent" -ForegroundColor Gray
Write-Host " Start-ScheduledTask HermesNodeAgent" -ForegroundColor Gray
}
Write-Host ""
Write-Host "3. Check logs:" -ForegroundColor White
Write-Host " Get-Content $configDir\hermes-node-agent.log -Wait -Tail 50" -ForegroundColor Gray
Write-Host ""
Write-Host "=== Important ===" -ForegroundColor Yellow
Write-Host "The agent runs as SYSTEM if using Task Scheduler, or as LocalSystem if using NSSM." -ForegroundColor Gray
Write-Host "Ensure the sexec.ps1 script exists at the configured path if using permissions." -ForegroundColor Gray
Write-Host ""
#!/bin/bash
# Hermes Node Agent Installation Script
# Installs the node agent on a remote machine
# Hermes Node Agent Installer — Linux Version
# Installs the Hermes Node Agent as a SysV init service
# Requires: bash, Python 3, pip, root (for service)
set -e
echo "=== Hermes Node Agent Installer ==="
echo "=== Hermes Node Agent Installer (Linux) ==="
echo ""
# Check if running as root
# Check if running as root (for service setup)
if [ "$EUID" -eq 0 ]; then
echo "ERROR: Do not run as root. Run as the user who will run the agent."
exit 1
RUN_AS_ROOT=true
else
RUN_AS_ROOT=false
echo "⚠️ Not running as root — skipping service installation."
echo " To install as a service, run: sudo $0"
echo ""
fi
# Check for Python 3
if ! command -v python3 &> /dev/null; then
echo "ERROR: Python 3 is required but not found."
echo "ERROR: Python 3 is required but not found."
exit 1
fi
echo "✓ Python: $(python3 --version)"
# Check for pip
if ! command -v pip3 &> /dev/null; then
echo "ERROR: pip3 is required but not found."
echo "Install with: sudo apt install python3-pip"
echo "❌ ERROR: pip3 is required but not found."
if [ "$RUN_AS_ROOT" = true ]; then
echo " Install with: apt install python3-pip"
else
echo " Install with: pip install --user websockets"
fi
exit 1
fi
# Install websockets library
echo "[1/6] Installing Python dependencies..."
apt-get update
apt-get install -y python3-websockets
echo "[1/5] Installing Python dependencies..."
pip3 install --quiet websockets 2>/dev/null || pip install --quiet websockets
if [ $? -ne 0 ]; then
echo "❌ Failed to install websockets. Try: pip3 install websockets"
exit 1
fi
echo "✓ websockets library installed"
# Create config directory
echo "[2/6] Creating config directory..."
sudo mkdir -p /etc/hermes-node
sudo chown $USER:$USER /etc/hermes-node
# Determine install locations
if [ "$RUN_AS_ROOT" = true ]; then
AGENT_DIR="/usr/local/bin"
CONFIG_DIR="/etc/hermes-node"
SERVICE_FILE="$(pwd)/node-agent/hermes-node-agent.init.d"
else
AGENT_DIR="$HOME/.local/bin"
CONFIG_DIR="$HOME/.config/hermes-node"
SERVICE_FILE=""
fi
mkdir -p "$AGENT_DIR"
mkdir -p "$CONFIG_DIR"
# Copy agent script
echo "[3/6] Installing agent script..."
sudo cp hermes_node_agent.py /usr/local/bin/hermes-node-agent
sudo chmod +x /usr/local/bin/hermes-node-agent
echo "[2/5] Installing agent script..."
cp "$(pwd)/node-agent/hermes_node_agent.py" "$AGENT_DIR/hermes-node-agent"
chmod +x "$AGENT_DIR/hermes-node-agent"
echo "✓ Installed: $AGENT_DIR/hermes-node-agent"
# Create example config if it doesn't exist
if [ ! -f /etc/hermes-node/config.json ]; then
echo "[4/6] Creating example config..."
cat > /etc/hermes-node/config.json <<EOF
# Create config file if missing
echo "[3/5] Checking configuration..."
if [ ! -f "$CONFIG_DIR/config.json" ]; then
echo "[4/5] Creating config file..."
TOKEN=$(python3 -c "import secrets; print(secrets.token_hex(16))")
cat > "$CONFIG_DIR/config.json" << EOF
{
"gateway_url": "ws://192.168.42.115:8765",
"gateway_url": "ws://YOUR-GATEWAY-HOST:8765",
"node_name": "$(hostname)",
"token": "CHANGE-ME-$(openssl rand -hex 16)",
"token": "$TOKEN",
"sexec_path": "$HOME/.openclaw/skills/sexec/sexec.sh",
"reconnect_interval": 5,
"heartbeat_interval": 30
}
EOF
echo " ⚠️ Config created at /etc/hermes-node/config.json"
echo " ⚠️ EDIT THIS FILE: Set gateway_url, node_name, and token"
echo "✓ Config: $CONFIG_DIR/config.json"
echo ""
echo "⚠️ IMPORTANT: Edit $CONFIG_DIR/config.json"
echo " → Set gateway_url to your gateway (e.g., wss://zeiss:8765)"
echo " → Set token to match gateway's token for this node"
else
echo "[4/6] Config already exists, skipping..."
echo "[4/5] Config already exists → skipping"
echo " Config: $CONFIG_DIR/config.json"
fi
# Install SysV init service
echo "[5/6] Installing SysV init service..."
sudo cp hermes-node-agent.init.d /etc/init.d/hermes-node-agent
sudo chmod +x /etc/init.d/hermes-node-agent
sudo update-rc.d hermes-node-agent defaults 2>/dev/null || true
# Enable but don't start (user needs to configure first)
echo "[6/6] Service configured..."
# Install SysV init service (root only)
if [ "$RUN_AS_ROOT" = true ] && [ -f "$SERVICE_FILE" ]; then
echo "[5/5] Installing SysV init service..."
cp "$SERVICE_FILE" /etc/init.d/hermes-node-agent
chmod +x /etc/init.d/hermes-node-agent
update-rc.d hermes-node-agent defaults 2>/dev/null || true
echo "✓ Service: /etc/init.d/hermes-node-agent"
echo ""
echo "Service commands:"
echo " /etc/init.d/hermes-node-agent start|stop|restart|status"
else
echo "[5/5] Skipping service installation (not root)"
echo ""
fi
echo ""
echo "✅ Installation complete!"
echo "=== Installation Complete ==="
echo ""
echo "Next steps:"
echo " 1. Edit /etc/hermes-node/config.json with your gateway URL and token"
echo " 2. Ensure sexec.sh is installed at the configured path"
echo " 3. Start the agent: /etc/init.d/hermes-node-agent start"
echo " 4. Check status: /etc/init.d/hermes-node-agent status"
echo " 5. View logs: tail -f /var/log/hermes-node-agent.log"
echo " 1. Edit config: $CONFIG_DIR/config.json"
echo " 2. Start agent: $AGENT_DIR/hermes-node-agent --config $CONFIG_DIR/config.json"
echo " 3. Verify logs: tail -f /tmp/hermes-node-agent.log"
echo ""
echo "For Windows nodes, see WINDOWS_INSTALL.md"
echo "For full deployment guide, see DEPLOYMENT.md"
# Hermes Node Agent — Windows Package Build Instructions
This directory contains the Windows-specific components for building a professional installer.
## Components
```
windows/
├── agent-manager.py # System tray GUI application
├── installer.iss # Inno Setup installer script
├── build.py # Automated build script
├── nssm.exe # Service wrapper (download separately)
├── icon.ico # Application icon (optional)
├── dist/ # PyInstaller output (generated)
│ ├── hermes-node-agent.exe
│ └── hermes-node-manager.exe
├── build/ # PyInstaller temp files (generated)
└── Output/ # Inno Setup output (generated)
└── hermes-node-agent-installer.exe
```
## Prerequisites
### Required Software
1. **Python 3.9+** with pip
- Download: https://www.python.org/downloads/
- Ensure "Add Python to PATH" is checked during install
2. **PyInstaller** (for creating .exe files)
```cmd
pip install pyinstaller
```
3. **Inno Setup 6** (for creating installer)
- Download: https://jrsoftware.org/isdl.php
- Install to default location: `C:\Program Files (x86)\Inno Setup 6\`
4. **NSSM** (Non-Sucking Service Manager)
- Download: https://nssm.cc/download
- Extract `nssm.exe` (64-bit version) to this `windows/` directory
### Python Dependencies
```cmd
pip install websockets pystray pillow wxpython win10toast pyinstaller
```
## Building the Installer
### Automated Build (Recommended)
Run the build script from the project root:
```cmd
cd hermes-node-protocol
python windows\build.py
```
This will:
1. Clean previous builds
2. Install Python dependencies
3. Build `hermes-node-agent.exe` with PyInstaller
4. Build `hermes-node-manager.exe` with PyInstaller
5. Verify NSSM is present
6. Compile the installer with Inno Setup
**Output:** `windows\Output\hermes-node-agent-installer.exe`
### Manual Build
If the automated script fails, build manually:
#### Step 1: Build Agent Executable
```cmd
cd hermes-node-protocol
pyinstaller --onefile --name hermes-node-agent --console node-agent\hermes_node_agent.py
```
Output: `dist\hermes-node-agent.exe`
#### Step 2: Build Manager GUI Executable
```cmd
pyinstaller --onefile --name hermes-node-manager --windowed windows\agent-manager.py
```
Output: `dist\hermes-node-manager.exe`
#### Step 3: Move Executables
```cmd
move dist\hermes-node-agent.exe windows\dist\
move dist\hermes-node-manager.exe windows\dist\
```
#### Step 4: Compile Installer
```cmd
"C:\Program Files (x86)\Inno Setup 6\ISCC.exe" windows\installer.iss
```
Output: `windows\Output\hermes-node-agent-installer.exe`
## Testing the Installer
### Test on Clean VM
1. Create a Windows 10/11 VM (VirtualBox, Hyper-V, VMware)
2. **Do not** install Python or any dependencies
3. Copy `hermes-node-agent-installer.exe` to the VM
4. Run the installer as Administrator
5. Verify:
- Service installs: `sc query HermesNodeAgent`
- Manager starts: Check system tray for "H" icon
- Config editor opens: Right-click tray → Configuration
- Logs viewer works: Right-click tray → Logs
### Test Service Management
```cmd
REM Start service
sc start HermesNodeAgent
REM Check status
sc query HermesNodeAgent
REM Stop service
sc stop HermesNodeAgent
```
### Test Uninstaller
1. Open **Settings** → **Apps** → **Apps & features**
2. Find **Hermes Node Agent**
3. Click **Uninstall**
4. Verify all files removed except config/logs
## Customization
### Change Application Icon
1. Create or download a `.ico` file (16x16, 32x32, 48x48, 256x256)
2. Save as `windows\icon.ico`
3. Rebuild with `python windows\build.py`
### Modify Installer Appearance
Edit `windows\installer.iss`:
- **App name/version**: `[Setup]` section
- **Install location**: `DefaultDirName`
- **Start menu group**: `DefaultGroupName`
- **License agreement**: Add `LicenseFile=LICENSE.txt` to `[Setup]`
- **Wizard images**: Add `WizardImageFile` and `WizardSmallImageFile`
### Add Custom Files
Edit `windows\installer.iss` → `[Files]` section:
```iss
Source: "path\to\file.txt"; DestDir: "{app}"; Flags: ignoreversion
```
## Troubleshooting
### PyInstaller: "Module not found"
Install missing module:
```cmd
pip install <module-name>
```
Then rebuild.
### PyInstaller: "Failed to execute script"
Run the .exe from command line to see error:
```cmd
cd windows\dist
hermes-node-agent.exe --config test.json
```
### Inno Setup: "Cannot find file"
Verify all `Source:` paths in `installer.iss` exist:
- `windows\nssm.exe`
- `windows\dist\hermes-node-agent.exe`
- `windows\dist\hermes-node-manager.exe`
- `node-agent\hermes_node_agent.py`
### Manager GUI: "wxPython not found"
```cmd
pip install wxpython
```
Note: wxPython can take 5-10 minutes to install (large package).
### Service won't start after install
Check Event Viewer:
1. Press `Win+R`, type `eventvwr.msc`, press Enter
2. Navigate to **Windows Logs** → **Application**
3. Look for errors from source "HermesNodeAgent"
Common causes:
- Config file missing or invalid JSON
- Python runtime not bundled correctly (use PyInstaller `--onefile`)
- Missing DLL dependencies (use `--hidden-import` in PyInstaller)
## Distribution
### Signing the Installer (Optional but Recommended)
Sign with a code signing certificate to avoid Windows SmartScreen warnings:
```cmd
signtool sign /f certificate.pfx /p password /t http://timestamp.digicert.com windows\Output\hermes-node-agent-installer.exe
```
### Creating a Portable Version
To create a portable (no-install) version:
1. Copy `windows\dist\hermes-node-agent.exe` to a folder
2. Copy `windows\dist\hermes-node-manager.exe` to the same folder
3. Create `config.json` in the same folder
4. Zip the folder
Users can run `hermes-node-agent.exe --config config.json` directly (no service).
## File Sizes (Approximate)
- `hermes-node-agent.exe`: ~15 MB (includes Python runtime)
- `hermes-node-manager.exe`: ~25 MB (includes wxPython)
- `hermes-node-agent-installer.exe`: ~45 MB (compressed)
## Build Environment
Tested on:
- Windows 10 21H2 (x64)
- Windows 11 22H2 (x64)
- Python 3.9.13, 3.10.11, 3.11.4
- PyInstaller 5.13.0
- Inno Setup 6.2.2
## Support
For build issues:
- Check PyInstaller docs: https://pyinstaller.org/
- Check Inno Setup docs: https://jrsoftware.org/ishelp/
- Review `windows\build.py` for detailed steps
This diff is collapsed.
# Hermes Node Agent — Windows Build Script
# Builds the Windows installer package using PyInstaller and Inno Setup
#
# Prerequisites:
# - Python 3.9+ with pip
# - PyInstaller: pip install pyinstaller
# - Inno Setup 6: https://jrsoftware.org/isdl.php
# - NSSM: https://nssm.cc/download (place nssm.exe in windows/ folder)
#
# Usage:
# cd hermes-node-protocol
# python windows/build.py
import os
import sys
import shutil
import subprocess
from pathlib import Path
# ── Configuration ────────────────────────────────────────────────────────────
PROJECT_ROOT = Path(__file__).parent.parent
WINDOWS_DIR = PROJECT_ROOT / 'windows'
NODE_AGENT_DIR = PROJECT_ROOT / 'node-agent'
DIST_DIR = WINDOWS_DIR / 'dist'
BUILD_DIR = WINDOWS_DIR / 'build'
AGENT_SCRIPT = NODE_AGENT_DIR / 'hermes_node_agent.py'
MANAGER_SCRIPT = WINDOWS_DIR / 'agent-manager.py'
INSTALLER_SCRIPT = WINDOWS_DIR / 'installer.iss'
PYINSTALLER_AGENT_SPEC = WINDOWS_DIR / 'agent.spec'
PYINSTALLER_MANAGER_SPEC = WINDOWS_DIR / 'manager.spec'
INNO_SETUP_COMPILER = r'C:\Program Files (x86)\Inno Setup 6\ISCC.exe'
# ── Step 1: Clean previous builds ───────────────────────────────────────────
def clean():
print("[1/5] Cleaning previous builds...")
for d in [DIST_DIR, BUILD_DIR]:
if d.exists():
shutil.rmtree(d)
print(f" Removed: {d}")
DIST_DIR.mkdir(parents=True, exist_ok=True)
BUILD_DIR.mkdir(parents=True, exist_ok=True)
print(" ✓ Clean complete")
# ── Step 2: Install Python dependencies ─────────────────────────────────────
def install_deps():
print("[2/5] Installing Python dependencies...")
deps = ['websockets', 'pyinstaller', 'pystray', 'pillow', 'wxpython', 'win10toast']
for dep in deps:
print(f" Installing {dep}...")
subprocess.run([sys.executable, '-m', 'pip', 'install', '--quiet', dep], check=True)
print(" ✓ Dependencies installed")
# ── Step 3: Build executables with PyInstaller ──────────────────────────────
def build_executables():
print("[3/5] Building executables with PyInstaller...")
# Build agent executable
print(" Building hermes-node-agent.exe...")
subprocess.run([
'pyinstaller',
'--onefile',
'--name', 'hermes-node-agent',
'--distpath', str(DIST_DIR),
'--workpath', str(BUILD_DIR / 'agent'),
'--specpath', str(WINDOWS_DIR),
'--console',
'--icon', str(WINDOWS_DIR / 'icon.ico') if (WINDOWS_DIR / 'icon.ico').exists() else 'NONE',
str(AGENT_SCRIPT)
], check=True, cwd=str(PROJECT_ROOT))
# Build manager GUI executable
print(" Building hermes-node-manager.exe...")
subprocess.run([
'pyinstaller',
'--onefile',
'--name', 'hermes-node-manager',
'--distpath', str(DIST_DIR),
'--workpath', str(BUILD_DIR / 'manager'),
'--specpath', str(WINDOWS_DIR),
'--windowed', # No console window
'--icon', str(WINDOWS_DIR / 'icon.ico') if (WINDOWS_DIR / 'icon.ico').exists() else 'NONE',
str(MANAGER_SCRIPT)
], check=True, cwd=str(PROJECT_ROOT))
print(" ✓ Executables built")
# ── Step 4: Download NSSM if missing ────────────────────────────────────────
def check_nssm():
print("[4/5] Checking for NSSM...")
nssm_path = WINDOWS_DIR / 'nssm.exe'
if not nssm_path.exists():
print(" ⚠️ NSSM not found. Download from https://nssm.cc/download")
print(f" Place nssm.exe in: {WINDOWS_DIR}")
print(" (64-bit version recommended)")
return False
print(f" ✓ NSSM found: {nssm_path}")
return True
# ── Step 5: Build installer with Inno Setup ─────────────────────────────────
def build_installer():
print("[5/5] Building installer with Inno Setup...")
if not Path(INNO_SETUP_COMPILER).exists():
print(f" ⚠️ Inno Setup not found at: {INNO_SETUP_COMPILER}")
print(" Download from: https://jrsoftware.org/isdl.php")
print(" Or adjust INNO_SETUP_COMPILER path in this script")
return False
# Run Inno Setup compiler
subprocess.run([
INNO_SETUP_COMPILER,
str(INSTALLER_SCRIPT)
], check=True, cwd=str(WINDOWS_DIR))
print(" ✓ Installer built")
# Find output
output_dir = WINDOWS_DIR / 'Output'
if output_dir.exists():
installers = list(output_dir.glob('*.exe'))
if installers:
print(f"\n✅ Installer ready: {installers[0]}")
return True
print(" ⚠️ Installer output not found")
return False
# ── Main ─────────────────────────────────────────────────────────────────────
def main():
print("=== Hermes Node Agent — Windows Build Script ===\n")
# Verify we're on Windows
if sys.platform not in ('win32', 'cygwin'):
print("❌ This script must be run on Windows")
sys.exit(1)
# Verify project structure
if not AGENT_SCRIPT.exists():
print(f"❌ Agent script not found: {AGENT_SCRIPT}")
sys.exit(1)
if not MANAGER_SCRIPT.exists():
print(f"❌ Manager script not found: {MANAGER_SCRIPT}")
sys.exit(1)
try:
clean()
install_deps()
build_executables()
if not check_nssm():
print("\n⚠️ Build incomplete: NSSM missing")
print(" Download NSSM and re-run this script")
sys.exit(1)
if build_installer():
print("\n✅ Build complete!")
print("\nNext steps:")
print(" 1. Test the installer on a clean Windows VM")
print(" 2. Distribute: windows/Output/hermes-node-agent-installer.exe")
else:
print("\n⚠️ Installer build failed")
print(" Executables are available in: windows/dist/")
sys.exit(1)
except subprocess.CalledProcessError as e:
print(f"\n❌ Build failed: {e}")
sys.exit(1)
except KeyboardInterrupt:
print("\n\n⚠️ Build cancelled by user")
sys.exit(1)
if __name__ == '__main__':
main()
"""Hermes Node Agent Windows Installer — GUI (Inno Setup)
Creates a professional Windows installer (.exe) that bundles:
• Python runtime (embedded)
• Hermes node agent script
• Hermes node manager GUI
• NSSM service wrapper
• All Python dependencies (websockets)
The installer creates:
• Service: HermesNodeAgent (runs as LocalSystem)
• System tray manager (starts at user login)
• Configuration in %PROGRAMDATA%\hermes-node\
• Uninstaller in Windows "Add/Remove Programs"
Author: Lisa (Hermes AI)
Date: 2026-04-30
"""
[Setup]
AppName=Hermes Node Agent
AppVersion=2.0
AppCopyright=Copyright (c) 2026 Lisa (Hermes AI)
DefaultDirName={autopf}\Hermes Node
DefaultGroupName=Hermes Node
UninstallDisplayIcon={app}\hermes-node-manager.exe
OutputBaseFilename=hermes-node-agent-installer
Compression=lzma
SolidCompression=yes
WizardStyle=modern
PrivilegesRequired=administrator
ArchitecturesInstallIn64BitMode=x64
DisableProgramGroupPage=no
[Languages]
Name: "english"; MessagesFile: "compiler:Default.isl"
Name: "italian"; MessagesFile: "compiler:Italian.isl"
[Tasks]
Name: "starttray"; Description: "Start Hermes Node Manager at login (recommended)"; GroupDescription: "Additional icons:"; Flags: unchecked
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "Additional icons:"; Flags: unchecked
[Files]
; ── NSSM (service manager)
Source: "windows\nssm.exe"; DestDir: "{app}"; Flags: ignoreversion
; ── Hermes agent script
Source: "node-agent\hermes_node_agent.py"; DestDir: "{app}"; Flags: ignoreversion
; ── Agent manager GUI
Source: "windows\agent-manager.py"; DestDir: "{app}"; Flags: ignoreversion
; ── Python embedded runtime (zipapp)
Source: "windows\python-embed.zip"; DestDir: "{app}"; Flags: ignoreversion
; ── PyInstaller-converted executables (these will be built first)
Source: "windows\dist\hermes-node-agent.exe"; DestDir: "{app}"; Flags: ignoreversion
Source: "windows\dist\hermes-node-manager.exe"; DestDir: "{app}"; Flags: ignoreversion
; ── Permissions script template
Source: "windows\sexec-template.ps1"; DestName: "sexec-template.ps1"; DestDir: "{app}"; Flags: ignoreversion
; ── Documentation
Source: "WINDOWS_INSTALL.md"; DestName: "README.md"; DestDir: "{app}"; Flags: isreadme
[Icons]
Name: "{group}\Hermes Node Manager"; Filename: "{app}\hermes-node-manager.exe"
Name: "{group}\Uninstall Hermes Node Agent"; Filename: "{uninstallexe}"
Name: "{commondesktop}\Hermes Node Agent"; Filename: "{app}\hermes-node-manager.exe"; Tasks: desktopicon
[Run]
; Start the manager on first install (if user chose the task)
Filename: "{app}\hermes-node-manager.exe"; Description: "Start Hermes Node Manager"; Flags: postinstall nowait skipifsilent; Tasks: starttray
[Code]
function InitializeSetup(): Boolean;
begin
// Show welcome page
Result := True;
end;
procedure CurStepChanged(CurStep: TSetupStep);
begin
if CurStep = ssPostInstall then begin
// Create config directory
ForceDirectories(ExpandConstant('{commonappdata}\hermes-node'));
end;
end;
function NextButtonClick(CurPageID: Integer): Boolean;
begin
Result := True;
if CurPageID = wpSelectDir then begin
// Validate that Program Files is writable (needs admin)
if not IsAdminLoggedOn then begin
MsgBox('This installer requires Administrator privileges to install the service.', mbError, MB_OK);
Result := False;
end;
end;
end;
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment