feat(install): enhance installation script for build tools and interactive prompts

- Updated the installation script to check for necessary build tools on Debian/Ubuntu systems and prompt the user to install them if missing.
- Improved user interaction by redirecting input from /dev/tty for prompts, ensuring compatibility when the script is piped from curl.
- Added checks to verify the successful installation of the main package and provide guidance if installation fails.
- Enhanced the handling of shell configuration files to ensure ~/.local/bin is added to PATH for various shell types.
This commit is contained in:
teknium1 2026-02-26 11:37:38 -08:00
parent a8ccaca8ea
commit 760fb2ca0e
2 changed files with 89 additions and 24 deletions

View file

@ -468,7 +468,7 @@ install_system_packages() {
# sudo needs password — ask once for everything # sudo needs password — ask once for everything
elif command -v sudo &> /dev/null; then elif command -v sudo &> /dev/null; then
echo "" echo ""
read -p "Install ${description}? (requires sudo) [y/N] " -n 1 -r read -p "Install ${description}? (requires sudo) [y/N] " -n 1 -r < /dev/tty
echo echo
if [[ $REPLY =~ ^[Yy]$ ]]; then if [[ $REPLY =~ ^[Yy]$ ]]; then
if sudo $install_cmd; then if sudo $install_cmd; then
@ -595,8 +595,45 @@ install_deps() {
export VIRTUAL_ENV="$INSTALL_DIR/venv" export VIRTUAL_ENV="$INSTALL_DIR/venv"
fi fi
# Install the main package in editable mode with all extras # On Debian/Ubuntu (including WSL), some Python packages need build tools.
$UV_CMD pip install -e ".[all]" || $UV_CMD pip install -e "." # Check and offer to install them if missing.
if [ "$DISTRO" = "ubuntu" ] || [ "$DISTRO" = "debian" ]; then
local need_build_tools=false
for pkg in gcc python3-dev libffi-dev; do
if ! dpkg -s "$pkg" &>/dev/null; then
need_build_tools=true
break
fi
done
if [ "$need_build_tools" = true ]; then
log_info "Some build tools may be needed for Python packages..."
if command -v sudo &> /dev/null; then
if sudo -n true 2>/dev/null; then
sudo apt-get update -qq && sudo apt-get install -y -qq build-essential python3-dev libffi-dev >/dev/null 2>&1 || true
log_success "Build tools installed"
else
read -p "Install build tools (build-essential, python3-dev)? (requires sudo) [Y/n] " -n 1 -r < /dev/tty
echo
if [[ $REPLY =~ ^[Yy]$ ]] || [[ -z $REPLY ]]; then
sudo apt-get update -qq && sudo apt-get install -y -qq build-essential python3-dev libffi-dev >/dev/null 2>&1 || true
log_success "Build tools installed"
fi
fi
fi
fi
fi
# Install the main package in editable mode with all extras.
# Try [all] first, fall back to base install if extras have issues.
if ! $UV_CMD pip install -e ".[all]" 2>/dev/null; then
log_warn "Full install (.[all]) failed, trying base install..."
if ! $UV_CMD pip install -e "."; then
log_error "Package installation failed."
log_info "Check that build tools are installed: sudo apt install build-essential python3-dev"
log_info "Then re-run: cd $INSTALL_DIR && uv pip install -e '.[all]'"
exit 1
fi
fi
log_success "Main package installed" log_success "Main package installed"
@ -633,35 +670,56 @@ setup_path() {
fi fi
fi fi
# Verify the entry point script was actually generated
if [ ! -x "$HERMES_BIN" ]; then
log_warn "hermes entry point not found at $HERMES_BIN"
log_info "This usually means the pip install didn't complete successfully."
log_info "Try: cd $INSTALL_DIR && uv pip install -e '.[all]'"
return 0
fi
# Create symlink in ~/.local/bin (standard user binary location, usually on PATH) # Create symlink in ~/.local/bin (standard user binary location, usually on PATH)
mkdir -p "$HOME/.local/bin" mkdir -p "$HOME/.local/bin"
ln -sf "$HERMES_BIN" "$HOME/.local/bin/hermes" ln -sf "$HERMES_BIN" "$HOME/.local/bin/hermes"
log_success "Symlinked hermes → ~/.local/bin/hermes" log_success "Symlinked hermes → ~/.local/bin/hermes"
# Check if ~/.local/bin is on PATH; if not, add it to shell config # Check if ~/.local/bin is on PATH; if not, add it to shell config.
# Detect the user's actual login shell (not the shell running this script,
# which is always bash when piped from curl).
if ! echo "$PATH" | tr ':' '\n' | grep -q "^$HOME/.local/bin$"; then if ! echo "$PATH" | tr ':' '\n' | grep -q "^$HOME/.local/bin$"; then
SHELL_CONFIG="" SHELL_CONFIGS=()
if [ -n "$BASH_VERSION" ]; then LOGIN_SHELL="$(basename "${SHELL:-/bin/bash}")"
if [ -f "$HOME/.bashrc" ]; then case "$LOGIN_SHELL" in
SHELL_CONFIG="$HOME/.bashrc" zsh)
elif [ -f "$HOME/.bash_profile" ]; then [ -f "$HOME/.zshrc" ] && SHELL_CONFIGS+=("$HOME/.zshrc")
SHELL_CONFIG="$HOME/.bash_profile" ;;
fi bash)
elif [ -n "$ZSH_VERSION" ] || [ -f "$HOME/.zshrc" ]; then [ -f "$HOME/.bashrc" ] && SHELL_CONFIGS+=("$HOME/.bashrc")
SHELL_CONFIG="$HOME/.zshrc" [ -f "$HOME/.bash_profile" ] && SHELL_CONFIGS+=("$HOME/.bash_profile")
fi ;;
*)
[ -f "$HOME/.bashrc" ] && SHELL_CONFIGS+=("$HOME/.bashrc")
[ -f "$HOME/.zshrc" ] && SHELL_CONFIGS+=("$HOME/.zshrc")
;;
esac
# Also ensure ~/.profile has it (sourced by login shells on
# Ubuntu/Debian/WSL even when ~/.bashrc is skipped)
[ -f "$HOME/.profile" ] && SHELL_CONFIGS+=("$HOME/.profile")
PATH_LINE='export PATH="$HOME/.local/bin:$PATH"' PATH_LINE='export PATH="$HOME/.local/bin:$PATH"'
if [ -n "$SHELL_CONFIG" ]; then for SHELL_CONFIG in "${SHELL_CONFIGS[@]}"; do
if ! grep -q '\.local/bin' "$SHELL_CONFIG" 2>/dev/null; then if ! grep -q '\.local/bin' "$SHELL_CONFIG" 2>/dev/null; then
echo "" >> "$SHELL_CONFIG" echo "" >> "$SHELL_CONFIG"
echo "# Hermes Agent — ensure ~/.local/bin is on PATH" >> "$SHELL_CONFIG" echo "# Hermes Agent — ensure ~/.local/bin is on PATH" >> "$SHELL_CONFIG"
echo "$PATH_LINE" >> "$SHELL_CONFIG" echo "$PATH_LINE" >> "$SHELL_CONFIG"
log_success "Added ~/.local/bin to PATH in $SHELL_CONFIG" log_success "Added ~/.local/bin to PATH in $SHELL_CONFIG"
else
log_info "~/.local/bin already referenced in $SHELL_CONFIG"
fi fi
done
if [ ${#SHELL_CONFIGS[@]} -eq 0 ]; then
log_warn "Could not detect shell config file to add ~/.local/bin to PATH"
log_info "Add manually: $PATH_LINE"
fi fi
else else
log_info "~/.local/bin already on PATH" log_info "~/.local/bin already on PATH"
@ -777,11 +835,12 @@ run_setup_wizard() {
cd "$INSTALL_DIR" cd "$INSTALL_DIR"
# Run hermes setup using the venv Python directly (no activation needed) # Run hermes setup using the venv Python directly (no activation needed).
# Redirect stdin from /dev/tty so interactive prompts work when piped from curl.
if [ "$USE_VENV" = true ]; then if [ "$USE_VENV" = true ]; then
"$INSTALL_DIR/venv/bin/python" -m hermes_cli.main setup "$INSTALL_DIR/venv/bin/python" -m hermes_cli.main setup < /dev/tty
else else
python -m hermes_cli.main setup python -m hermes_cli.main setup < /dev/tty
fi fi
} }
@ -817,7 +876,7 @@ maybe_start_gateway() {
log_info "WhatsApp is enabled but not yet paired." log_info "WhatsApp is enabled but not yet paired."
log_info "Running 'hermes whatsapp' to pair via QR code..." log_info "Running 'hermes whatsapp' to pair via QR code..."
echo "" echo ""
read -p "Pair WhatsApp now? [Y/n] " -n 1 -r read -p "Pair WhatsApp now? [Y/n] " -n 1 -r < /dev/tty
echo echo
if [[ $REPLY =~ ^[Yy]$ ]] || [[ -z $REPLY ]]; then if [[ $REPLY =~ ^[Yy]$ ]] || [[ -z $REPLY ]]; then
HERMES_CMD="$HOME/.local/bin/hermes" HERMES_CMD="$HOME/.local/bin/hermes"
@ -827,7 +886,7 @@ maybe_start_gateway() {
fi fi
echo "" echo ""
read -p "Would you like to install the gateway as a background service? [Y/n] " -n 1 -r read -p "Would you like to install the gateway as a background service? [Y/n] " -n 1 -r < /dev/tty
echo echo
if [[ $REPLY =~ ^[Yy]$ ]] || [[ -z $REPLY ]]; then if [[ $REPLY =~ ^[Yy]$ ]] || [[ -z $REPLY ]]; then

View file

@ -73,8 +73,14 @@ class DockerEnvironment(BaseEnvironment):
resource_args.extend(["--cpus", str(cpu)]) resource_args.extend(["--cpus", str(cpu)])
if memory > 0: if memory > 0:
resource_args.extend(["--memory", f"{memory}m"]) resource_args.extend(["--memory", f"{memory}m"])
if disk > 0 and sys.platform != "darwin" and self._storage_opt_supported(): if disk > 0 and sys.platform != "darwin":
resource_args.extend(["--storage-opt", f"size={disk}m"]) if self._storage_opt_supported():
resource_args.extend(["--storage-opt", f"size={disk}m"])
else:
logger.warning(
"Docker storage driver does not support per-container disk limits "
"(requires overlay2 on XFS with pquota). Container will run without disk quota."
)
if not network: if not network:
resource_args.append("--network=none") resource_args.append("--network=none")