From 461b7dbef3796d0474482bcc5f9c35c3c93a5403 Mon Sep 17 00:00:00 2001 From: ~JADIS Date: Mon, 12 Jan 2026 17:34:37 -0600 Subject: [PATCH] Unify installer into scripts/install.sh --- README.md | 19 +- docs/ABOUT.md | 2 +- docs/INSTILLATION.md | 33 ++- docs/TROUBLESHOOTING.md | 8 +- docs/index.md | 4 +- docs/install.md | 4 +- install.sh | 69 ------ install_deps.sh | 403 --------------------------------- scripts/install.sh | 480 ++++++++++++++++++++++++++++++++++++++++ 9 files changed, 515 insertions(+), 507 deletions(-) delete mode 100755 install.sh delete mode 100755 install_deps.sh create mode 100755 scripts/install.sh diff --git a/README.md b/README.md index c3ef51b..858287d 100644 --- a/README.md +++ b/README.md @@ -44,14 +44,14 @@ uv tool install outervoid-dat pipx install outervoid-dat ``` -### curl/wget bootstrap (uv → pipx → pip --user) +### curl/wget bootstrap (pipx → uv → venv/pip --user) ```bash -curl -fsSL https://raw.githubusercontent.com/Outer-Void/dat/main/install.sh | bash +curl -fsSL https://raw.githubusercontent.com/Outer-Void/dat/v3.0.0/scripts/install.sh | bash ``` ```bash -wget -qO- https://raw.githubusercontent.com/Outer-Void/dat/main/install.sh | bash +wget -qO- https://raw.githubusercontent.com/Outer-Void/dat/v3.0.0/scripts/install.sh | bash ``` > Need PDF output? Install with `outervoid-dat[pdf]` (e.g., `uv tool install "outervoid-dat[pdf]"`). @@ -61,8 +61,8 @@ wget -qO- https://raw.githubusercontent.com/Outer-Void/dat/main/install.sh | bas ```bash git clone https://github.com/Outer-Void/dat.git cd dat -chmod +x dat install_deps.sh -./install_deps.sh +chmod +x scripts/install.sh +./scripts/install.sh --mode dev ./dat # default Markdown report in artifacts/report.md ``` @@ -71,7 +71,8 @@ chmod +x dat install_deps.sh dat # default report.md generates audit report with main files code base print output (Entire project codebase located within a single document) ``` -> Make sure you’re using a venv, `install_deps.sh` should create/activate first; the script supports it. Permissions errors? `chmod +x dat install_deps.sh`. +> Termux: the installer uses `pkg install python3` and avoids mixing prefixes. +> proot-distro: use the proot shell and avoid Termux paths in `$PATH`. ### Docker @@ -80,7 +81,7 @@ FROM python:3.11-slim WORKDIR /app COPY . . RUN pip install -r requirements.txt -RUN chmod +x dat install_deps.sh +RUN chmod +x scripts/install.sh ENTRYPOINT ["./dat"] ``` @@ -140,7 +141,7 @@ dat -o /path/to/custom_label.json run: | git clone https://github.com/Outer-Void/dat.git cd dat - ./install_deps.sh + ./scripts/install.sh - name: Security Scan run: | @@ -177,7 +178,7 @@ Repo link: **LRC — Local Repo Compiler** → [Outer-Void/lrc](https://github.c ## Troubleshooting -- **Permissions:** `chmod +x dat install_deps.sh bootstrap.sh` +- **Permissions:** `chmod +x scripts/install.sh bootstrap.sh` - **Missing deps:** `pip install --force-reinstall -r requirements.txt` (Linux may need `libmagic`). - **Termux:** `termux-setup-storage` and clone into `~/storage/shared` if needed. diff --git a/docs/ABOUT.md b/docs/ABOUT.md index be9b677..70c98ce 100644 --- a/docs/ABOUT.md +++ b/docs/ABOUT.md @@ -144,7 +144,7 @@ dat/ ### **Quick Installation** ```bash # Run the installer -./install_deps.sh +./scripts/install.sh # Or install manually pip install -e . diff --git a/docs/INSTILLATION.md b/docs/INSTILLATION.md index 51f6c6e..0f4fddc 100644 --- a/docs/INSTILLATION.md +++ b/docs/INSTILLATION.md @@ -55,8 +55,8 @@ unzip dat-v3.0.0-alpha.1.zip && cd dat ```bash # Run the comprehensive installer -chmod +x install_deps.sh -./install_deps.sh +chmod +x scripts/install.sh +./scripts/install.sh ``` ### What the installer does: @@ -76,15 +76,15 @@ chmod +x install_deps.sh ```bash # Ubuntu/Debian sudo apt-get update -./install_deps.sh +./scripts/install.sh # Fedora/RHEL sudo dnf groupinstall "Development Tools" -./install_deps.sh +./scripts/install.sh # Arch Linux sudo pacman -S base-devel -./install_deps.sh +./scripts/install.sh ``` ### macOS @@ -92,18 +92,18 @@ sudo pacman -S base-devel ```bash # With Homebrew (recommended) brew install python3 libmagic -./install_deps.sh +./scripts/install.sh # Without Homebrew python3 -m ensurepip -./install_deps.sh +./scripts/install.sh ``` ### Windows ```bash # Git Bash / WSL2 -./install_deps.sh +./scripts/install.sh # If Python not found, install from: # https://python.org/downloads/ @@ -114,7 +114,7 @@ python3 -m ensurepip ```bash pkg update && pkg upgrade pkg install python libmagic -./install_deps.sh +./scripts/install.sh ``` --- @@ -286,7 +286,7 @@ FROM python:3.11-slim WORKDIR /app COPY . . RUN pip install -r requirements.txt -RUN chmod +x dat install_deps.sh +RUN chmod +x scripts/install.sh ENTRYPOINT ["./dat"] ``` @@ -305,7 +305,7 @@ docker run -v $(pwd):/scan dat /scan --deep --json report.json run: | git clone https://github.com/Justadudeinspace/dat.git cd dat - ./install_deps.sh + ./scripts/install.sh - name: Security Scan run: | @@ -343,12 +343,12 @@ python3 --version || python --version ```bash # Fix script permissions -chmod +x dat install_deps.sh bootstrap.sh +chmod +x scripts/install.sh bootstrap.sh # Use virtual environment to avoid system installs python3 -m venv venv source venv/bin/activate -./install_deps.sh +./scripts/install.sh ``` ### Missing Dependencies @@ -439,11 +439,11 @@ ls -la *.pdf *.json *.asc && echo "✅ Installation successful" # Update to latest version cd dat git pull origin main -./install_deps.sh --upgrade +./scripts/install.sh --mode prod -# Or reinstall completely +# Or reinstall completely (venv install) rm -rf .venv -./install_deps.sh +./scripts/install.sh --venv --mode prod ``` --- @@ -480,4 +480,3 @@ DAT: 3.0.0-alpha.1 --- DAT is now installed and ready for enterprise security scanning and compliance auditing. - diff --git a/docs/TROUBLESHOOTING.md b/docs/TROUBLESHOOTING.md index 52fef7c..2efe4bc 100644 --- a/docs/TROUBLESHOOTING.md +++ b/docs/TROUBLESHOOTING.md @@ -48,7 +48,7 @@ export PATH="$PATH:$(pwd)" ```bash # Re-run installation -./install_deps.sh +./scripts/install.sh # Or manual installation python3 -m venv .venv @@ -124,7 +124,7 @@ pip3 install --user -r requirements.txt ```bash # Run with debug output -bash -x ./install_deps.sh +bash -x ./scripts/install.sh # Check Python version python3 --version # Requires 3.8+ @@ -512,7 +512,7 @@ dat . --no-sign ```bash # Fix script permissions -chmod +x dat install_deps.sh +chmod +x scripts/install.sh # Use virtual environment python3 -m venv venv @@ -587,7 +587,7 @@ rm -f ~/.datconfig # Fresh installation git clean -fdx -./install_deps.sh +./scripts/install.sh # Test basic functionality ./dat . --safe --report test.json diff --git a/docs/index.md b/docs/index.md index 493819b..1e925bd 100644 --- a/docs/index.md +++ b/docs/index.md @@ -11,8 +11,8 @@ title: DAT · Dev Audit Tool

Install quickstart

git clone https://github.com/Justadudeinspace/dat.git
 cd dat
-chmod +x install_deps.sh
-./install_deps.sh
+chmod +x scripts/install.sh
+./scripts/install.sh
 python3 dat

View on GitHub diff --git a/docs/install.md b/docs/install.md index 2e24dc0..91e4ded 100644 --- a/docs/install.md +++ b/docs/install.md @@ -7,8 +7,8 @@ title: Install ```bash git clone https://github.com/Justadudeinspace/dat.git cd dat -chmod +x install_deps.sh -./install_deps.sh +chmod +x scripts/install.sh +./scripts/install.sh python3 dat ``` diff --git a/install.sh b/install.sh deleted file mode 100755 index d135eb8..0000000 --- a/install.sh +++ /dev/null @@ -1,69 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail -IFS=$'\n\t' - -PKG_NAME="outervoid-dat" -EXTRAS="${DAT_EXTRAS:-}" - -if [[ -n "$EXTRAS" ]]; then - PKG_SPEC="${PKG_NAME}[${EXTRAS}]" -else - PKG_SPEC="${PKG_NAME}" -fi - -log() { - printf '%s\n' "$*" >&2 -} - -ensure_on_path() { - if command -v dat >/dev/null 2>&1; then - return 0 - fi - log "⚠️ 'dat' not found on PATH. You may need to add ~/.local/bin or the pipx bin dir." - return 0 -} - -install_with_uv() { - log "→ Installing via uv tool install: ${PKG_SPEC}" - uv tool install "${PKG_SPEC}" - ensure_on_path -} - -install_with_pipx() { - log "→ Installing via pipx: ${PKG_SPEC}" - pipx install "${PKG_SPEC}" - if command -v pipx >/dev/null 2>&1; then - pipx ensurepath >/dev/null 2>&1 || true - fi - ensure_on_path -} - -install_with_pip() { - local python_bin="${1}" - log "→ Installing via pip --user: ${PKG_SPEC}" - "${python_bin}" -m pip install --user "${PKG_SPEC}" - ensure_on_path -} - -main() { - if command -v uv >/dev/null 2>&1; then - install_with_uv - return 0 - fi - - if command -v pipx >/dev/null 2>&1; then - install_with_pipx - return 0 - fi - - local python_bin="" - python_bin="$(command -v python3 || command -v python || true)" - if [[ -z "$python_bin" ]]; then - log "❌ Python not found. Install Python 3.9+ and re-run." - exit 1 - fi - - install_with_pip "$python_bin" -} - -main "$@" diff --git a/install_deps.sh b/install_deps.sh deleted file mode 100755 index 99c481a..0000000 --- a/install_deps.sh +++ /dev/null @@ -1,403 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail -IFS=$'\n\t' - -# Colors for better output -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -CYAN='\033[0;36m' -NC='\033[0m' # No Color - -# Logging functions (all to stderr) -log_info() { echo -e "${BLUE}[INFO]${NC} $1" >&2; } -log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1" >&2; } -log_warning() { echo -e "${YELLOW}[WARNING]${NC} $1" >&2; } -log_error() { echo -e "${RED}[ERROR]${NC} $1" >&2; } - -# Print banner -print_banner() { - echo -e "${CYAN}" - echo "╔══════════════════════════════════════════════════════════════╗" - echo "║ DAT Installer v3.0.0 ║" - echo "║ Enterprise Security Scanning Tool ║" - echo "╚══════════════════════════════════════════════════════════════╝" - echo -e "${NC}" -} - -# Check if running as root -check_root() { - if [[ $EUID -eq 0 ]]; then - log_warning "Running as root user - this is not recommended" - read -p "Continue anyway? [y/N] " -n 1 -r - echo - if [[ ! $REPLY =~ ^[Yy]$ ]]; then - exit 1 - fi - fi -} - -# Robust Python detection -detect_python() { - log_info "Detecting Python..." - - # Try different Python commands in order of preference - for py_cmd in python3.12 python3.11 python3.10 python3.9 python3 python; do - if command -v "$py_cmd" >/dev/null 2>&1; then - # Check Python version - version=$("$py_cmd" -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')" 2>/dev/null || echo "0.0") - - # Extract major and minor version - major=$(echo "$version" | cut -d. -f1) - minor=$(echo "$version" | cut -d. -f2) - - if [[ $major -eq 3 ]] && [[ $minor -ge 9 ]]; then - log_success "Found Python $version using $py_cmd" - printf '%s\n' "$py_cmd" - return 0 - else - log_warning "Python $version found but 3.9+ required" - fi - fi - done - - log_error "No suitable Python 3.9+ installation found" - log_info "Please install Python 3.9 or newer from: https://python.org/downloads/" - return 1 -} - -# Install DAT package -install_dat_package() { - local python_cmd="$1" - local install_mode="$2" - - log_info "Installing DAT package ($install_mode)..." - - case "$install_mode" in - "dev") - # Development mode - install from current directory - if [[ -f "pyproject.toml" ]]; then - log_info "Installing in development mode from pyproject.toml" - if "$python_cmd" -m pip install -e .[dev]; then - log_success "DAT installed in development mode" - else - log_error "Failed to install DAT in development mode" - return 1 - fi - else - log_error "pyproject.toml not found - cannot install in development mode" - return 1 - fi - ;; - "prod") - # Production mode - install from current directory - if [[ -f "pyproject.toml" ]]; then - log_info "Installing in production mode from pyproject.toml" - if "$python_cmd" -m pip install .; then - log_success "DAT installed in production mode" - else - log_error "Failed to install DAT in production mode" - return 1 - fi - elif [[ -f "requirements.txt" ]]; then - log_info "Installing from requirements.txt" - if "$python_cmd" -m pip install -r requirements.txt; then - log_success "DAT installed from requirements.txt" - else - log_error "Failed to install from requirements.txt" - return 1 - fi - else - log_error "No installation files found (pyproject.toml or requirements.txt)" - return 1 - fi - ;; - "pypi") - # Install from PyPI - log_info "Installing from PyPI..." - if "$python_cmd" -m pip install outervoid-dat; then - log_success "DAT installed from PyPI" - else - log_error "Failed to install from PyPI" - log_info "Trying with --user flag..." - if "$python_cmd" -m pip install --user outervoid-dat; then - log_success "DAT installed from PyPI with --user flag" - else - return 1 - fi - fi - ;; - esac - - return 0 -} - -# Install system dependencies -install_system_deps() { - local os=$(uname -s | tr '[:upper:]' '[:lower:]') - - log_info "Detected platform: $os" - - case "$os" in - linux*) - install_linux_deps - ;; - darwin*) - install_macos_deps - ;; - mingw*|cygwin*|msys*) - install_windows_deps - ;; - *) - log_warning "Unsupported platform: $os" - show_manual_instructions - ;; - esac -} - -# Linux dependency installation -install_linux_deps() { - log_info "Installing Linux dependencies..." - - # Detect distribution - if [[ -f /etc/os-release ]]; then - source /etc/os-release - distro=$ID - else - log_warning "Cannot detect Linux distribution" - return 1 - fi - - case "$distro" in - ubuntu|debian|linuxmint) - log_info "Detected Ubuntu/Debian-based system" - # Install libmagic for file type detection - if command -v sudo >/dev/null 2>&1; then - sudo apt-get update - if sudo apt-get install -y libmagic1 file; then - log_success "Linux dependencies installed" - else - log_warning "Failed to install some dependencies, continuing..." - fi - else - log_warning "sudo not available, please install manually: apt-get install libmagic1 file" - fi - ;; - fedora|rhel|centos) - log_info "Detected Fedora/RHEL-based system" - if command -v sudo >/dev/null 2>&1; then - sudo dnf install -y file-libs file - else - log_warning "sudo not available, please install manually: dnf install file-libs file" - fi - ;; - arch|manjaro) - log_info "Detected Arch Linux-based system" - if command -v sudo >/dev/null 2>&1; then - sudo pacman -S --noconfirm file - else - log_warning "sudo not available, please install manually: pacman -S file" - fi - ;; - *) - log_warning "Unsupported Linux distribution: $distro" - ;; - esac -} - -# macOS dependency installation -install_macos_deps() { - log_info "Installing macOS dependencies..." - - if command -v brew >/dev/null 2>&1; then - if brew install libmagic; then - log_success "macOS dependencies installed" - else - log_warning "Failed to install libmagic with Homebrew" - fi - else - log_warning "Homebrew not installed. Install from: https://brew.sh" - fi -} - -# Windows dependency installation -install_windows_deps() { - log_info "Windows detected - using python-magic-bin for file detection" - # python-magic-bin will be installed via pip - return 0 -} - -# Show manual instructions -show_manual_instructions() { - log_info "Manual installation instructions:" - log_info "Ubuntu/Debian: sudo apt-get install libmagic1 file" - log_info "Fedora/RHEL: sudo dnf install file-libs file" - log_info "Arch Linux: sudo pacman -S file" - log_info "macOS: brew install libmagic" -} - -# Verify installation -verify_installation() { - local python_cmd="$1" - - log_info "Verifying installation..." - - "$python_cmd" -c " -import sys -print('Verifying DAT installation...') - -# Test core imports -tests = [ - ('rich', 'from rich.console import Console'), - ('colorama', 'import colorama'), - ('typer', 'import typer'), - ('yaml', 'import yaml'), - ('packaging', 'import packaging.version') -] - -all_ok = True -for name, test_code in tests: - try: - exec(test_code) - print(f'✅ {name}: OK') - except ImportError as e: - print(f'❌ {name}: FAILED - {e}') - all_ok = False - -# Test DAT package -try: - import dat - print(f'✅ dat: OK (version {dat.__version__})') -except ImportError as e: - print(f'❌ dat: FAILED - {e}') - all_ok = False - -# Test DAT CLI -try: - from dat.cli import main - print('✅ dat.cli: OK') -except ImportError as e: - print(f'❌ dat.cli: FAILED - {e}') - all_ok = False - -if all_ok: - print('All core dependencies verified!') -else: - print('Some dependencies failed - DAT may have limited functionality') - sys.exit(1) -" -} - -# Setup DAT executable -setup_dat() { - local python_cmd="$1" - - log_info "Testing DAT installation..." - - # Test DAT functionality - if command -v dat >/dev/null 2>&1; then - log_success "DAT command available in PATH" - if dat --version >/dev/null 2>&1 || dat --help >/dev/null 2>&1; then - log_success "DAT is functional" - return 0 - fi - fi - - # Fallback: try python module - log_info "Trying Python module execution..." - if "$python_cmd" -m dat --version >/dev/null 2>&1 || "$python_cmd" -m dat --help >/dev/null 2>&1; then - log_success "DAT works via Python module" - log_info "You can run DAT using: $python_cmd -m dat" - return 0 - fi - - log_warning "DAT command not found in PATH" - log_info "You may need to add Python user base bin to your PATH:" - log_info " export PATH=\"\$($python_cmd -m site --user-base)/bin:\$PATH\"" - return 1 -} - -# Ask for installation mode -choose_installation_mode() { - echo - log_info "Choose installation mode:" - echo "1) Development - Install from current source with dev dependencies" - echo "2) Production - Install from current source (production only)" - echo "3) PyPI - Install latest release from PyPI" - echo - read -p "Enter choice [1-3] (default: 1): " choice - - case "${choice:-1}" in - 1|"") echo "dev" ;; - 2) echo "prod" ;; - 3) echo "pypi" ;; - *) - log_error "Invalid choice" - choose_installation_mode - ;; - esac -} - -# Main installation function -main() { - print_banner - check_root - - log_info "Starting DAT installation..." - - # Detect Python - PYTHON_CMD=$(detect_python) || exit 1 - - # Choose installation mode - INSTALL_MODE=$(choose_installation_mode) - - # Upgrade pip - log_info "Upgrading pip..." - if ! "$PYTHON_CMD" -m pip install --upgrade pip; then - log_warning "pip upgrade failed, continuing anyway..." - fi - - # Install system dependencies (non-critical) - log_info "Installing system dependencies..." - if install_system_deps; then - log_success "System dependencies handled" - else - log_warning "System dependencies may need manual installation" - fi - - # Install DAT package - log_info "Installing DAT package..." - install_dat_package "$PYTHON_CMD" "$INSTALL_MODE" || exit 1 - - # Verify installation - verify_installation "$PYTHON_CMD" || exit 1 - - # Setup DAT - log_info "Setting up DAT..." - setup_dat "$PYTHON_CMD" - - # Success message - echo - log_success "🎉 DAT installation completed successfully!" - echo - log_info "🚀 Quick Start:" - - if command -v dat >/dev/null 2>&1; then - log_info " dat --version # Check version" - log_info " dat --help # Show help" - log_info " dat scan # Scan current directory" - else - log_info " $PYTHON_CMD -m dat --version # Check version" - log_info " $PYTHON_CMD -m dat --help # Show help" - log_info " $PYTHON_CMD -m dat scan # Scan current directory" - fi - - echo - log_info "📚 Documentation: https://github.com/Outer-Void/dat" - log_info "🐛 Issues: https://github.com/Outer-Void/dat/issues" - echo -} - -# Run main function -main "$@" diff --git a/scripts/install.sh b/scripts/install.sh new file mode 100755 index 0000000..b8e552d --- /dev/null +++ b/scripts/install.sh @@ -0,0 +1,480 @@ +#!/usr/bin/env bash +set -euo pipefail +IFS=$'\n\t' + +PKG_NAME="outervoid-dat" +EXTRAS="${DAT_EXTRAS:-}" + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" + +MODE="" +USE_VENV="false" +USE_PIP_USER="false" +PREFIX="" +DEBUG="false" +DRY_RUN="false" + +if [[ -n "${EXTRAS}" ]]; then + PKG_SPEC="${PKG_NAME}[${EXTRAS}]" +else + PKG_SPEC="${PKG_NAME}" +fi + +log() { + printf '%s\n' "$*" >&2 +} + +log_info() { + log "[INFO] $*" +} + +log_warn() { + log "[WARN] $*" +} + +log_error() { + log "[ERROR] $*" +} + +on_error() { + local exit_code=$? + local line_no=$1 + local cmd=$2 + log_error "Command failed (exit ${exit_code}) at line ${line_no}: ${cmd}" + exit "$exit_code" +} + +trap 'on_error ${LINENO} "$BASH_COMMAND"' ERR + +is_tty() { + [[ -t 0 && -t 1 ]] +} + +is_termux() { + [[ -n "${TERMUX_VERSION:-}" || -d "/data/data/com.termux/files/usr" ]] +} + +is_proot() { + [[ -n "${PROOT_DISTRIBUTION_ID:-}" ]] || ( [[ -r /proc/1/cmdline ]] && grep -qa "proot" /proc/1/cmdline ) +} + +is_wsl() { + [[ -r /proc/version ]] && grep -qiE "microsoft|wsl" /proc/version +} + +is_macos() { + [[ "$(uname -s)" == "Darwin" ]] +} + +is_windows() { + case "$(uname -s)" in + MINGW*|MSYS*|CYGWIN*) return 0 ;; + esac + [[ "${OS:-}" == "Windows_NT" ]] +} + +is_linux() { + [[ "$(uname -s)" == "Linux" ]] +} + +detect_distro() { + if [[ -r /etc/os-release ]]; then + # shellcheck disable=SC1091 + . /etc/os-release + printf '%s' "${ID:-}" + else + printf '' + fi +} + +ensure_python() { + local python_bin + python_bin="$(command -v python3 || command -v python || true)" + if [[ -z "$python_bin" ]]; then + log_error "Python 3.9+ not found. Install Python and re-run." + exit 1 + fi + local version + version="$($python_bin -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")' 2>/dev/null || echo '0.0')" + local major minor + major="${version%%.*}" + minor="${version##*.}" + if [[ "$major" -ne 3 || "$minor" -lt 9 ]]; then + log_error "Python 3.9+ required; found ${version}." + exit 1 + fi + printf '%s' "$python_bin" +} + +maybe_prompt() { + local prompt="$1" + local default_no="${2:-true}" + if ! is_tty; then + return 1 + fi + local reply + if [[ "$default_no" == "true" ]]; then + read -r -p "${prompt} [y/N] " reply + [[ "$reply" =~ ^[Yy]$ ]] + else + read -r -p "${prompt} [Y/n] " reply + [[ -z "$reply" || "$reply" =~ ^[Yy]$ ]] + fi +} + +install_system_deps() { + log_info "Installing system dependencies (best effort)..." + + if is_windows; then + log_warn "Windows detected. System dependency installation is not supported in this shell." + log_warn "Use Python + pipx/uv, or PowerShell:" + log_warn " py -m pip install --user pipx" + log_warn " py -m pipx ensurepath" + log_warn " py -m pipx install ${PKG_SPEC}" + return 0 + fi + + if is_macos; then + if command -v brew >/dev/null 2>&1; then + log_info "Using Homebrew to install libmagic (optional)." + if ! brew install libmagic; then + log_warn "Homebrew failed to install libmagic." + fi + else + log_warn "Homebrew not found. Install from https://brew.sh then run: brew install libmagic" + fi + log_warn "Ensure python3 is installed (brew install python@3.11 or python.org installer)." + return 0 + fi + + if is_termux && ! is_proot; then + log_info "Termux detected. Installing dependencies via pkg." + pkg update -y || log_warn "pkg update failed" + pkg install -y python3 git curl wget ca-certificates file libmagic jq || log_warn "pkg install failed" + return 0 + fi + + if is_linux; then + local distro + distro="$(detect_distro)" + local sudo_cmd="" + if [[ $EUID -ne 0 ]]; then + if command -v sudo >/dev/null 2>&1; then + sudo_cmd="sudo" + else + log_warn "sudo not available; install dependencies manually." + return 0 + fi + fi + + case "$distro" in + debian|ubuntu|linuxmint) + log_info "Using apt-get for Debian/Ubuntu." + ${sudo_cmd} apt-get update || log_warn "apt-get update failed" + ${sudo_cmd} apt-get install -y python3 python3-venv python3-pip python3-dev build-essential pkg-config libssl-dev libffi-dev file libmagic1 git curl wget ca-certificates jq || log_warn "apt-get install failed" + ;; + fedora|rhel|centos|rocky|almalinux) + log_info "Using dnf for Fedora/RHEL." + ${sudo_cmd} dnf install -y python3 python3-pip python3-devel gcc gcc-c++ make pkgconf-pkg-config openssl-devel libffi-devel file file-libs git curl wget ca-certificates jq || log_warn "dnf install failed" + ;; + arch|manjaro) + log_info "Using pacman for Arch." + ${sudo_cmd} pacman -S --noconfirm python python-pip base-devel pkgconf openssl libffi file git curl wget ca-certificates jq || log_warn "pacman install failed" + ;; + *) + log_warn "Unsupported Linux distro (${distro}). Install dependencies manually." + ;; + esac + return 0 + fi + + log_warn "Unsupported platform for dependency installation." +} + +warn_termux_proot_prefix() { + if ! is_proot; then + return 0 + fi + if ! command -v dat >/dev/null 2>&1; then + return 0 + fi + local dat_path + dat_path="$(command -v dat || true)" + if [[ "$dat_path" == /data/data/com.termux/files/usr/* ]]; then + log_warn "Detected DAT from Termux prefix while running in proot-distro." + log_warn "This can cause prefix hijack. Consider uninstalling from Termux:" + log_warn " pkg uninstall python3 && pkg uninstall pipx" + log_warn " pipx uninstall ${PKG_NAME}" + fi +} + +ensure_on_path() { + if command -v dat >/dev/null 2>&1; then + return 0 + fi + log_warn "'dat' not found on PATH. Ensure ~/.local/bin or pipx bin dir is in PATH." +} + +pipx_env() { + if [[ -n "$PREFIX" ]]; then + export PIPX_HOME="${PREFIX}/pipx" + export PIPX_BIN_DIR="${PREFIX}/bin" + fi +} + +install_with_pipx() { + pipx_env + local spec="$1" + local editable="$2" + log_info "Installing via pipx: ${spec}" + if [[ "$editable" == "true" ]]; then + pipx install --editable "$spec" + else + pipx install "$spec" + fi + if command -v pipx >/dev/null 2>&1; then + pipx ensurepath >/dev/null 2>&1 || true + fi + ensure_on_path +} + +install_with_uv() { + local spec="$1" + local editable="$2" + if [[ -n "$PREFIX" ]]; then + log_warn "--prefix is not supported for uv tool installs. Ignoring prefix." + fi + log_info "Installing via uv tool install: ${spec}" + if [[ "$editable" == "true" ]]; then + uv tool install --editable "$spec" + else + uv tool install "$spec" + fi + ensure_on_path +} + +install_with_venv() { + local python_bin="$1" + local spec="$2" + local editable="$3" + local venv_path="${PREFIX:-${REPO_ROOT}/.venv}" + log_info "Installing into venv at ${venv_path}" + "$python_bin" -m venv "$venv_path" + local venv_python="${venv_path}/bin/python" + if [[ ! -x "$venv_python" ]]; then + log_error "Venv python not found at ${venv_python}" + exit 1 + fi + "$venv_python" -m pip install --upgrade pip + if [[ "$editable" == "true" ]]; then + "$venv_python" -m pip install --editable "$spec" + else + "$venv_python" -m pip install "$spec" + fi + log_info "Activate with: source ${venv_path}/bin/activate" + ensure_on_path +} + +install_with_pip_user() { + local python_bin="$1" + local spec="$2" + local editable="$3" + if [[ -n "$PREFIX" ]]; then + log_warn "--prefix is not supported for --pip-user installs. Ignoring prefix." + fi + log_info "Installing via pip --user: ${spec}" + if [[ "$editable" == "true" ]]; then + "$python_bin" -m pip install --user --editable "$spec" + else + "$python_bin" -m pip install --user "$spec" + fi + ensure_on_path +} + +print_env_summary() { + local distro + distro="$(detect_distro)" + log_info "Detected environment summary:" + log_info " OS: $(uname -s)" + log_info " Termux: $(is_termux && echo yes || echo no)" + log_info " proot-distro: $(is_proot && echo yes || echo no)" + log_info " WSL: $(is_wsl && echo yes || echo no)" + log_info " macOS: $(is_macos && echo yes || echo no)" + log_info " Windows shell: $(is_windows && echo yes || echo no)" + log_info " Distro: ${distro:-unknown}" +} + +usage() { + cat <<'USAGE' +Usage: scripts/install.sh [options] + +Options: + --mode Install mode (default: dev inside repo, else pypi). + --venv Install into local .venv (fallback when pipx/uv unavailable). + --pip-user Install via pip --user (fallback when pipx/uv unavailable). + --prefix Advanced: override prefix (pipx home/bin or venv path). + --dry-run Print detected environment and planned actions, then exit. + --debug Enable bash debug output. + --help Show help. +USAGE +} + +parse_args() { + while [[ $# -gt 0 ]]; do + case "$1" in + --mode) + MODE="${2:-}" + shift 2 + ;; + --venv) + USE_VENV="true" + shift + ;; + --pip-user) + USE_PIP_USER="true" + shift + ;; + --prefix) + PREFIX="${2:-}" + shift 2 + ;; + --debug) + DEBUG="true" + shift + ;; + --dry-run) + DRY_RUN="true" + shift + ;; + --help|-h) + usage + exit 0 + ;; + *) + log_error "Unknown argument: $1" + usage + exit 1 + ;; + esac + done +} + +resolve_mode() { + if [[ -n "$MODE" ]]; then + return 0 + fi + if [[ -f "${REPO_ROOT}/pyproject.toml" ]]; then + MODE="dev" + else + MODE="pypi" + fi +} + +build_spec() { + local mode="$1" + local spec + case "$mode" in + pypi) + spec="$PKG_SPEC" + ;; + prod|dev) + if [[ ! -f "${REPO_ROOT}/pyproject.toml" ]]; then + log_error "Local repo install requested but pyproject.toml not found at ${REPO_ROOT}." + exit 1 + fi + if [[ -n "$EXTRAS" ]]; then + spec="${REPO_ROOT}[${EXTRAS}]" + else + spec="${REPO_ROOT}" + fi + ;; + *) + log_error "Unsupported mode: ${mode}" + exit 1 + ;; + esac + printf '%s' "$spec" +} + +main() { + parse_args "$@" + + if [[ "$DEBUG" == "true" ]]; then + set -x + fi + + resolve_mode + + print_env_summary + warn_termux_proot_prefix + + local spec + spec="$(build_spec "$MODE")" + + local editable="false" + if [[ "$MODE" == "dev" ]]; then + editable="true" + fi + + if [[ "$USE_VENV" == "true" && "$USE_PIP_USER" == "true" ]]; then + log_error "--venv and --pip-user are mutually exclusive." + exit 1 + fi + + if [[ "$DRY_RUN" == "true" ]]; then + local method + if [[ "$USE_VENV" == "true" ]]; then + method="venv" + elif [[ "$USE_PIP_USER" == "true" ]]; then + method="pip --user" + elif command -v pipx >/dev/null 2>&1; then + method="pipx" + elif command -v uv >/dev/null 2>&1; then + method="uv tool install" + else + method="none (pipx/uv not found)" + fi + log_info "Dry run: mode=${MODE}, spec=${spec}, editable=${editable}, method=${method}" + return 0 + fi + + install_system_deps + + if [[ "$USE_VENV" == "true" ]]; then + local python_bin + python_bin="$(ensure_python)" + install_with_venv "$python_bin" "$spec" "$editable" + return 0 + fi + + if [[ "$USE_PIP_USER" == "true" ]]; then + local python_bin + python_bin="$(ensure_python)" + install_with_pip_user "$python_bin" "$spec" "$editable" + return 0 + fi + + if command -v pipx >/dev/null 2>&1; then + install_with_pipx "$spec" "$editable" + return 0 + fi + + if command -v uv >/dev/null 2>&1; then + install_with_uv "$spec" "$editable" + return 0 + fi + + log_warn "Neither pipx nor uv is available." + if maybe_prompt "Install using a local venv instead?"; then + local python_bin + python_bin="$(ensure_python)" + install_with_venv "$python_bin" "$spec" "$editable" + return 0 + fi + + log_error "No supported install method found. Install pipx or uv, or re-run with --venv or --pip-user." + exit 1 +} + +main "$@"