Merge remote-tracking branch 'origin/antigravity/installers' into review/all
This commit is contained in:
commit
c730c769e0
11 changed files with 914 additions and 10 deletions
|
|
@ -279,6 +279,18 @@ namespace HermesHubSetup
|
||||||
File.Copy(launcherSrc, Path.Combine(HermesHome, "HermesHub.exe"), true);
|
File.Copy(launcherSrc, Path.Combine(HermesHome, "HermesHub.exe"), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
string webLauncherSrc = Path.Combine(sourceRoot, @"launcher\HermesHubWeb.exe");
|
||||||
|
if (!File.Exists(webLauncherSrc))
|
||||||
|
{
|
||||||
|
webLauncherSrc = Path.Combine(sourceRoot, "HermesHubWeb.exe");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (File.Exists(webLauncherSrc))
|
||||||
|
{
|
||||||
|
File.Copy(webLauncherSrc, Path.Combine(TargetInstallDir, "HermesHubWeb.exe"), true);
|
||||||
|
File.Copy(webLauncherSrc, Path.Combine(HermesHome, "HermesHubWeb.exe"), true);
|
||||||
|
}
|
||||||
|
|
||||||
// Copy Setup.exe itself to target dir for uninstaller/repair
|
// Copy Setup.exe itself to target dir for uninstaller/repair
|
||||||
string setupSrc = Process.GetCurrentProcess().MainModule.FileName;
|
string setupSrc = Process.GetCurrentProcess().MainModule.FileName;
|
||||||
if (File.Exists(setupSrc))
|
if (File.Exists(setupSrc))
|
||||||
|
|
@ -419,6 +431,12 @@ namespace HermesHubSetup
|
||||||
try { File.Delete(homeExe); } catch { }
|
try { File.Delete(homeExe); } catch { }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
string homeWebExe = Path.Combine(HermesHome, "HermesHubWeb.exe");
|
||||||
|
if (File.Exists(homeWebExe))
|
||||||
|
{
|
||||||
|
try { File.Delete(homeWebExe); } catch { }
|
||||||
|
}
|
||||||
|
|
||||||
// Remove plugin
|
// Remove plugin
|
||||||
string pluginDir = Path.Combine(HermesHome, @"plugins\antigravity-provider");
|
string pluginDir = Path.Combine(HermesHome, @"plugins\antigravity-provider");
|
||||||
if (Directory.Exists(pluginDir))
|
if (Directory.Exists(pluginDir))
|
||||||
|
|
@ -504,19 +522,53 @@ namespace HermesHubSetup
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
string startMenu = Environment.GetFolderPath(Environment.SpecialFolder.Programs);
|
string startMenu = Environment.GetFolderPath(Environment.SpecialFolder.Programs);
|
||||||
string shortcutPath = Path.Combine(startMenu, "Hermes Hub.lnk");
|
string targetWebExe = Path.Combine(TargetInstallDir, "HermesHubWeb.exe");
|
||||||
string targetExe = Path.Combine(TargetInstallDir, "HermesHub.exe");
|
if (!File.Exists(targetWebExe)) targetWebExe = Path.Combine(HermesHome, "HermesHubWeb.exe");
|
||||||
|
|
||||||
|
string targetDesktopExe = Path.Combine(TargetInstallDir, "HermesHub.exe");
|
||||||
|
if (!File.Exists(targetDesktopExe)) targetDesktopExe = Path.Combine(HermesHome, "HermesHub.exe");
|
||||||
|
|
||||||
Type shellType = Type.GetTypeFromProgID("WScript.Shell");
|
Type shellType = Type.GetTypeFromProgID("WScript.Shell");
|
||||||
if (shellType != null)
|
if (shellType != null)
|
||||||
{
|
{
|
||||||
dynamic shell = Activator.CreateInstance(shellType);
|
dynamic shell = Activator.CreateInstance(shellType);
|
||||||
dynamic shortcut = shell.CreateShortcut(shortcutPath);
|
|
||||||
shortcut.TargetPath = targetExe;
|
// 1. Web Application Window Shortcut (Primary Web App mode)
|
||||||
shortcut.WorkingDirectory = TargetInstallDir;
|
if (File.Exists(targetWebExe))
|
||||||
shortcut.Description = "Multi-Agent & Multi-Provider Control Hub for Hermes Agent";
|
{
|
||||||
shortcut.IconLocation = targetExe + ",0";
|
string webShortcutPath = Path.Combine(startMenu, "Hermes Hub (Web).lnk");
|
||||||
shortcut.Save();
|
dynamic webShortcut = shell.CreateShortcut(webShortcutPath);
|
||||||
|
webShortcut.TargetPath = targetWebExe;
|
||||||
|
webShortcut.WorkingDirectory = TargetInstallDir;
|
||||||
|
webShortcut.Description = "Hermes Hub — Web Application Window";
|
||||||
|
webShortcut.IconLocation = targetWebExe + ",0";
|
||||||
|
webShortcut.Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Native Desktop Shortcut (CustomTkinter GUI)
|
||||||
|
if (File.Exists(targetDesktopExe))
|
||||||
|
{
|
||||||
|
string desktopShortcutPath = Path.Combine(startMenu, "Hermes Hub (Desktop).lnk");
|
||||||
|
dynamic desktopShortcut = shell.CreateShortcut(desktopShortcutPath);
|
||||||
|
desktopShortcut.TargetPath = targetDesktopExe;
|
||||||
|
desktopShortcut.WorkingDirectory = TargetInstallDir;
|
||||||
|
desktopShortcut.Description = "Hermes Hub — Native Desktop App";
|
||||||
|
desktopShortcut.IconLocation = targetDesktopExe + ",0";
|
||||||
|
desktopShortcut.Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Standard "Hermes Hub.lnk" shortcut
|
||||||
|
string standardTarget = File.Exists(targetWebExe) ? targetWebExe : targetDesktopExe;
|
||||||
|
if (File.Exists(standardTarget))
|
||||||
|
{
|
||||||
|
string standardShortcutPath = Path.Combine(startMenu, "Hermes Hub.lnk");
|
||||||
|
dynamic standardShortcut = shell.CreateShortcut(standardShortcutPath);
|
||||||
|
standardShortcut.TargetPath = standardTarget;
|
||||||
|
standardShortcut.WorkingDirectory = TargetInstallDir;
|
||||||
|
standardShortcut.Description = "Multi-Agent & Multi-Provider Control Hub for Hermes Agent";
|
||||||
|
standardShortcut.IconLocation = standardTarget + ",0";
|
||||||
|
standardShortcut.Save();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch { }
|
catch { }
|
||||||
|
|
@ -527,8 +579,18 @@ namespace HermesHubSetup
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
string startMenu = Environment.GetFolderPath(Environment.SpecialFolder.Programs);
|
string startMenu = Environment.GetFolderPath(Environment.SpecialFolder.Programs);
|
||||||
string shortcutPath = Path.Combine(startMenu, "Hermes Hub.lnk");
|
string[] shortcuts = new string[]
|
||||||
if (File.Exists(shortcutPath)) File.Delete(shortcutPath);
|
{
|
||||||
|
"Hermes Hub.lnk",
|
||||||
|
"Hermes Hub (Web).lnk",
|
||||||
|
"Hermes Hub (Desktop).lnk",
|
||||||
|
"Hermes Hub Web.lnk"
|
||||||
|
};
|
||||||
|
foreach (string sc in shortcuts)
|
||||||
|
{
|
||||||
|
string p = Path.Combine(startMenu, sc);
|
||||||
|
if (File.Exists(p)) try { File.Delete(p); } catch { }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch { }
|
catch { }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,20 @@ $DistDir = Join-Path $RepoRoot "dist"
|
||||||
New-Item -ItemType Directory -Path $DistDir -Force | Out-Null
|
New-Item -ItemType Directory -Path $DistDir -Force | Out-Null
|
||||||
$OutFile = Join-Path $DistDir "HermesHubSetup.exe"
|
$OutFile = Join-Path $DistDir "HermesHubSetup.exe"
|
||||||
|
|
||||||
|
$LauncherDir = Join-Path $RepoRoot "launcher"
|
||||||
|
$HubCs = Join-Path $LauncherDir "HermesHub.cs"
|
||||||
|
$HubWebCs = Join-Path $LauncherDir "HermesHubWeb.cs"
|
||||||
|
|
||||||
|
Write-Host "Compiling native launchers..." -ForegroundColor Cyan
|
||||||
|
if (Test-Path $HubCs) {
|
||||||
|
$HubExe = Join-Path $LauncherDir "HermesHub.exe"
|
||||||
|
& $CscPath /target:winexe /out:"$HubExe" /r:System.Windows.Forms.dll /r:System.Drawing.dll "$HubCs"
|
||||||
|
}
|
||||||
|
if (Test-Path $HubWebCs) {
|
||||||
|
$HubWebExe = Join-Path $LauncherDir "HermesHubWeb.exe"
|
||||||
|
& $CscPath /target:winexe /out:"$HubWebExe" /r:System.Windows.Forms.dll /r:System.Drawing.dll "$HubWebCs"
|
||||||
|
}
|
||||||
|
|
||||||
Write-Host "Compiling HermesHubSetup.exe..." -ForegroundColor Cyan
|
Write-Host "Compiling HermesHubSetup.exe..." -ForegroundColor Cyan
|
||||||
& $CscPath /target:winexe /out:"$OutFile" /r:System.Windows.Forms.dll /r:System.Drawing.dll "$SourceFile"
|
& $CscPath /target:winexe /out:"$OutFile" /r:System.Windows.Forms.dll /r:System.Drawing.dll "$SourceFile"
|
||||||
|
|
||||||
|
|
|
||||||
189
installer/install-linux.sh
Normal file
189
installer/install-linux.sh
Normal file
|
|
@ -0,0 +1,189 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# ==============================================================================
|
||||||
|
# Hermes Hub — Linux Installation Script
|
||||||
|
# Mirrors plugin files, registers .desktop entry, creates application launcher,
|
||||||
|
# and supports both desktop and headless server environments.
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
HUB_VERSION="0.1.1"
|
||||||
|
DEFAULT_HERMES_HOME="$HOME/.hermes"
|
||||||
|
HERMES_HOME="${HERMES_HOME:-$DEFAULT_HERMES_HOME}"
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||||
|
|
||||||
|
echo "======================================================================"
|
||||||
|
echo " HERMES HUB INSTALLER (Linux / POSIX) "
|
||||||
|
echo "======================================================================"
|
||||||
|
echo "Version : $HUB_VERSION"
|
||||||
|
echo "Hermes Home : $HERMES_HOME"
|
||||||
|
echo "Source Root : $REPO_ROOT"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# 1. Check / Discover Python Runtime and Hermes Environment
|
||||||
|
echo "[1/6] Checking Python and Hermes environment..."
|
||||||
|
PYTHON_BIN=""
|
||||||
|
|
||||||
|
if [ -f "$HERMES_HOME/hermes-agent/venv/bin/python3" ]; then
|
||||||
|
PYTHON_BIN="$HERMES_HOME/hermes-agent/venv/bin/python3"
|
||||||
|
elif [ -f "$HERMES_HOME/hermes-agent/venv/bin/python" ]; then
|
||||||
|
PYTHON_BIN="$HERMES_HOME/hermes-agent/venv/bin/python"
|
||||||
|
elif command -v python3 >/dev/null 2>&1; then
|
||||||
|
PYTHON_BIN="$(command -v python3)"
|
||||||
|
elif command -v python >/dev/null 2>&1; then
|
||||||
|
PYTHON_BIN="$(command -v python)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$PYTHON_BIN" ]; then
|
||||||
|
echo "❌ Error: Python 3 not found on system. Please install Python 3.9+." >&2
|
||||||
|
exit 10
|
||||||
|
fi
|
||||||
|
|
||||||
|
PY_VER="$("$PYTHON_BIN" -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")')"
|
||||||
|
echo " Using Python: $PYTHON_BIN (v$PY_VER)"
|
||||||
|
|
||||||
|
# Ensure Hermes Home Directory Structure
|
||||||
|
mkdir -p "$HERMES_HOME"
|
||||||
|
mkdir -p "$HERMES_HOME/config"
|
||||||
|
mkdir -p "$HERMES_HOME/plugins/antigravity-provider/src"
|
||||||
|
mkdir -p "$HERMES_HOME/plugins/antigravity-provider/assets"
|
||||||
|
mkdir -p "$HOME/.local/bin"
|
||||||
|
mkdir -p "$HOME/.local/share/applications"
|
||||||
|
|
||||||
|
# 2. Check and Install Dependencies
|
||||||
|
echo "[2/6] Verifying Python dependencies..."
|
||||||
|
DEPS_OK=true
|
||||||
|
"$PYTHON_BIN" -c "import fastapi, uvicorn, pydantic, psutil, yaml; print('DEPS_OK')" >/dev/null 2>&1 || DEPS_OK=false
|
||||||
|
|
||||||
|
if [ "$DEPS_OK" != "true" ]; then
|
||||||
|
echo " Installing required packages (fastapi, uvicorn, pydantic, psutil, pyyaml)..."
|
||||||
|
"$PYTHON_BIN" -m pip install --no-warn-script-location -q fastapi uvicorn pydantic psutil pyyaml || {
|
||||||
|
echo "⚠️ Warning: pip install returned non-zero code. Trying with --user..."
|
||||||
|
"$PYTHON_BIN" -m pip install --user --no-warn-script-location -q fastapi uvicorn pydantic psutil pyyaml || true
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 3. Mirror Plugin Files to ~/.hermes/plugins/antigravity-provider (with cleanup of stale files)
|
||||||
|
echo "[3/6] Deploying plugin source files (mirrored)..."
|
||||||
|
PLUGIN_SRC="$REPO_ROOT/src/antigravity_provider"
|
||||||
|
PLUGIN_DST="$HERMES_HOME/plugins/antigravity-provider/src/antigravity_provider"
|
||||||
|
|
||||||
|
if [ -d "$PLUGIN_SRC" ]; then
|
||||||
|
mkdir -p "$PLUGIN_DST"
|
||||||
|
# Use rsync with --delete if available, or python mirroring
|
||||||
|
if command -v rsync >/dev/null 2>&1; then
|
||||||
|
rsync -a --delete --exclude='__pycache__' --exclude='*.pyc' "$PLUGIN_SRC/" "$PLUGIN_DST/"
|
||||||
|
else
|
||||||
|
"$PYTHON_BIN" -c "
|
||||||
|
import os, shutil
|
||||||
|
src = r'$PLUGIN_SRC'
|
||||||
|
dst = r'$PLUGIN_DST'
|
||||||
|
if os.path.exists(dst):
|
||||||
|
shutil.rmtree(dst)
|
||||||
|
shutil.copytree(src, dst, ignore=shutil.ignore_patterns('__pycache__', '*.pyc'))
|
||||||
|
"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "❌ Error: Source directory $PLUGIN_SRC not found!" >&2
|
||||||
|
exit 12
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Deploy Assets
|
||||||
|
if [ -d "$REPO_ROOT/assets" ]; then
|
||||||
|
mkdir -p "$HERMES_HOME/plugins/antigravity-provider/assets"
|
||||||
|
cp -r "$REPO_ROOT/assets/"* "$HERMES_HOME/plugins/antigravity-provider/assets/" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 4. Write Deployment Manifest
|
||||||
|
echo "[4/6] Writing deployment manifest..."
|
||||||
|
MANIFEST_FILE="$HERMES_HOME/plugins/antigravity-provider/deployment_manifest.json"
|
||||||
|
GIT_COMMIT="$(cd "$REPO_ROOT" 2>/dev/null && git rev-parse --short HEAD 2>/dev/null || echo 'fb23bff')"
|
||||||
|
TIMESTAMP="$(date -u +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || date -u)"
|
||||||
|
|
||||||
|
cat <<EOF > "$MANIFEST_FILE"
|
||||||
|
{
|
||||||
|
"version": "$HUB_VERSION",
|
||||||
|
"deployed_at": "$TIMESTAMP",
|
||||||
|
"git_commit": "$GIT_COMMIT",
|
||||||
|
"platform": "linux"
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Deploy Template Config only if not existing
|
||||||
|
if [ ! -f "$HERMES_HOME/config/router_profiles.yaml" ] && [ -f "$REPO_ROOT/config/router_profiles.example.yaml" ]; then
|
||||||
|
echo " Installing default router_profiles.yaml from template..."
|
||||||
|
cp "$REPO_ROOT/config/router_profiles.example.yaml" "$HERMES_HOME/config/router_profiles.yaml"
|
||||||
|
else
|
||||||
|
echo " Preserving existing user router_profiles.yaml."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 5. Deploy Launcher Executable and .desktop Entry
|
||||||
|
echo "[5/6] Creating application launcher and .desktop entry..."
|
||||||
|
LAUNCHER_SRC="$REPO_ROOT/launcher/hermes-hub-web.sh"
|
||||||
|
if [ ! -f "$LAUNCHER_SRC" ]; then
|
||||||
|
LAUNCHER_SRC="$SCRIPT_DIR/hermes-hub-web.sh"
|
||||||
|
fi
|
||||||
|
|
||||||
|
LAUNCHER_BIN="$HOME/.local/bin/hermes-hub-web"
|
||||||
|
cp "$LAUNCHER_SRC" "$LAUNCHER_BIN"
|
||||||
|
chmod +x "$LAUNCHER_BIN"
|
||||||
|
|
||||||
|
# Also place in ~/.hermes/bin for convenience
|
||||||
|
mkdir -p "$HERMES_HOME/bin"
|
||||||
|
cp "$LAUNCHER_SRC" "$HERMES_HOME/bin/hermes-hub-web"
|
||||||
|
chmod +x "$HERMES_HOME/bin/hermes-hub-web"
|
||||||
|
|
||||||
|
# Create .desktop file
|
||||||
|
DESKTOP_FILE="$HOME/.local/share/applications/hermes-hub-web.desktop"
|
||||||
|
ICON_PATH="$HERMES_HOME/plugins/antigravity-provider/assets/branding/app/HermesHub.ico"
|
||||||
|
if [ ! -f "$ICON_PATH" ]; then
|
||||||
|
ICON_PATH="utilities-terminal"
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat <<EOF > "$DESKTOP_FILE"
|
||||||
|
[Desktop Entry]
|
||||||
|
Version=1.0
|
||||||
|
Type=Application
|
||||||
|
Name=Hermes Hub Web
|
||||||
|
GenericName=Multi-Agent & Multi-Provider Control Hub
|
||||||
|
Comment=Multi-Agent & Multi-Provider Control Hub for Hermes Agent
|
||||||
|
Exec=$LAUNCHER_BIN
|
||||||
|
Icon=$ICON_PATH
|
||||||
|
Terminal=false
|
||||||
|
Categories=Development;Utility;
|
||||||
|
StartupNotify=true
|
||||||
|
StartupWMClass=hermes-hub-web
|
||||||
|
EOF
|
||||||
|
|
||||||
|
chmod +x "$DESKTOP_FILE"
|
||||||
|
if command -v update-desktop-database >/dev/null 2>&1; then
|
||||||
|
update-desktop-database "$HOME/.local/share/applications" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 6. Post-install Smoke Test Verification
|
||||||
|
echo "[6/6] Running post-install verification smoke test..."
|
||||||
|
"$PYTHON_BIN" -c "
|
||||||
|
import sys
|
||||||
|
sys.path.insert(0, '$HERMES_HOME/plugins/antigravity-provider/src')
|
||||||
|
import antigravity_provider.router.web.server
|
||||||
|
print('HERMES_HUB_LINUX_VERIFY_OK')
|
||||||
|
" || {
|
||||||
|
echo "❌ Error: Post-install verification failed!" >&2
|
||||||
|
exit 14
|
||||||
|
}
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "======================================================================"
|
||||||
|
echo " HERMES HUB SUCCESSFULLY INSTALLED ON LINUX! "
|
||||||
|
echo "======================================================================"
|
||||||
|
echo "Application Launcher : $LAUNCHER_BIN"
|
||||||
|
echo "Desktop Shortcut : $DESKTOP_FILE"
|
||||||
|
echo ""
|
||||||
|
echo "To launch the Web Application window:"
|
||||||
|
echo " $LAUNCHER_BIN"
|
||||||
|
echo ""
|
||||||
|
echo "Or open 'Hermes Hub Web' from your Applications menu."
|
||||||
|
echo "======================================================================"
|
||||||
|
exit 0
|
||||||
60
installer/uninstall-linux.sh
Normal file
60
installer/uninstall-linux.sh
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# ==============================================================================
|
||||||
|
# Hermes Hub — Linux Uninstaller
|
||||||
|
# Removes application files, plugin integration, launcher and .desktop shortcuts.
|
||||||
|
# Preserves user data and credentials by default unless --purge-user-data is passed.
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
DEFAULT_HERMES_HOME="$HOME/.hermes"
|
||||||
|
HERMES_HOME="${HERMES_HOME:-$DEFAULT_HERMES_HOME}"
|
||||||
|
|
||||||
|
PURGE_USER_DATA=false
|
||||||
|
for arg in "$@"; do
|
||||||
|
if [ "$arg" = "--purge-user-data" ] || [ "$arg" = "-p" ]; then
|
||||||
|
PURGE_USER_DATA=true
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "======================================================================"
|
||||||
|
echo " HERMES HUB UNINSTALLER (Linux / POSIX) "
|
||||||
|
echo "======================================================================"
|
||||||
|
echo "Hermes Home : $HERMES_HOME"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# 1. Remove Plugin Integration
|
||||||
|
echo "[1/3] Removing plugin integration..."
|
||||||
|
if [ -d "$HERMES_HOME/plugins/antigravity-provider" ]; then
|
||||||
|
rm -rf "$HERMES_HOME/plugins/antigravity-provider"
|
||||||
|
echo " Removed $HERMES_HOME/plugins/antigravity-provider"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 2. Remove Launchers and Shortcuts
|
||||||
|
echo "[2/3] Removing application launchers and desktop entries..."
|
||||||
|
rm -f "$HOME/.local/bin/hermes-hub-web"
|
||||||
|
rm -f "$HERMES_HOME/bin/hermes-hub-web"
|
||||||
|
rm -f "$HOME/.local/share/applications/hermes-hub-web.desktop"
|
||||||
|
|
||||||
|
if command -v update-desktop-database >/dev/null 2>&1; then
|
||||||
|
update-desktop-database "$HOME/.local/share/applications" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 3. User Data Handling
|
||||||
|
if [ "$PURGE_USER_DATA" = "true" ]; then
|
||||||
|
echo "[3/3] Purging user data (--purge-user-data specified)..."
|
||||||
|
rm -f "$HERMES_HOME/config/router_profiles.yaml"
|
||||||
|
rm -rf "$HERMES_HOME/agy_profiles"
|
||||||
|
rm -rf "$HERMES_HOME/codex_profiles"
|
||||||
|
rm -rf "$HERMES_HOME/opencode_profiles"
|
||||||
|
echo " User configuration and profiles purged."
|
||||||
|
else
|
||||||
|
echo "[3/3] Preserving user data and credentials."
|
||||||
|
echo " Your router profiles, auth keys, and settings in $HERMES_HOME remain intact."
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "======================================================================"
|
||||||
|
echo " HERMES HUB UNINSTALLED SUCCESSFULLY FROM LINUX "
|
||||||
|
echo "======================================================================"
|
||||||
|
exit 0
|
||||||
Binary file not shown.
326
launcher/HermesHubWeb.cs
Normal file
326
launcher/HermesHubWeb.cs
Normal file
|
|
@ -0,0 +1,326 @@
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Net;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using Microsoft.Win32;
|
||||||
|
|
||||||
|
namespace HermesHub
|
||||||
|
{
|
||||||
|
public static class WebLauncher
|
||||||
|
{
|
||||||
|
[STAThread]
|
||||||
|
public static void Main(string[] args)
|
||||||
|
{
|
||||||
|
Application.EnableVisualStyles();
|
||||||
|
Application.SetCompatibleTextRenderingDefault(false);
|
||||||
|
|
||||||
|
string localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
|
||||||
|
string hermesHome = Environment.GetEnvironmentVariable("HERMES_HOME");
|
||||||
|
if (string.IsNullOrEmpty(hermesHome))
|
||||||
|
{
|
||||||
|
hermesHome = Path.Combine(localAppData, "hermes");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. Determine Web API Host and Port from hub_settings.json or default
|
||||||
|
string host = "127.0.0.1";
|
||||||
|
int port = 5800;
|
||||||
|
string settingsFile = Path.Combine(hermesHome, "hub_settings.json");
|
||||||
|
if (File.Exists(settingsFile))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string json = File.ReadAllText(settingsFile, Encoding.UTF8);
|
||||||
|
int pIdx = json.IndexOf("\"web_api_port\":", StringComparison.OrdinalIgnoreCase);
|
||||||
|
if (pIdx >= 0)
|
||||||
|
{
|
||||||
|
int colon = json.IndexOf(':', pIdx);
|
||||||
|
int comma = json.IndexOfAny(new char[] { ',', '}', '\r', '\n' }, colon + 1);
|
||||||
|
if (colon >= 0 && comma > colon)
|
||||||
|
{
|
||||||
|
string pStr = json.Substring(colon + 1, comma - colon - 1).Trim();
|
||||||
|
int parsedPort;
|
||||||
|
if (int.TryParse(pStr, out parsedPort) && parsedPort > 0 && parsedPort < 65536)
|
||||||
|
{
|
||||||
|
port = parsedPort;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int hIdx = json.IndexOf("\"web_api_host\":", StringComparison.OrdinalIgnoreCase);
|
||||||
|
if (hIdx >= 0)
|
||||||
|
{
|
||||||
|
int q1 = json.IndexOf('"', hIdx + 15);
|
||||||
|
int q2 = json.IndexOf('"', q1 + 1);
|
||||||
|
if (q1 >= 0 && q2 > q1)
|
||||||
|
{
|
||||||
|
string hStr = json.Substring(q1 + 1, q2 - q1 - 1).Trim();
|
||||||
|
if (!string.IsNullOrEmpty(hStr) && hStr != "0.0.0.0")
|
||||||
|
{
|
||||||
|
host = hStr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
string targetUrl = string.Format("http://{0}:{1}/", host, port);
|
||||||
|
string healthUrl = string.Format("http://{0}:{1}/api/health", host, port);
|
||||||
|
|
||||||
|
// 2. Check if server is already running and healthy
|
||||||
|
bool serverWasAlreadyRunning = IsServerHealthy(healthUrl);
|
||||||
|
Process serverProcess = null;
|
||||||
|
|
||||||
|
if (!serverWasAlreadyRunning)
|
||||||
|
{
|
||||||
|
// Find Python
|
||||||
|
string hermesPythonW = Path.Combine(hermesHome, @"hermes-agent\venv\Scripts\pythonw.exe");
|
||||||
|
string hermesPython = Path.Combine(hermesHome, @"hermes-agent\venv\Scripts\python.exe");
|
||||||
|
string exe = File.Exists(hermesPythonW) ? hermesPythonW : hermesPython;
|
||||||
|
|
||||||
|
if (!File.Exists(exe))
|
||||||
|
{
|
||||||
|
MessageBox.Show(
|
||||||
|
"Hermes Python not found:\n" + exe + "\n\nPlease install Hermes Agent first.",
|
||||||
|
"Hermes Hub", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string pluginSrc = Path.Combine(hermesHome, @"plugins\antigravity-provider\src");
|
||||||
|
string agentDir = Path.Combine(hermesHome, "hermes-agent");
|
||||||
|
string baseDir = AppDomain.CurrentDomain.BaseDirectory;
|
||||||
|
string hubSrc = Path.GetFullPath(Path.Combine(baseDir, @"..\src"));
|
||||||
|
|
||||||
|
StringBuilder script = new StringBuilder();
|
||||||
|
script.AppendLine("import sys");
|
||||||
|
if (Directory.Exists(pluginSrc))
|
||||||
|
script.AppendLine("sys.path.insert(0, r'" + pluginSrc.Replace('\\', '/') + "')");
|
||||||
|
if (Directory.Exists(agentDir))
|
||||||
|
script.AppendLine("sys.path.insert(0, r'" + agentDir.Replace('\\', '/') + "')");
|
||||||
|
if (Directory.Exists(hubSrc))
|
||||||
|
script.AppendLine("sys.path.insert(0, r'" + hubSrc.Replace('\\', '/') + "')");
|
||||||
|
script.AppendLine("from antigravity_provider.router.web.server import run_server");
|
||||||
|
script.AppendLine("run_server()");
|
||||||
|
|
||||||
|
string entryScript = Path.Combine(hermesHome, "hermes_hub_web_entry.py");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
File.WriteAllText(entryScript, script.ToString(), Encoding.UTF8);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
MessageBox.Show("Cannot write entry script:\n" + ex.Message, "Hermes Hub", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ProcessStartInfo serverPsi = new ProcessStartInfo();
|
||||||
|
serverPsi.FileName = exe;
|
||||||
|
serverPsi.Arguments = "\"" + entryScript + "\"";
|
||||||
|
serverPsi.WorkingDirectory = hermesHome;
|
||||||
|
serverPsi.UseShellExecute = false;
|
||||||
|
serverPsi.CreateNoWindow = true;
|
||||||
|
serverPsi.WindowStyle = ProcessWindowStyle.Hidden;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
serverProcess = Process.Start(serverPsi);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
MessageBox.Show("Failed to start Hermes Hub web server:\n" + ex.Message, "Hermes Hub", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Poll /api/health with timeout (up to 15s)
|
||||||
|
bool ready = false;
|
||||||
|
for (int i = 0; i < 75; i++)
|
||||||
|
{
|
||||||
|
if (IsServerHealthy(healthUrl))
|
||||||
|
{
|
||||||
|
ready = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (serverProcess.HasExited)
|
||||||
|
{
|
||||||
|
MessageBox.Show("Hermes Hub web server process terminated unexpectedly.", "Hermes Hub", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Thread.Sleep(200);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ready)
|
||||||
|
{
|
||||||
|
MessageBox.Show("Hermes Hub web server failed to respond within 15 seconds.", "Hermes Hub", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||||
|
if (serverProcess != null && !serverProcess.HasExited)
|
||||||
|
{
|
||||||
|
try { serverProcess.Kill(); } catch { }
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Locate browser in strict priority: Edge -> Chrome -> Chromium registry -> Fallback
|
||||||
|
string browserPath = FindChromiumBrowser();
|
||||||
|
Process browserProc = null;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(browserPath))
|
||||||
|
{
|
||||||
|
ProcessStartInfo browserPsi = new ProcessStartInfo();
|
||||||
|
browserPsi.FileName = browserPath;
|
||||||
|
browserPsi.Arguments = string.Format("--app=\"{0}\" --window-size=1400,900", targetUrl);
|
||||||
|
browserPsi.UseShellExecute = false;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
browserProc = Process.Start(browserPsi);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
MessageBox.Show(
|
||||||
|
"Не удалось запустить браузер в режиме приложения:\n" + ex.Message + "\n\nОткрываю стандартный браузер.",
|
||||||
|
"Hermes Hub", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||||
|
Process.Start(targetUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// No Chromium browser found
|
||||||
|
MessageBox.Show(
|
||||||
|
"Браузер с поддержкой режима приложения (Microsoft Edge или Google Chrome) не найден.\n\n" +
|
||||||
|
"Интерфейс будет открыт в стандартном браузере с адресной строкой.",
|
||||||
|
"Hermes Hub", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Process.Start(targetUrl);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
MessageBox.Show("Не удалось открыть браузер: " + ex.Message, "Hermes Hub", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Server Lifecycle:
|
||||||
|
// If the server was started by this launcher session and browser is tracked,
|
||||||
|
// wait for browser window to close, then gracefully terminate server process.
|
||||||
|
if (!serverWasAlreadyRunning && serverProcess != null && !serverProcess.HasExited && browserProc != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
browserProc.WaitForExit();
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!serverProcess.HasExited)
|
||||||
|
{
|
||||||
|
serverProcess.Kill();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsServerHealthy(string url)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url);
|
||||||
|
req.Timeout = 800;
|
||||||
|
req.Method = "GET";
|
||||||
|
using (HttpWebResponse resp = (HttpWebResponse)req.GetResponse())
|
||||||
|
{
|
||||||
|
if (resp.StatusCode == HttpStatusCode.OK)
|
||||||
|
{
|
||||||
|
using (StreamReader r = new StreamReader(resp.GetResponseStream(), Encoding.UTF8))
|
||||||
|
{
|
||||||
|
string txt = r.ReadToEnd();
|
||||||
|
return txt.Contains("\"ok\":true") || txt.Contains("\"ok\": true");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string FindChromiumBrowser()
|
||||||
|
{
|
||||||
|
// 1. Microsoft Edge
|
||||||
|
string[] edgePaths = new string[]
|
||||||
|
{
|
||||||
|
@"C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe",
|
||||||
|
@"C:\Program Files\Microsoft\Edge\Application\msedge.exe",
|
||||||
|
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"Microsoft\Edge\Application\msedge.exe")
|
||||||
|
};
|
||||||
|
foreach (string p in edgePaths)
|
||||||
|
{
|
||||||
|
if (File.Exists(p)) return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
string edgeReg = GetAppPathFromRegistry("msedge.exe");
|
||||||
|
if (!string.IsNullOrEmpty(edgeReg) && File.Exists(edgeReg)) return edgeReg;
|
||||||
|
|
||||||
|
// 2. Google Chrome
|
||||||
|
string[] chromePaths = new string[]
|
||||||
|
{
|
||||||
|
@"C:\Program Files\Google\Chrome\Application\chrome.exe",
|
||||||
|
@"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe",
|
||||||
|
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"Google\Chrome\Application\chrome.exe")
|
||||||
|
};
|
||||||
|
foreach (string p in chromePaths)
|
||||||
|
{
|
||||||
|
if (File.Exists(p)) return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
string chromeReg = GetAppPathFromRegistry("chrome.exe");
|
||||||
|
if (!string.IsNullOrEmpty(chromeReg) && File.Exists(chromeReg)) return chromeReg;
|
||||||
|
|
||||||
|
// 3. Brave / Vivaldi / Chromium
|
||||||
|
string[] otherPaths = new string[]
|
||||||
|
{
|
||||||
|
@"C:\Program Files\BraveSoftware\Brave-Browser\Application\brave.exe",
|
||||||
|
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"BraveSoftware\Brave-Browser\Application\brave.exe"),
|
||||||
|
@"C:\Program Files\Vivaldi\Application\vivaldi.exe",
|
||||||
|
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"Vivaldi\Application\vivaldi.exe")
|
||||||
|
};
|
||||||
|
foreach (string p in otherPaths)
|
||||||
|
{
|
||||||
|
if (File.Exists(p)) return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
string braveReg = GetAppPathFromRegistry("brave.exe");
|
||||||
|
if (!string.IsNullOrEmpty(braveReg) && File.Exists(braveReg)) return braveReg;
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetAppPathFromRegistry(string exeName)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string key = @"SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\" + exeName;
|
||||||
|
using (RegistryKey rk = Registry.LocalMachine.OpenSubKey(key))
|
||||||
|
{
|
||||||
|
if (rk != null)
|
||||||
|
{
|
||||||
|
object val = rk.GetValue(null);
|
||||||
|
if (val != null) return val.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
using (RegistryKey rk = Registry.CurrentUser.OpenSubKey(key))
|
||||||
|
{
|
||||||
|
if (rk != null)
|
||||||
|
{
|
||||||
|
object val = rk.GetValue(null);
|
||||||
|
if (val != null) return val.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
launcher/HermesHubWeb.exe
Normal file
BIN
launcher/HermesHubWeb.exe
Normal file
Binary file not shown.
134
launcher/hermes-hub-web.sh
Normal file
134
launcher/hermes-hub-web.sh
Normal file
|
|
@ -0,0 +1,134 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# ==============================================================================
|
||||||
|
# Hermes Hub — Linux Web Application Launcher
|
||||||
|
# Starts the background web server if not already active, waits for /api/health,
|
||||||
|
# and opens the UI in application window mode (--app=URL) or handles headless mode.
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
DEFAULT_HERMES_HOME="$HOME/.hermes"
|
||||||
|
HERMES_HOME="${HERMES_HOME:-$DEFAULT_HERMES_HOME}"
|
||||||
|
|
||||||
|
# 1. Discover Python Runtime
|
||||||
|
PYTHON_BIN=""
|
||||||
|
if [ -f "$HERMES_HOME/hermes-agent/venv/bin/python3" ]; then
|
||||||
|
PYTHON_BIN="$HERMES_HOME/hermes-agent/venv/bin/python3"
|
||||||
|
elif [ -f "$HERMES_HOME/hermes-agent/venv/bin/python" ]; then
|
||||||
|
PYTHON_BIN="$HERMES_HOME/hermes-agent/venv/bin/python"
|
||||||
|
elif command -v python3 >/dev/null 2>&1; then
|
||||||
|
PYTHON_BIN="$(command -v python3)"
|
||||||
|
elif command -v python >/dev/null 2>&1; then
|
||||||
|
PYTHON_BIN="$(command -v python)"
|
||||||
|
else
|
||||||
|
echo "❌ Error: Python 3 not found on system." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 2. Configure Environment and Read Settings (Host/Port)
|
||||||
|
export PYTHONPATH="$HERMES_HOME/plugins/antigravity-provider/src:$HERMES_HOME/hermes-agent:$PYTHONPATH"
|
||||||
|
|
||||||
|
HOST="127.0.0.1"
|
||||||
|
PORT=5800
|
||||||
|
|
||||||
|
SETTINGS_FILE="$HERMES_HOME/hub_settings.json"
|
||||||
|
if [ -f "$SETTINGS_FILE" ]; then
|
||||||
|
PARSED_PORT="$("$PYTHON_BIN" -c "import json; print(json.load(open('$SETTINGS_FILE')).get('web_api_port', 5800))" 2>/dev/null || echo 5800)"
|
||||||
|
if [ -n "$PARSED_PORT" ] && [ "$PARSED_PORT" -gt 0 ] 2>/dev/null; then
|
||||||
|
PORT="$PARSED_PORT"
|
||||||
|
fi
|
||||||
|
PARSED_HOST="$("$PYTHON_BIN" -c "import json; h = json.load(open('$SETTINGS_FILE')).get('web_api_host', '127.0.0.1'); print('127.0.0.1' if h in ('0.0.0.0', '') else h)" 2>/dev/null || echo '127.0.0.1')"
|
||||||
|
if [ -n "$PARSED_HOST" ]; then
|
||||||
|
HOST="$PARSED_HOST"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
TARGET_URL="http://$HOST:$PORT/"
|
||||||
|
HEALTH_URL="http://$HOST:$PORT/api/health"
|
||||||
|
|
||||||
|
# Function to check server health
|
||||||
|
check_health() {
|
||||||
|
"$PYTHON_BIN" -c "
|
||||||
|
import urllib.request, json
|
||||||
|
try:
|
||||||
|
req = urllib.request.urlopen('$HEALTH_URL', timeout=0.8)
|
||||||
|
data = json.loads(req.read().decode('utf-8'))
|
||||||
|
sys.exit(0 if data.get('ok') is True else 1)
|
||||||
|
except Exception:
|
||||||
|
import sys
|
||||||
|
sys.exit(1)
|
||||||
|
" >/dev/null 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
# 3. Start Web Server if not already active
|
||||||
|
SERVER_STARTED_BY_US=false
|
||||||
|
if ! check_health; then
|
||||||
|
echo "Starting Hermes Hub Web Server in background..."
|
||||||
|
LOG_FILE="$HERMES_HOME/hermes_web_server.log"
|
||||||
|
nohup "$PYTHON_BIN" -m antigravity_provider.router.web > "$LOG_FILE" 2>&1 &
|
||||||
|
SERVER_PID=$!
|
||||||
|
SERVER_STARTED_BY_US=true
|
||||||
|
|
||||||
|
# Wait for server readiness (polling /api/health)
|
||||||
|
READY=false
|
||||||
|
for i in $(seq 1 75); do
|
||||||
|
if check_health; then
|
||||||
|
READY=true
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
if ! kill -0 "$SERVER_PID" 2>/dev/null; then
|
||||||
|
echo "❌ Error: Web server process terminated unexpectedly. Check $LOG_FILE" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
sleep 0.2
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "$READY" != "true" ]; then
|
||||||
|
echo "❌ Error: Web server failed to respond at $HEALTH_URL within 15 seconds." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 4. Headless Server Check (SSH / No Graphical Display)
|
||||||
|
if [ -z "$DISPLAY" ] && [ -z "$WAYLAND_DISPLAY" ]; then
|
||||||
|
echo "======================================================================"
|
||||||
|
echo " Hermes Hub Web Server is running (Headless Mode)"
|
||||||
|
echo " Web Interface URL: $TARGET_URL"
|
||||||
|
echo ""
|
||||||
|
echo " To access the interface from your local computer, forward the port:"
|
||||||
|
echo " ssh -L $PORT:127.0.0.1:$PORT user@server"
|
||||||
|
echo ""
|
||||||
|
echo " Then open in your browser:"
|
||||||
|
echo " $TARGET_URL"
|
||||||
|
echo "======================================================================"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 5. Graphical Environment: Search for Chromium-based browser in priority order
|
||||||
|
CHROMIUM_BIN=""
|
||||||
|
|
||||||
|
for b in google-chrome google-chrome-stable chromium chromium-browser microsoft-edge microsoft-edge-stable brave-browser; do
|
||||||
|
if command -v "$b" >/dev/null 2>&1; then
|
||||||
|
CHROMIUM_BIN="$(command -v "$b")"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ -n "$CHROMIUM_BIN" ]; then
|
||||||
|
# Launch in Application Window mode without address bar, tabs, or menus
|
||||||
|
"$CHROMIUM_BIN" --app="$TARGET_URL" --window-size=1400,900 "$@"
|
||||||
|
else
|
||||||
|
# Fallback to standard default browser
|
||||||
|
echo "Запуск в обычном браузере: режим приложения (без адресной строки) требует Google Chrome, Chromium или Microsoft Edge."
|
||||||
|
if command -v xdg-open >/dev/null 2>&1; then
|
||||||
|
xdg-open "$TARGET_URL"
|
||||||
|
elif command -v sensible-browser >/dev/null 2>&1; then
|
||||||
|
sensible-browser "$TARGET_URL"
|
||||||
|
elif command -v firefox >/dev/null 2>&1; then
|
||||||
|
firefox "$TARGET_URL"
|
||||||
|
else
|
||||||
|
echo "Please open $TARGET_URL in your web browser."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit 0
|
||||||
|
|
@ -58,6 +58,11 @@ if (Test-Path $LauncherExe) {
|
||||||
Copy-Item -Path $LauncherExe -Destination (Join-Path $TargetDir "HermesHub.exe") -Force
|
Copy-Item -Path $LauncherExe -Destination (Join-Path $TargetDir "HermesHub.exe") -Force
|
||||||
Copy-Item -Path $LauncherExe -Destination (Join-Path $HermesHome "HermesHub.exe") -Force
|
Copy-Item -Path $LauncherExe -Destination (Join-Path $HermesHome "HermesHub.exe") -Force
|
||||||
}
|
}
|
||||||
|
$WebLauncherExe = Join-Path $RepoRoot "launcher\HermesHubWeb.exe"
|
||||||
|
if (Test-Path $WebLauncherExe) {
|
||||||
|
Copy-Item -Path $WebLauncherExe -Destination (Join-Path $TargetDir "HermesHubWeb.exe") -Force
|
||||||
|
Copy-Item -Path $WebLauncherExe -Destination (Join-Path $HermesHome "HermesHubWeb.exe") -Force
|
||||||
|
}
|
||||||
|
|
||||||
# 5. Deploy Plugin Integration into Hermes
|
# 5. Deploy Plugin Integration into Hermes
|
||||||
Write-Host "[4/6] Deploying plugin components to Hermes..." -ForegroundColor Yellow
|
Write-Host "[4/6] Deploying plugin components to Hermes..." -ForegroundColor Yellow
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,10 @@ $HomeLauncher = Join-Path $HermesHome "HermesHub.exe"
|
||||||
if (Test-Path $HomeLauncher) {
|
if (Test-Path $HomeLauncher) {
|
||||||
Remove-Item -Path $HomeLauncher -Force -ErrorAction SilentlyContinue
|
Remove-Item -Path $HomeLauncher -Force -ErrorAction SilentlyContinue
|
||||||
}
|
}
|
||||||
|
$HomeWebLauncher = Join-Path $HermesHome "HermesHubWeb.exe"
|
||||||
|
if (Test-Path $HomeWebLauncher) {
|
||||||
|
Remove-Item -Path $HomeWebLauncher -Force -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
|
||||||
Write-Host "[2/3] Removing plugin integration..." -ForegroundColor Gray
|
Write-Host "[2/3] Removing plugin integration..." -ForegroundColor Gray
|
||||||
$PluginDir = Join-Path $HermesHome "plugins\antigravity-provider"
|
$PluginDir = Join-Path $HermesHome "plugins\antigravity-provider"
|
||||||
|
|
|
||||||
110
tests/test_installer_windows_and_linux.py
Normal file
110
tests/test_installer_windows_and_linux.py
Normal file
|
|
@ -0,0 +1,110 @@
|
||||||
|
"""
|
||||||
|
Hermes Hub — Windows & Linux Installers and Launchers Test Suite.
|
||||||
|
Verifies requirements of Task A19.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
import subprocess
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
REPO_ROOT = Path(__file__).resolve().parent.parent
|
||||||
|
INSTALLER_DIR = REPO_ROOT / "installer"
|
||||||
|
LAUNCHER_DIR = REPO_ROOT / "launcher"
|
||||||
|
|
||||||
|
|
||||||
|
def test_windows_csharp_launchers_and_setup_compile():
|
||||||
|
"""Verify HermesHub.cs, HermesHubWeb.cs, and HermesHubSetup.cs compile without errors on Windows."""
|
||||||
|
csc_path = r"C:\Windows\Microsoft.NET\Framework64\v4.0.30319\csc.exe"
|
||||||
|
if not os.path.isfile(csc_path):
|
||||||
|
csc_path = r"C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe"
|
||||||
|
|
||||||
|
if not os.path.isfile(csc_path):
|
||||||
|
pytest.skip("csc.exe compiler not found in standard .NET Framework location")
|
||||||
|
|
||||||
|
temp_out = REPO_ROOT / "artifacts" / "test_compile"
|
||||||
|
temp_out.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
# 1. Compile HermesHub.cs
|
||||||
|
hub_cs = LAUNCHER_DIR / "HermesHub.cs"
|
||||||
|
res1 = subprocess.run([
|
||||||
|
csc_path, "/target:winexe", f"/out:{temp_out / 'HermesHub.exe'}",
|
||||||
|
"/r:System.Windows.Forms.dll", "/r:System.Drawing.dll", str(hub_cs)
|
||||||
|
], capture_output=True, text=True)
|
||||||
|
assert res1.returncode == 0, f"HermesHub.cs compilation failed: {res1.stdout}\n{res1.stderr}"
|
||||||
|
|
||||||
|
# 2. Compile HermesHubWeb.cs
|
||||||
|
hub_web_cs = LAUNCHER_DIR / "HermesHubWeb.cs"
|
||||||
|
res2 = subprocess.run([
|
||||||
|
csc_path, "/target:winexe", f"/out:{temp_out / 'HermesHubWeb.exe'}",
|
||||||
|
"/r:System.Windows.Forms.dll", "/r:System.Drawing.dll", str(hub_web_cs)
|
||||||
|
], capture_output=True, text=True)
|
||||||
|
assert res2.returncode == 0, f"HermesHubWeb.cs compilation failed: {res2.stdout}\n{res2.stderr}"
|
||||||
|
|
||||||
|
# 3. Compile HermesHubSetup.cs
|
||||||
|
setup_cs = INSTALLER_DIR / "HermesHubSetup.cs"
|
||||||
|
res3 = subprocess.run([
|
||||||
|
csc_path, "/target:winexe", f"/out:{temp_out / 'HermesHubSetup.exe'}",
|
||||||
|
"/r:System.Windows.Forms.dll", "/r:System.Drawing.dll", str(setup_cs)
|
||||||
|
], capture_output=True, text=True)
|
||||||
|
assert res3.returncode == 0, f"HermesHubSetup.cs compilation failed: {res3.stdout}\n{res3.stderr}"
|
||||||
|
|
||||||
|
|
||||||
|
def test_windows_launcher_browser_search_and_health_check():
|
||||||
|
"""Verify HermesHubWeb.cs includes Edge/Chrome detection, --app mode, and /api/health polling."""
|
||||||
|
web_cs = (LAUNCHER_DIR / "HermesHubWeb.cs").read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
assert "msedge.exe" in web_cs
|
||||||
|
assert "chrome.exe" in web_cs
|
||||||
|
assert "--app=" in web_cs
|
||||||
|
assert "/api/health" in web_cs
|
||||||
|
assert "IsServerHealthy" in web_cs
|
||||||
|
assert "HermesHubWeb" in web_cs or "WebLauncher" in web_cs
|
||||||
|
|
||||||
|
|
||||||
|
def test_windows_installer_creates_both_shortcuts():
|
||||||
|
"""Verify HermesHubSetup.cs creates both Web and Desktop shortcuts."""
|
||||||
|
setup_cs = (INSTALLER_DIR / "HermesHubSetup.cs").read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
assert "Hermes Hub (Web).lnk" in setup_cs
|
||||||
|
assert "Hermes Hub (Desktop).lnk" in setup_cs
|
||||||
|
assert "HermesHubWeb.exe" in setup_cs
|
||||||
|
assert "HermesHub.exe" in setup_cs
|
||||||
|
|
||||||
|
|
||||||
|
def test_linux_installer_script_structure():
|
||||||
|
"""Verify install-linux.sh contains prerequisite checks, mirroring, manifest, and .desktop setup."""
|
||||||
|
install_sh = (INSTALLER_DIR / "install-linux.sh").read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
assert "#!/usr/bin/env bash" in install_sh
|
||||||
|
assert "deployment_manifest.json" in install_sh
|
||||||
|
assert "hermes-hub-web.desktop" in install_sh
|
||||||
|
assert "HERMES_HOME" in install_sh
|
||||||
|
assert "antigravity-provider" in install_sh
|
||||||
|
|
||||||
|
|
||||||
|
def test_linux_launcher_script_headless_and_app_mode():
|
||||||
|
"""Verify hermes-hub-web.sh checks DISPLAY, prints SSH port forwarding on headless, and uses --app on desktop."""
|
||||||
|
launcher_sh = (LAUNCHER_DIR / "hermes-hub-web.sh").read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
assert "#!/usr/bin/env bash" in launcher_sh
|
||||||
|
assert "DISPLAY" in launcher_sh
|
||||||
|
assert "WAYLAND_DISPLAY" in launcher_sh
|
||||||
|
assert "ssh -L" in launcher_sh
|
||||||
|
assert "127.0.0.1" in launcher_sh
|
||||||
|
assert "--app=" in launcher_sh
|
||||||
|
assert "/api/health" in launcher_sh
|
||||||
|
assert "google-chrome" in launcher_sh or "chromium" in launcher_sh or "microsoft-edge" in launcher_sh
|
||||||
|
|
||||||
|
|
||||||
|
def test_linux_uninstaller_preserves_user_data():
|
||||||
|
"""Verify uninstall-linux.sh preserves user config, keys, and profiles by default."""
|
||||||
|
uninstall_sh = (INSTALLER_DIR / "uninstall-linux.sh").read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
assert "#!/usr/bin/env bash" in uninstall_sh
|
||||||
|
assert "PURGE_USER_DATA" in uninstall_sh
|
||||||
|
assert "--purge-user-data" in uninstall_sh
|
||||||
|
assert "Preserving user data and credentials" in uninstall_sh
|
||||||
|
assert "hermes-hub-web.desktop" in uninstall_sh
|
||||||
Loading…
Reference in a new issue