feat: add PyInstaller build for standalone binary distribution
- Add PyInstaller spec file and build script for creating standalone executables - Add install.sh for curl | sh installation from GitHub releases - Add GitHub Actions workflow for multi-platform builds (macOS, Linux, Windows) - Move sandbox-only deps (playwright, ipython, libtmux, etc.) to optional extras - Make google-cloud-aiplatform optional ([vertex] extra) to reduce binary size - Use lazy imports in tool actions to avoid loading sandbox deps at startup - Add -v/--version flag to CLI - Add website and Discord links to completion message - Binary size: ~97MB (down from ~120MB with all deps)
This commit is contained in:
98
scripts/build.sh
Executable file
98
scripts/build.sh
Executable file
@@ -0,0 +1,98 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
echo -e "${BLUE}🦉 Strix Build Script${NC}"
|
||||
echo "================================"
|
||||
|
||||
OS="$(uname -s)"
|
||||
ARCH="$(uname -m)"
|
||||
|
||||
case "$OS" in
|
||||
Linux*) OS_NAME="linux";;
|
||||
Darwin*) OS_NAME="macos";;
|
||||
MINGW*|MSYS*|CYGWIN*) OS_NAME="windows";;
|
||||
*) OS_NAME="unknown";;
|
||||
esac
|
||||
|
||||
case "$ARCH" in
|
||||
x86_64|amd64) ARCH_NAME="x86_64";;
|
||||
arm64|aarch64) ARCH_NAME="arm64";;
|
||||
*) ARCH_NAME="$ARCH";;
|
||||
esac
|
||||
|
||||
echo -e "${YELLOW}Platform:${NC} $OS_NAME-$ARCH_NAME"
|
||||
|
||||
cd "$PROJECT_ROOT"
|
||||
|
||||
if ! command -v poetry &> /dev/null; then
|
||||
echo -e "${RED}Error: Poetry is not installed${NC}"
|
||||
echo "Please install Poetry first: https://python-poetry.org/docs/#installation"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "\n${BLUE}Installing dependencies...${NC}"
|
||||
poetry install --with dev
|
||||
|
||||
VERSION=$(poetry version -s)
|
||||
echo -e "${YELLOW}Version:${NC} $VERSION"
|
||||
|
||||
echo -e "\n${BLUE}Cleaning previous builds...${NC}"
|
||||
rm -rf build/ dist/
|
||||
|
||||
echo -e "\n${BLUE}Building binary with PyInstaller...${NC}"
|
||||
poetry run pyinstaller strix.spec --noconfirm
|
||||
|
||||
RELEASE_DIR="dist/release"
|
||||
mkdir -p "$RELEASE_DIR"
|
||||
|
||||
BINARY_NAME="strix-${VERSION}-${OS_NAME}-${ARCH_NAME}"
|
||||
|
||||
if [ "$OS_NAME" = "windows" ]; then
|
||||
if [ ! -f "dist/strix.exe" ]; then
|
||||
echo -e "${RED}Build failed: Binary not found${NC}"
|
||||
exit 1
|
||||
fi
|
||||
BINARY_NAME="${BINARY_NAME}.exe"
|
||||
cp "dist/strix.exe" "$RELEASE_DIR/$BINARY_NAME"
|
||||
echo -e "\n${BLUE}Creating zip...${NC}"
|
||||
ARCHIVE_NAME="${BINARY_NAME%.exe}.zip"
|
||||
|
||||
if command -v 7z &> /dev/null; then
|
||||
7z a "$RELEASE_DIR/$ARCHIVE_NAME" "$RELEASE_DIR/$BINARY_NAME"
|
||||
else
|
||||
powershell -Command "Compress-Archive -Path '$RELEASE_DIR/$BINARY_NAME' -DestinationPath '$RELEASE_DIR/$ARCHIVE_NAME'"
|
||||
fi
|
||||
echo -e "${GREEN}Created:${NC} $RELEASE_DIR/$ARCHIVE_NAME"
|
||||
else
|
||||
if [ ! -f "dist/strix" ]; then
|
||||
echo -e "${RED}Build failed: Binary not found${NC}"
|
||||
exit 1
|
||||
fi
|
||||
cp "dist/strix" "$RELEASE_DIR/$BINARY_NAME"
|
||||
chmod +x "$RELEASE_DIR/$BINARY_NAME"
|
||||
echo -e "\n${BLUE}Creating tarball...${NC}"
|
||||
ARCHIVE_NAME="${BINARY_NAME}.tar.gz"
|
||||
tar -czvf "$RELEASE_DIR/$ARCHIVE_NAME" -C "$RELEASE_DIR" "$BINARY_NAME"
|
||||
echo -e "${GREEN}Created:${NC} $RELEASE_DIR/$ARCHIVE_NAME"
|
||||
fi
|
||||
|
||||
echo -e "\n${GREEN}Build successful!${NC}"
|
||||
echo "================================"
|
||||
echo -e "${YELLOW}Binary:${NC} $RELEASE_DIR/$BINARY_NAME"
|
||||
|
||||
SIZE=$(ls -lh "$RELEASE_DIR/$BINARY_NAME" | awk '{print $5}')
|
||||
echo -e "${YELLOW}Size:${NC} $SIZE"
|
||||
|
||||
echo -e "\n${BLUE}Testing binary...${NC}"
|
||||
"$RELEASE_DIR/$BINARY_NAME" --help > /dev/null 2>&1 && echo -e "${GREEN}Binary test passed!${NC}" || echo -e "${RED}Binary test failed${NC}"
|
||||
|
||||
echo -e "\n${GREEN}Done!${NC}"
|
||||
328
scripts/install.sh
Executable file
328
scripts/install.sh
Executable file
@@ -0,0 +1,328 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
APP=strix
|
||||
REPO="usestrix/strix"
|
||||
STRIX_IMAGE="ghcr.io/usestrix/strix-sandbox:0.1.10"
|
||||
|
||||
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|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}"
|
||||
}
|
||||
|
||||
check_existing_installation() {
|
||||
local found_paths=()
|
||||
while IFS= read -r -d '' path; do
|
||||
found_paths+=("$path")
|
||||
done < <(which -a strix 2>/dev/null | tr '\n' '\0' || true)
|
||||
|
||||
if [ ${#found_paths[@]} -gt 0 ]; then
|
||||
for path in "${found_paths[@]}"; do
|
||||
if [[ ! -e "$path" ]] || [[ "$path" == "$INSTALL_DIR/strix"* ]]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
if [[ -n "$path" ]]; then
|
||||
echo -e "${MUTED}Found existing strix at: ${NC}$path"
|
||||
|
||||
if [[ "$path" == *".local/bin"* ]]; then
|
||||
echo -e "${MUTED}Removing old pipx installation...${NC}"
|
||||
if command -v pipx >/dev/null 2>&1; then
|
||||
pipx uninstall strix-agent 2>/dev/null || true
|
||||
fi
|
||||
rm -f "$path" 2>/dev/null || true
|
||||
elif [[ -L "$path" || -f "$path" ]]; then
|
||||
echo -e "${MUTED}Removing old installation...${NC}"
|
||||
rm -f "$path" 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
done
|
||||
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
|
||||
return 0
|
||||
elif [[ -w $config_file ]]; then
|
||||
echo -e "\n# strix" >> "$config_file"
|
||||
echo "$command" >> "$config_file"
|
||||
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="$HOME/.zshrc $HOME/.zshenv"
|
||||
;;
|
||||
bash)
|
||||
config_files="$HOME/.bashrc $HOME/.bash_profile $HOME/.profile"
|
||||
;;
|
||||
*)
|
||||
config_files="$HOME/.bashrc $HOME/.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
|
||||
config_file="$HOME/.bashrc"
|
||||
touch "$config_file"
|
||||
fi
|
||||
|
||||
if [[ ":$PATH:" != *":$INSTALL_DIR:"* ]]; then
|
||||
case $current_shell in
|
||||
fish)
|
||||
add_to_path "$config_file" "fish_add_path $INSTALL_DIR"
|
||||
;;
|
||||
*)
|
||||
add_to_path "$config_file" "export PATH=\"$INSTALL_DIR:\$PATH\""
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
if [ -n "${GITHUB_ACTIONS-}" ] && [ "${GITHUB_ACTIONS}" == "true" ]; then
|
||||
echo "$INSTALL_DIR" >> "$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}⚠ Found conflicting strix at: ${NC}$which_strix"
|
||||
echo -e "${MUTED}Attempting to remove...${NC}"
|
||||
|
||||
if rm -f "$which_strix" 2>/dev/null; then
|
||||
echo -e "${GREEN}✓ Removed conflicting installation${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}Could not remove automatically.${NC}"
|
||||
echo -e "${MUTED}Please remove manually: ${NC}rm $which_strix"
|
||||
fi
|
||||
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 LLM provider:"
|
||||
echo -e " ${MUTED}export STRIX_LLM='openai/gpt-5'${NC}"
|
||||
echo -e " ${MUTED}export LLM_API_KEY='your-api-key'${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://usestrix.com"
|
||||
echo -e "${MUTED}Join our community ${NC}https://discord.gg/YjKFvEZSdZ"
|
||||
echo ""
|
||||
|
||||
if [[ ":$PATH:" != *":$INSTALL_DIR:"* ]]; then
|
||||
echo -e "${YELLOW}→${NC} Run ${MUTED}source ~/.$(basename $SHELL)rc${NC} or open a new terminal"
|
||||
echo ""
|
||||
fi
|
||||
Reference in New Issue
Block a user