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