mirror of
https://github.com/usestrix/strix.git
synced 2026-09-30 01:52:18 +00:00
Review feedback on the conflict guidance, both valid. The pipx hint keyed off `.local/bin` appearing in the path, which is where a uv-managed or hand-placed strix lands too. Telling that user to `pipx uninstall strix-agent` either does nothing or removes a different package, and leaves the real PATH conflict in place. Ask pipx instead: compare the executable's directory against `PIPX_BIN_DIR` and confirm `strix-agent` is in `pipx list`. The `rm` suggestion printed the path bare, so copying it would split on spaces or expand a glob. Print it through `printf '%q'`. The decoy tests asserted that pipx was never invoked at all, which the ownership check now legitimately does. They assert no `uninstall` instead, which is the contract that matters. `describe_removal` only runs when another executable wins PATH resolution, which a successful install prevents, so the advice itself is now tested by lifting the functions out of the script and calling them directly. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
368 lines
12 KiB
Bash
Executable file
368 lines
12 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
|
|
set -euo pipefail
|
|
|
|
APP=strix
|
|
REPO="usestrix/strix"
|
|
STRIX_IMAGE="ghcr.io/usestrix/strix-sandbox:1.3.0"
|
|
|
|
MUTED='\033[0;2m'
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
CYAN='\033[0;36m'
|
|
NC='\033[0m'
|
|
|
|
requested_version=${VERSION:-}
|
|
SKIP_DOWNLOAD=false
|
|
|
|
raw_os=$(uname -s)
|
|
os=$(echo "$raw_os" | tr '[:upper:]' '[:lower:]')
|
|
case "$raw_os" in
|
|
Darwin*) os="macos" ;;
|
|
Linux*) os="linux" ;;
|
|
MINGW*|MSYS*|CYGWIN*) os="windows" ;;
|
|
esac
|
|
|
|
arch=$(uname -m)
|
|
if [[ "$arch" == "aarch64" ]]; then
|
|
arch="arm64"
|
|
fi
|
|
if [[ "$arch" == "x86_64" ]]; then
|
|
arch="x86_64"
|
|
fi
|
|
|
|
if [ "$os" = "macos" ] && [ "$arch" = "x86_64" ]; then
|
|
rosetta_flag=$(sysctl -n sysctl.proc_translated 2>/dev/null || echo 0)
|
|
if [ "$rosetta_flag" = "1" ]; then
|
|
arch="arm64"
|
|
fi
|
|
fi
|
|
|
|
combo="$os-$arch"
|
|
case "$combo" in
|
|
linux-x86_64|linux-arm64|macos-x86_64|macos-arm64|windows-x86_64)
|
|
;;
|
|
*)
|
|
echo -e "${RED}Unsupported OS/Arch: $os/$arch${NC}"
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
archive_ext=".tar.gz"
|
|
if [ "$os" = "windows" ]; then
|
|
archive_ext=".zip"
|
|
fi
|
|
|
|
target="$os-$arch"
|
|
|
|
if [ "$os" = "linux" ]; then
|
|
if ! command -v tar >/dev/null 2>&1; then
|
|
echo -e "${RED}Error: 'tar' is required but not installed.${NC}"
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
if [ "$os" = "windows" ]; then
|
|
if ! command -v unzip >/dev/null 2>&1; then
|
|
echo -e "${RED}Error: 'unzip' is required but not installed.${NC}"
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
INSTALL_DIR=$HOME/.strix/bin
|
|
mkdir -p "$INSTALL_DIR"
|
|
|
|
if [ -z "$requested_version" ]; then
|
|
specific_version=$(curl -s "https://api.github.com/repos/$REPO/releases/latest" | sed -n 's/.*"tag_name": *"v\([^"]*\)".*/\1/p')
|
|
if [[ $? -ne 0 || -z "$specific_version" ]]; then
|
|
echo -e "${RED}Failed to fetch version information${NC}"
|
|
exit 1
|
|
fi
|
|
else
|
|
specific_version=$requested_version
|
|
fi
|
|
|
|
filename="$APP-${specific_version}-${target}${archive_ext}"
|
|
url="https://github.com/$REPO/releases/download/v${specific_version}/$filename"
|
|
|
|
print_message() {
|
|
local level=$1
|
|
local message=$2
|
|
local color=""
|
|
case $level in
|
|
info) color="${NC}" ;;
|
|
success) color="${GREEN}" ;;
|
|
warning) color="${YELLOW}" ;;
|
|
error) color="${RED}" ;;
|
|
esac
|
|
echo -e "${color}${message}${NC}"
|
|
}
|
|
|
|
pipx_owns() {
|
|
# Ask pipx instead of guessing from the path. A uv-managed or hand-placed
|
|
# strix can sit in ~/.local/bin too, and telling the user to
|
|
# "pipx uninstall strix-agent" would then either do nothing or uninstall a
|
|
# different package than the one winning PATH resolution.
|
|
local path=$1
|
|
local bin_dir
|
|
|
|
command -v pipx >/dev/null 2>&1 || return 1
|
|
bin_dir=$(pipx environment --value PIPX_BIN_DIR 2>/dev/null) || return 1
|
|
[[ -n "$bin_dir" ]] || return 1
|
|
[[ "$(dirname "$path")" == "${bin_dir%/}" ]] || return 1
|
|
pipx list --short 2>/dev/null | grep -q "^strix-agent "
|
|
}
|
|
|
|
describe_removal() {
|
|
local path=$1
|
|
|
|
if pipx_owns "$path"; then
|
|
echo -e "${MUTED} pipx installed it. To remove it: ${NC}pipx uninstall strix-agent"
|
|
else
|
|
# Quote the path: a copied "rm" of a path with spaces or globbing
|
|
# characters would otherwise split or expand.
|
|
echo -e "${MUTED} To remove it: ${NC}rm $(printf '%q' "$path")"
|
|
fi
|
|
}
|
|
|
|
check_existing_installation() {
|
|
UNMANAGED_INSTALLATIONS=()
|
|
|
|
local found_paths=()
|
|
while IFS= read -r -d '' path; do
|
|
found_paths+=("$path")
|
|
done < <(which -a strix 2>/dev/null | tr '\n' '\0' || true)
|
|
|
|
for path in "${found_paths[@]:-}"; do
|
|
if [[ -z "$path" ]] || [[ ! -e "$path" ]] || [[ "$path" == "$INSTALL_DIR/strix"* ]]; then
|
|
continue
|
|
fi
|
|
|
|
UNMANAGED_INSTALLATIONS+=("$path")
|
|
echo -e "${MUTED}Found another strix at: ${NC}$path"
|
|
done
|
|
|
|
if [ ${#UNMANAGED_INSTALLATIONS[@]} -gt 0 ]; then
|
|
echo -e "${MUTED}This installer only manages ${NC}$INSTALL_DIR${MUTED}, so it is left alone.${NC}"
|
|
fi
|
|
}
|
|
|
|
check_version() {
|
|
check_existing_installation
|
|
|
|
if [[ -x "$INSTALL_DIR/strix" ]]; then
|
|
installed_version=$("$INSTALL_DIR/strix" --version 2>/dev/null | awk '{print $2}' || echo "")
|
|
if [[ "$installed_version" == "$specific_version" ]]; then
|
|
print_message info "${GREEN}✓ Strix ${NC}$specific_version${GREEN} already installed${NC}"
|
|
SKIP_DOWNLOAD=true
|
|
elif [[ -n "$installed_version" ]]; then
|
|
print_message info "${MUTED}Installed: ${NC}$installed_version ${MUTED}→ Upgrading to ${NC}$specific_version"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
download_and_install() {
|
|
print_message info "\n${CYAN}🦉 Installing Strix${NC} ${MUTED}version: ${NC}$specific_version"
|
|
print_message info "${MUTED}Platform: ${NC}$target\n"
|
|
|
|
local tmp_dir=$(mktemp -d)
|
|
cd "$tmp_dir"
|
|
|
|
echo -e "${MUTED}Downloading...${NC}"
|
|
curl -# -L -o "$filename" "$url"
|
|
|
|
if [ ! -f "$filename" ]; then
|
|
echo -e "${RED}Download failed${NC}"
|
|
exit 1
|
|
fi
|
|
|
|
echo -e "${MUTED}Extracting...${NC}"
|
|
if [ "$os" = "windows" ]; then
|
|
unzip -q "$filename"
|
|
mv "strix-${specific_version}-${target}.exe" "$INSTALL_DIR/strix.exe"
|
|
else
|
|
tar -xzf "$filename"
|
|
mv "strix-${specific_version}-${target}" "$INSTALL_DIR/strix"
|
|
chmod 755 "$INSTALL_DIR/strix"
|
|
fi
|
|
|
|
cd - > /dev/null
|
|
rm -rf "$tmp_dir"
|
|
|
|
echo -e "${GREEN}✓ Strix installed to $INSTALL_DIR${NC}"
|
|
}
|
|
|
|
check_docker() {
|
|
echo ""
|
|
if ! command -v docker >/dev/null 2>&1; then
|
|
echo -e "${YELLOW}⚠ Docker not found${NC}"
|
|
echo -e "${MUTED}Strix requires Docker to run the security sandbox.${NC}"
|
|
echo -e "${MUTED}Please install Docker: ${NC}https://docs.docker.com/get-docker/"
|
|
echo ""
|
|
return 1
|
|
fi
|
|
|
|
if ! docker info >/dev/null 2>&1; then
|
|
echo -e "${YELLOW}⚠ Docker daemon not running${NC}"
|
|
echo -e "${MUTED}Please start Docker and run: ${NC}docker pull $STRIX_IMAGE"
|
|
echo ""
|
|
return 1
|
|
fi
|
|
|
|
echo -e "${MUTED}Checking for sandbox image...${NC}"
|
|
if docker image inspect "$STRIX_IMAGE" >/dev/null 2>&1; then
|
|
echo -e "${GREEN}✓ Sandbox image already available${NC}"
|
|
else
|
|
echo -e "${MUTED}Pulling sandbox image (this may take a few minutes)...${NC}"
|
|
if docker pull "$STRIX_IMAGE"; then
|
|
echo -e "${GREEN}✓ Sandbox image pulled successfully${NC}"
|
|
else
|
|
echo -e "${YELLOW}⚠ Failed to pull sandbox image${NC}"
|
|
echo -e "${MUTED}You can pull it manually later: ${NC}docker pull $STRIX_IMAGE"
|
|
fi
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
add_to_path() {
|
|
local config_file=$1
|
|
local command=$2
|
|
|
|
if grep -Fxq "$command" "$config_file" 2>/dev/null; then
|
|
print_message info "${MUTED}PATH already configured in ${NC}$config_file"
|
|
elif [[ -w $config_file ]]; then
|
|
echo -e "\n# strix" >> "$config_file"
|
|
echo "$command" >> "$config_file"
|
|
print_message info "${MUTED}Successfully added ${NC}strix ${MUTED}to \$PATH in ${NC}$config_file"
|
|
else
|
|
print_message warning "Manually add the directory to $config_file (or similar):"
|
|
print_message info " $command"
|
|
fi
|
|
}
|
|
|
|
setup_path() {
|
|
XDG_CONFIG_HOME=${XDG_CONFIG_HOME:-$HOME/.config}
|
|
current_shell=$(basename "$SHELL")
|
|
|
|
case $current_shell in
|
|
fish)
|
|
config_files="$HOME/.config/fish/config.fish"
|
|
;;
|
|
zsh)
|
|
config_files="${ZDOTDIR:-$HOME}/.zshrc ${ZDOTDIR:-$HOME}/.zshenv $XDG_CONFIG_HOME/zsh/.zshrc $XDG_CONFIG_HOME/zsh/.zshenv"
|
|
;;
|
|
bash)
|
|
config_files="$HOME/.bashrc $HOME/.bash_profile $HOME/.profile $XDG_CONFIG_HOME/bash/.bashrc $XDG_CONFIG_HOME/bash/.bash_profile"
|
|
;;
|
|
ash)
|
|
config_files="$HOME/.ashrc $HOME/.profile /etc/profile"
|
|
;;
|
|
sh)
|
|
config_files="$HOME/.ashrc $HOME/.profile /etc/profile"
|
|
;;
|
|
*)
|
|
config_files="$HOME/.bashrc $HOME/.bash_profile $XDG_CONFIG_HOME/bash/.bashrc $XDG_CONFIG_HOME/bash/.bash_profile"
|
|
;;
|
|
esac
|
|
|
|
config_file=""
|
|
for file in $config_files; do
|
|
if [[ -f $file ]]; then
|
|
config_file=$file
|
|
break
|
|
fi
|
|
done
|
|
|
|
if [[ -z $config_file ]]; then
|
|
print_message warning "No config file found for $current_shell. You may need to manually add to PATH:"
|
|
print_message info " export PATH=$INSTALL_DIR:\$PATH"
|
|
elif [[ ":$PATH:" != *":$INSTALL_DIR:"* ]]; then
|
|
case $current_shell in
|
|
fish)
|
|
add_to_path "$config_file" "fish_add_path $INSTALL_DIR"
|
|
;;
|
|
zsh)
|
|
add_to_path "$config_file" "export PATH=$INSTALL_DIR:\$PATH"
|
|
;;
|
|
bash)
|
|
add_to_path "$config_file" "export PATH=$INSTALL_DIR:\$PATH"
|
|
;;
|
|
ash)
|
|
add_to_path "$config_file" "export PATH=$INSTALL_DIR:\$PATH"
|
|
;;
|
|
sh)
|
|
add_to_path "$config_file" "export PATH=$INSTALL_DIR:\$PATH"
|
|
;;
|
|
*)
|
|
export PATH=$INSTALL_DIR:$PATH
|
|
print_message warning "Manually add the directory to $config_file (or similar):"
|
|
print_message info " export PATH=$INSTALL_DIR:\$PATH"
|
|
;;
|
|
esac
|
|
fi
|
|
|
|
if [ -n "${GITHUB_ACTIONS-}" ] && [ "${GITHUB_ACTIONS}" == "true" ]; then
|
|
echo "$INSTALL_DIR" >> "$GITHUB_PATH"
|
|
print_message info "Added $INSTALL_DIR to \$GITHUB_PATH"
|
|
fi
|
|
}
|
|
|
|
verify_installation() {
|
|
export PATH="$INSTALL_DIR:$PATH"
|
|
|
|
local which_strix=$(which strix 2>/dev/null || echo "")
|
|
|
|
if [[ "$which_strix" != "$INSTALL_DIR/strix" && "$which_strix" != "$INSTALL_DIR/strix.exe" ]]; then
|
|
if [[ -n "$which_strix" ]]; then
|
|
echo -e "${YELLOW}⚠ Another strix wins PATH resolution: ${NC}$which_strix"
|
|
echo -e "${MUTED}The version just installed lives in ${NC}$INSTALL_DIR${MUTED} and will not run until that changes.${NC}"
|
|
echo -e "${MUTED}Put ${NC}$INSTALL_DIR${MUTED} earlier on your PATH, or remove the other executable yourself.${NC}"
|
|
describe_removal "$which_strix"
|
|
fi
|
|
fi
|
|
|
|
if [[ -x "$INSTALL_DIR/strix" ]]; then
|
|
local version=$("$INSTALL_DIR/strix" --version 2>/dev/null | awk '{print $2}' || echo "unknown")
|
|
echo -e "${GREEN}✓ Strix ${NC}$version${GREEN} ready${NC}"
|
|
fi
|
|
}
|
|
|
|
check_version
|
|
if [ "$SKIP_DOWNLOAD" = false ]; then
|
|
download_and_install
|
|
fi
|
|
setup_path
|
|
verify_installation
|
|
check_docker
|
|
|
|
echo ""
|
|
echo -e "${CYAN}"
|
|
echo " ███████╗████████╗██████╗ ██╗██╗ ██╗"
|
|
echo " ██╔════╝╚══██╔══╝██╔══██╗██║╚██╗██╔╝"
|
|
echo " ███████╗ ██║ ██████╔╝██║ ╚███╔╝ "
|
|
echo " ╚════██║ ██║ ██╔══██╗██║ ██╔██╗ "
|
|
echo " ███████║ ██║ ██║ ██║██║██╔╝ ██╗"
|
|
echo " ╚══════╝ ╚═╝ ╚═╝ ╚═╝╚═╝╚═╝ ╚═╝"
|
|
echo -e "${NC}"
|
|
echo -e "${MUTED} AI Penetration Testing Agent${NC}"
|
|
echo ""
|
|
echo -e "${MUTED}To get started:${NC}"
|
|
echo ""
|
|
echo -e " ${CYAN}1.${NC} Set your environment:"
|
|
echo -e " ${MUTED}export LLM_API_KEY='your-api-key'${NC}"
|
|
echo -e " ${MUTED}export STRIX_LLM='openai/gpt-5.4'${NC}"
|
|
echo ""
|
|
echo -e " ${CYAN}2.${NC} Run a penetration test:"
|
|
echo -e " ${MUTED}strix --target https://example.com${NC}"
|
|
echo ""
|
|
echo -e "${MUTED}For more information visit ${NC}https://strix.ai"
|
|
echo -e "${MUTED}Supported models ${NC}https://docs.strix.ai/llm-providers/overview"
|
|
echo -e "${MUTED}Join our community ${NC}https://discord.gg/strix-ai"
|
|
echo ""
|
|
echo -e "${MUTED}Run a pentest in Strix Cloud ${NC}https://app.strix.ai"
|
|
echo -e "${MUTED}Enterprise ${NC}https://strix.ai/demo"
|
|
echo ""
|
|
|
|
echo -e "${YELLOW}→${NC} Run ${MUTED}source ~/.$(basename $SHELL)rc${NC} or open a new terminal"
|
|
echo ""
|