fix(termux): make setup-hermes use android path

This commit is contained in:
adybag14-cyber 2026-04-09 10:41:58 +02:00 committed by Teknium
parent 3878495972
commit 2194425918
2 changed files with 233 additions and 120 deletions

View file

@ -3,17 +3,17 @@
# Hermes Agent Setup Script # Hermes Agent Setup Script
# ============================================================================ # ============================================================================
# Quick setup for developers who cloned the repo manually. # Quick setup for developers who cloned the repo manually.
# Uses uv for fast Python provisioning and package management. # Uses uv for desktop/server setup and Python's stdlib venv + pip on Termux.
# #
# Usage: # Usage:
# ./setup-hermes.sh # ./setup-hermes.sh
# #
# This script: # This script:
# 1. Installs uv if not present # 1. Detects desktop/server vs Android/Termux setup path
# 2. Creates a virtual environment with Python 3.11 via uv # 2. Creates a Python 3.11 virtual environment
# 3. Installs all dependencies (main package + submodules) # 3. Installs the appropriate dependency set for the platform
# 4. Creates .env from template (if not exists) # 4. Creates .env from template (if not exists)
# 5. Symlinks the 'hermes' CLI command into ~/.local/bin # 5. Symlinks the 'hermes' CLI command into a user-facing bin dir
# 6. Runs the setup wizard (optional) # 6. Runs the setup wizard (optional)
# ============================================================================ # ============================================================================
@ -31,6 +31,26 @@ cd "$SCRIPT_DIR"
PYTHON_VERSION="3.11" PYTHON_VERSION="3.11"
is_termux() {
[ -n "${TERMUX_VERSION:-}" ] || [[ "${PREFIX:-}" == *"com.termux/files/usr"* ]]
}
get_command_link_dir() {
if is_termux && [ -n "${PREFIX:-}" ]; then
echo "$PREFIX/bin"
else
echo "$HOME/.local/bin"
fi
}
get_command_link_display_dir() {
if is_termux && [ -n "${PREFIX:-}" ]; then
echo '$PREFIX/bin'
else
echo '~/.local/bin'
fi
}
echo "" echo ""
echo -e "${CYAN}⚕ Hermes Agent Setup${NC}" echo -e "${CYAN}⚕ Hermes Agent Setup${NC}"
echo "" echo ""
@ -42,36 +62,40 @@ echo ""
echo -e "${CYAN}${NC} Checking for uv..." echo -e "${CYAN}${NC} Checking for uv..."
UV_CMD="" UV_CMD=""
if command -v uv &> /dev/null; then if is_termux; then
UV_CMD="uv" echo -e "${CYAN}${NC} Termux detected — using Python's stdlib venv + pip instead of uv"
elif [ -x "$HOME/.local/bin/uv" ]; then
UV_CMD="$HOME/.local/bin/uv"
elif [ -x "$HOME/.cargo/bin/uv" ]; then
UV_CMD="$HOME/.cargo/bin/uv"
fi
if [ -n "$UV_CMD" ]; then
UV_VERSION=$($UV_CMD --version 2>/dev/null)
echo -e "${GREEN}${NC} uv found ($UV_VERSION)"
else else
echo -e "${CYAN}${NC} Installing uv..." if command -v uv &> /dev/null; then
if curl -LsSf https://astral.sh/uv/install.sh | sh 2>/dev/null; then UV_CMD="uv"
if [ -x "$HOME/.local/bin/uv" ]; then elif [ -x "$HOME/.local/bin/uv" ]; then
UV_CMD="$HOME/.local/bin/uv" UV_CMD="$HOME/.local/bin/uv"
elif [ -x "$HOME/.cargo/bin/uv" ]; then elif [ -x "$HOME/.cargo/bin/uv" ]; then
UV_CMD="$HOME/.cargo/bin/uv" UV_CMD="$HOME/.cargo/bin/uv"
fi fi
if [ -n "$UV_CMD" ]; then if [ -n "$UV_CMD" ]; then
UV_VERSION=$($UV_CMD --version 2>/dev/null) UV_VERSION=$($UV_CMD --version 2>/dev/null)
echo -e "${GREEN}${NC} uv installed ($UV_VERSION)" echo -e "${GREEN}${NC} uv found ($UV_VERSION)"
else
echo -e "${CYAN}${NC} Installing uv..."
if curl -LsSf https://astral.sh/uv/install.sh | sh 2>/dev/null; then
if [ -x "$HOME/.local/bin/uv" ]; then
UV_CMD="$HOME/.local/bin/uv"
elif [ -x "$HOME/.cargo/bin/uv" ]; then
UV_CMD="$HOME/.cargo/bin/uv"
fi
if [ -n "$UV_CMD" ]; then
UV_VERSION=$($UV_CMD --version 2>/dev/null)
echo -e "${GREEN}${NC} uv installed ($UV_VERSION)"
else
echo -e "${RED}${NC} uv installed but not found. Add ~/.local/bin to PATH and retry."
exit 1
fi
else else
echo -e "${RED}${NC} uv installed but not found. Add ~/.local/bin to PATH and retry." echo -e "${RED}${NC} Failed to install uv. Visit https://docs.astral.sh/uv/"
exit 1 exit 1
fi fi
else
echo -e "${RED}${NC} Failed to install uv. Visit https://docs.astral.sh/uv/"
exit 1
fi fi
fi fi
@ -81,16 +105,34 @@ fi
echo -e "${CYAN}${NC} Checking Python $PYTHON_VERSION..." echo -e "${CYAN}${NC} Checking Python $PYTHON_VERSION..."
if $UV_CMD python find "$PYTHON_VERSION" &> /dev/null; then if is_termux; then
PYTHON_PATH=$($UV_CMD python find "$PYTHON_VERSION") if command -v python >/dev/null 2>&1; then
PYTHON_FOUND_VERSION=$($PYTHON_PATH --version 2>/dev/null) PYTHON_PATH="$(command -v python)"
echo -e "${GREEN}${NC} $PYTHON_FOUND_VERSION found" if "$PYTHON_PATH" -c 'import sys; raise SystemExit(0 if sys.version_info >= (3, 11) else 1)' 2>/dev/null; then
PYTHON_FOUND_VERSION=$($PYTHON_PATH --version 2>/dev/null)
echo -e "${GREEN}${NC} $PYTHON_FOUND_VERSION found"
else
echo -e "${RED}${NC} Termux Python must be 3.11+"
echo " Run: pkg install python"
exit 1
fi
else
echo -e "${RED}${NC} Python not found in Termux"
echo " Run: pkg install python"
exit 1
fi
else else
echo -e "${CYAN}${NC} Python $PYTHON_VERSION not found, installing via uv..." if $UV_CMD python find "$PYTHON_VERSION" &> /dev/null; then
$UV_CMD python install "$PYTHON_VERSION" PYTHON_PATH=$($UV_CMD python find "$PYTHON_VERSION")
PYTHON_PATH=$($UV_CMD python find "$PYTHON_VERSION") PYTHON_FOUND_VERSION=$($PYTHON_PATH --version 2>/dev/null)
PYTHON_FOUND_VERSION=$($PYTHON_PATH --version 2>/dev/null) echo -e "${GREEN}${NC} $PYTHON_FOUND_VERSION found"
echo -e "${GREEN}${NC} $PYTHON_FOUND_VERSION installed" else
echo -e "${CYAN}${NC} Python $PYTHON_VERSION not found, installing via uv..."
$UV_CMD python install "$PYTHON_VERSION"
PYTHON_PATH=$($UV_CMD python find "$PYTHON_VERSION")
PYTHON_FOUND_VERSION=$($PYTHON_PATH --version 2>/dev/null)
echo -e "${GREEN}${NC} $PYTHON_FOUND_VERSION installed"
fi
fi fi
# ============================================================================ # ============================================================================
@ -104,11 +146,16 @@ if [ -d "venv" ]; then
rm -rf venv rm -rf venv
fi fi
$UV_CMD venv venv --python "$PYTHON_VERSION" if is_termux; then
echo -e "${GREEN}${NC} venv created (Python $PYTHON_VERSION)" "$PYTHON_PATH" -m venv venv
echo -e "${GREEN}${NC} venv created with stdlib venv"
else
$UV_CMD venv venv --python "$PYTHON_VERSION"
echo -e "${GREEN}${NC} venv created (Python $PYTHON_VERSION)"
fi
# Tell uv to install into this venv (no activation needed for uv)
export VIRTUAL_ENV="$SCRIPT_DIR/venv" export VIRTUAL_ENV="$SCRIPT_DIR/venv"
SETUP_PYTHON="$SCRIPT_DIR/venv/bin/python"
# ============================================================================ # ============================================================================
# Dependencies # Dependencies
@ -116,19 +163,34 @@ export VIRTUAL_ENV="$SCRIPT_DIR/venv"
echo -e "${CYAN}${NC} Installing dependencies..." echo -e "${CYAN}${NC} Installing dependencies..."
# Prefer uv sync with lockfile (hash-verified installs) when available, if is_termux; then
# fall back to pip install for compatibility or when lockfile is stale. export ANDROID_API_LEVEL="$(getprop ro.build.version.sdk 2>/dev/null || printf '%s' "${ANDROID_API_LEVEL:-}")"
if [ -f "uv.lock" ]; then echo -e "${CYAN}${NC} Termux detected — installing the tested Android bundle"
echo -e "${CYAN}${NC} Using uv.lock for hash-verified installation..." "$SETUP_PYTHON" -m pip install --upgrade pip setuptools wheel
UV_PROJECT_ENVIRONMENT="$SCRIPT_DIR/venv" $UV_CMD sync --all-extras --locked 2>/dev/null && \ if [ -f "constraints-termux.txt" ]; then
echo -e "${GREEN}${NC} Dependencies installed (lockfile verified)" || { "$SETUP_PYTHON" -m pip install -e ".[termux]" -c constraints-termux.txt || {
echo -e "${YELLOW}${NC} Lockfile install failed (may be outdated), falling back to pip install..." echo -e "${YELLOW}${NC} Termux bundle install failed, falling back to base install..."
"$SETUP_PYTHON" -m pip install -e "." -c constraints-termux.txt
}
else
"$SETUP_PYTHON" -m pip install -e ".[termux]" || "$SETUP_PYTHON" -m pip install -e "."
fi
echo -e "${GREEN}${NC} Dependencies installed"
else
# Prefer uv sync with lockfile (hash-verified installs) when available,
# fall back to pip install for compatibility or when lockfile is stale.
if [ -f "uv.lock" ]; then
echo -e "${CYAN}${NC} Using uv.lock for hash-verified installation..."
UV_PROJECT_ENVIRONMENT="$SCRIPT_DIR/venv" $UV_CMD sync --all-extras --locked 2>/dev/null && \
echo -e "${GREEN}${NC} Dependencies installed (lockfile verified)" || {
echo -e "${YELLOW}${NC} Lockfile install failed (may be outdated), falling back to pip install..."
$UV_CMD pip install -e ".[all]" || $UV_CMD pip install -e "."
echo -e "${GREEN}${NC} Dependencies installed"
}
else
$UV_CMD pip install -e ".[all]" || $UV_CMD pip install -e "." $UV_CMD pip install -e ".[all]" || $UV_CMD pip install -e "."
echo -e "${GREEN}${NC} Dependencies installed" echo -e "${GREEN}${NC} Dependencies installed"
} fi
else
$UV_CMD pip install -e ".[all]" || $UV_CMD pip install -e "."
echo -e "${GREEN}${NC} Dependencies installed"
fi fi
# ============================================================================ # ============================================================================
@ -138,7 +200,9 @@ fi
echo -e "${CYAN}${NC} Installing optional submodules..." echo -e "${CYAN}${NC} Installing optional submodules..."
# tinker-atropos (RL training backend) # tinker-atropos (RL training backend)
if [ -d "tinker-atropos" ] && [ -f "tinker-atropos/pyproject.toml" ]; then if is_termux; then
echo -e "${CYAN}${NC} Skipping tinker-atropos on Termux (not part of the tested Android path)"
elif [ -d "tinker-atropos" ] && [ -f "tinker-atropos/pyproject.toml" ]; then
$UV_CMD pip install -e "./tinker-atropos" && \ $UV_CMD pip install -e "./tinker-atropos" && \
echo -e "${GREEN}${NC} tinker-atropos installed" || \ echo -e "${GREEN}${NC} tinker-atropos installed" || \
echo -e "${YELLOW}${NC} tinker-atropos install failed (RL tools may not work)" echo -e "${YELLOW}${NC} tinker-atropos install failed (RL tools may not work)"
@ -161,33 +225,41 @@ else
if [[ $REPLY =~ ^[Yy]$ ]] || [[ -z $REPLY ]]; then if [[ $REPLY =~ ^[Yy]$ ]] || [[ -z $REPLY ]]; then
INSTALLED=false INSTALLED=false
# Check if sudo is available if is_termux; then
if command -v sudo &> /dev/null && sudo -n true 2>/dev/null; then pkg install -y ripgrep && INSTALLED=true
if command -v apt &> /dev/null; then else
sudo apt install -y ripgrep && INSTALLED=true # Check if sudo is available
elif command -v dnf &> /dev/null; then if command -v sudo &> /dev/null && sudo -n true 2>/dev/null; then
sudo dnf install -y ripgrep && INSTALLED=true if command -v apt &> /dev/null; then
sudo apt install -y ripgrep && INSTALLED=true
elif command -v dnf &> /dev/null; then
sudo dnf install -y ripgrep && INSTALLED=true
fi
fi fi
fi
# Try brew (no sudo needed) # Try brew (no sudo needed)
if [ "$INSTALLED" = false ] && command -v brew &> /dev/null; then if [ "$INSTALLED" = false ] && command -v brew &> /dev/null; then
brew install ripgrep && INSTALLED=true brew install ripgrep && INSTALLED=true
fi fi
# Try cargo (no sudo needed) # Try cargo (no sudo needed)
if [ "$INSTALLED" = false ] && command -v cargo &> /dev/null; then if [ "$INSTALLED" = false ] && command -v cargo &> /dev/null; then
echo -e "${CYAN}${NC} Trying cargo install (no sudo required)..." echo -e "${CYAN}${NC} Trying cargo install (no sudo required)..."
cargo install ripgrep && INSTALLED=true cargo install ripgrep && INSTALLED=true
fi
fi fi
if [ "$INSTALLED" = true ]; then if [ "$INSTALLED" = true ]; then
echo -e "${GREEN}${NC} ripgrep installed" echo -e "${GREEN}${NC} ripgrep installed"
else else
echo -e "${YELLOW}${NC} Auto-install failed. Install options:" echo -e "${YELLOW}${NC} Auto-install failed. Install options:"
echo " sudo apt install ripgrep # Debian/Ubuntu" if is_termux; then
echo " brew install ripgrep # macOS" echo " pkg install ripgrep # Termux / Android"
echo " cargo install ripgrep # With Rust (no sudo)" else
echo " sudo apt install ripgrep # Debian/Ubuntu"
echo " brew install ripgrep # macOS"
echo " cargo install ripgrep # With Rust (no sudo)"
fi
echo " https://github.com/BurntSushi/ripgrep#installation" echo " https://github.com/BurntSushi/ripgrep#installation"
fi fi
fi fi
@ -207,49 +279,56 @@ else
fi fi
# ============================================================================ # ============================================================================
# PATH setup — symlink hermes into ~/.local/bin # PATH setup — symlink hermes into a user-facing bin dir
# ============================================================================ # ============================================================================
echo -e "${CYAN}${NC} Setting up hermes command..." echo -e "${CYAN}${NC} Setting up hermes command..."
HERMES_BIN="$SCRIPT_DIR/venv/bin/hermes" HERMES_BIN="$SCRIPT_DIR/venv/bin/hermes"
mkdir -p "$HOME/.local/bin" COMMAND_LINK_DIR="$(get_command_link_dir)"
ln -sf "$HERMES_BIN" "$HOME/.local/bin/hermes" COMMAND_LINK_DISPLAY_DIR="$(get_command_link_display_dir)"
echo -e "${GREEN}${NC} Symlinked hermes → ~/.local/bin/hermes" mkdir -p "$COMMAND_LINK_DIR"
ln -sf "$HERMES_BIN" "$COMMAND_LINK_DIR/hermes"
echo -e "${GREEN}${NC} Symlinked hermes → $COMMAND_LINK_DISPLAY_DIR/hermes"
# Determine the appropriate shell config file if is_termux; then
SHELL_CONFIG="" export PATH="$COMMAND_LINK_DIR:$PATH"
if [[ "$SHELL" == *"zsh"* ]]; then echo -e "${GREEN}${NC} $COMMAND_LINK_DISPLAY_DIR is already on PATH in Termux"
SHELL_CONFIG="$HOME/.zshrc"
elif [[ "$SHELL" == *"bash"* ]]; then
SHELL_CONFIG="$HOME/.bashrc"
[ ! -f "$SHELL_CONFIG" ] && SHELL_CONFIG="$HOME/.bash_profile"
else else
# Fallback to checking existing files # Determine the appropriate shell config file
if [ -f "$HOME/.zshrc" ]; then SHELL_CONFIG=""
if [[ "$SHELL" == *"zsh"* ]]; then
SHELL_CONFIG="$HOME/.zshrc" SHELL_CONFIG="$HOME/.zshrc"
elif [ -f "$HOME/.bashrc" ]; then elif [[ "$SHELL" == *"bash"* ]]; then
SHELL_CONFIG="$HOME/.bashrc" SHELL_CONFIG="$HOME/.bashrc"
elif [ -f "$HOME/.bash_profile" ]; then [ ! -f "$SHELL_CONFIG" ] && SHELL_CONFIG="$HOME/.bash_profile"
SHELL_CONFIG="$HOME/.bash_profile"
fi
fi
if [ -n "$SHELL_CONFIG" ]; then
# Touch the file just in case it doesn't exist yet but was selected
touch "$SHELL_CONFIG" 2>/dev/null || true
if ! echo "$PATH" | tr ':' '\n' | grep -q "^$HOME/.local/bin$"; then
if ! grep -q '\.local/bin' "$SHELL_CONFIG" 2>/dev/null; then
echo "" >> "$SHELL_CONFIG"
echo "# Hermes Agent — ensure ~/.local/bin is on PATH" >> "$SHELL_CONFIG"
echo 'export PATH="$HOME/.local/bin:$PATH"' >> "$SHELL_CONFIG"
echo -e "${GREEN}${NC} Added ~/.local/bin to PATH in $SHELL_CONFIG"
else
echo -e "${GREEN}${NC} ~/.local/bin already in $SHELL_CONFIG"
fi
else else
echo -e "${GREEN}${NC} ~/.local/bin already on PATH" # Fallback to checking existing files
if [ -f "$HOME/.zshrc" ]; then
SHELL_CONFIG="$HOME/.zshrc"
elif [ -f "$HOME/.bashrc" ]; then
SHELL_CONFIG="$HOME/.bashrc"
elif [ -f "$HOME/.bash_profile" ]; then
SHELL_CONFIG="$HOME/.bash_profile"
fi
fi
if [ -n "$SHELL_CONFIG" ]; then
# Touch the file just in case it doesn't exist yet but was selected
touch "$SHELL_CONFIG" 2>/dev/null || true
if ! echo "$PATH" | tr ':' '\n' | grep -q "^$HOME/.local/bin$"; then
if ! grep -q '\.local/bin' "$SHELL_CONFIG" 2>/dev/null; then
echo "" >> "$SHELL_CONFIG"
echo "# Hermes Agent — ensure ~/.local/bin is on PATH" >> "$SHELL_CONFIG"
echo 'export PATH="$HOME/.local/bin:$PATH"' >> "$SHELL_CONFIG"
echo -e "${GREEN}${NC} Added ~/.local/bin to PATH in $SHELL_CONFIG"
else
echo -e "${GREEN}${NC} ~/.local/bin already in $SHELL_CONFIG"
fi
else
echo -e "${GREEN}${NC} ~/.local/bin already on PATH"
fi
fi fi
fi fi
@ -281,18 +360,31 @@ echo -e "${GREEN}✓ Setup complete!${NC}"
echo "" echo ""
echo "Next steps:" echo "Next steps:"
echo "" echo ""
echo " 1. Reload your shell:" if is_termux; then
echo " source $SHELL_CONFIG" echo " 1. Run the setup wizard to configure API keys:"
echo "" echo " hermes setup"
echo " 2. Run the setup wizard to configure API keys:" echo ""
echo " hermes setup" echo " 2. Start chatting:"
echo "" echo " hermes"
echo " 3. Start chatting:" echo ""
echo " hermes" else
echo "" echo " 1. Reload your shell:"
echo " source $SHELL_CONFIG"
echo ""
echo " 2. Run the setup wizard to configure API keys:"
echo " hermes setup"
echo ""
echo " 3. Start chatting:"
echo " hermes"
echo ""
fi
echo "Other commands:" echo "Other commands:"
echo " hermes status # Check configuration" echo " hermes status # Check configuration"
echo " hermes gateway install # Install gateway service (messaging + cron)" if is_termux; then
echo " hermes gateway # Run gateway in foreground"
else
echo " hermes gateway install # Install gateway service (messaging + cron)"
fi
echo " hermes cron list # View scheduled jobs" echo " hermes cron list # View scheduled jobs"
echo " hermes doctor # Diagnose issues" echo " hermes doctor # Diagnose issues"
echo "" echo ""

View file

@ -0,0 +1,21 @@
from pathlib import Path
import subprocess
REPO_ROOT = Path(__file__).resolve().parents[2]
SETUP_SCRIPT = REPO_ROOT / "setup-hermes.sh"
def test_setup_hermes_script_is_valid_shell():
result = subprocess.run(["bash", "-n", str(SETUP_SCRIPT)], capture_output=True, text=True)
assert result.returncode == 0, result.stderr
def test_setup_hermes_script_has_termux_path():
content = SETUP_SCRIPT.read_text(encoding="utf-8")
assert "is_termux()" in content
assert ".[termux]" in content
assert "constraints-termux.txt" in content
assert "$PREFIX/bin" in content
assert "Skipping tinker-atropos on Termux" in content