#!/usr/bin/env bash # Bootstrap script for POSIX (Linux/macOS) to create a Python venv and install the project. # Usage: scripts/bootstrap.sh [--editable] [--venv ] [--python ] [--desktop] [--no-install] set -euo pipefail VENV_PATH=".venv" EDITABLE=false DESKTOP=false PYTHON_CMD="" NOINSTALL=false FORCE=false QUIET=false # Playwright options PLAYWRIGHT_BROWSERS="chromium" # comma-separated (chromium,firefox,webkit) or 'all' NO_PLAYWRIGHT=false usage() { cat < Venv path (default: .venv) --python Python executable to use (e.g. python3) -d, --desktop Create a desktop launcher (~/.local/share/applications and ~/Desktop) -n, --no-install Skip pip install --no-playwright Skip installing Playwright browsers (default: install chromium) --playwright-browsers Comma-separated list of browsers to install (default: chromium) -q, --quiet Quiet / non-interactive mode; abort on errors instead of prompting -f, --force Overwrite existing venv without prompting -h, --help Show this help EOF } while [[ $# -gt 0 ]]; do case "$1" in -e|--editable) EDITABLE=true; shift;; -p|--venv) VENV_PATH="$2"; shift 2;; --python) PYTHON_CMD="$2"; shift 2;; -d|--desktop) DESKTOP=true; shift;; -n|--no-install) NOINSTALL=true; shift;; -f|--force) FORCE=true; shift;; -q|--quiet) QUIET=true; shift;; -h|--help) usage; exit 0;; --no-playwright) NO_PLAYWRIGHT=true; shift;; --playwright-browsers) PLAYWRIGHT_BROWSERS="$2"; shift 2;; *) echo "Unknown option: $1"; usage; exit 1;; esac done if [[ -n "$PYTHON_CMD" ]]; then PY="$PYTHON_CMD" elif command -v python3 >/dev/null 2>&1; then PY=python3 elif command -v python >/dev/null 2>&1; then PY=python else echo "ERROR: No python executable found; install Python or pass --python " >&2 exit 2 fi echo "Using Python: $PY" if [[ -d "$VENV_PATH" ]]; then # Detect whether the existing venv has a working python executable VENV_PY="" for cand in "$VENV_PATH/bin/python" "$VENV_PATH/bin/python3" "$VENV_PATH/Scripts/python.exe"; do if [[ -x "$cand" ]]; then VENV_PY="$cand" break fi done if [[ "$FORCE" == "true" ]]; then echo "Removing existing venv $VENV_PATH" rm -rf "$VENV_PATH" else if [[ -z "$VENV_PY" ]]; then if [[ "$QUIET" == "true" ]]; then echo "ERROR: Existing venv appears incomplete or broken (no python executable). Use --force to recreate." >&2 exit 4 fi read -p "$VENV_PATH exists but appears invalid (no python executable). Overwrite to recreate? (y/N) " REPLY if [[ "$REPLY" != "y" && "$REPLY" != "Y" ]]; then echo "Aborted."; exit 4 fi rm -rf "$VENV_PATH" else if [[ "$QUIET" == "true" ]]; then echo "Using existing venv at $VENV_PATH (quiet mode)" else read -p "$VENV_PATH already exists. Overwrite? (y/N) (default: use existing venv) " REPLY if [[ "$REPLY" == "y" || "$REPLY" == "Y" ]]; then echo "Removing existing venv $VENV_PATH" rm -rf "$VENV_PATH" else echo "Continuing using existing venv at $VENV_PATH" fi fi fi fi fi if [[ -d "$VENV_PATH" && -n "${VENV_PY:-}" && -x "${VENV_PY:-}" ]]; then echo "Using existing venv at $VENV_PATH" else echo "Creating venv at $VENV_PATH" $PY -m venv "$VENV_PATH" VENV_PY="$VENV_PATH/bin/python" fi if [[ ! -x "$VENV_PY" ]]; then echo "ERROR: venv python not found at $VENV_PY" >&2 exit 3 fi if [[ "$NOINSTALL" != "true" ]]; then echo "Upgrading pip, setuptools, wheel..." "$VENV_PY" -m pip install -U pip setuptools wheel if [[ "$EDITABLE" == "true" ]]; then echo "Installing project in editable mode..." "$VENV_PY" -m pip install -e . else echo "Installing project..." "$VENV_PY" -m pip install . fi # Verify the installed CLI module can be imported. This helps catch packaging # or installation problems early (e.g., missing modules or mispackaged project). echo "Verifying installed CLI import..." if "$VENV_PY" -c 'import importlib; importlib.import_module("medeia_macina.cli_entry")' >/dev/null 2>&1; then echo "OK: 'medeia_macina.cli_entry' is importable in the venv." else echo "WARNING: Could not import 'medeia_macina.cli_entry' from the venv." >&2 # Check if legacy top-level module is present; if so, inform the user to prefer the packaged entrypoint if "$VENV_PY" -c 'import importlib; importlib.import_module("medeia_entry")' >/dev/null 2>&1; then echo "Note: 'medeia_entry' top-level module is present. It's recommended to install the project so 'medeia_macina.cli_entry' is available." >&2 else echo "Action: Try running: $VENV_PY -m pip install -e . or inspect the venv site-packages to verify the installation." >&2 fi fi echo "Verifying environment for known issues (urllib3 compatibility)..." if ! "$VENV_PY" -c 'from SYS.env_check import check_urllib3_compat; ok,msg = check_urllib3_compat(); print(msg); import sys; sys.exit(0 if ok else 2)'; then echo "ERROR: Bootstrap detected a potentially broken 'urllib3' installation. See message above." >&2 echo "You can attempt to fix with:" >&2 echo " $VENV_PY -m pip uninstall urllib3-future -y" >&2 echo " $VENV_PY -m pip install --upgrade --force-reinstall urllib3" >&2 echo " $VENV_PY -m pip install niquests -U" >&2 exit 7 fi # Install Playwright browsers (default: chromium) unless explicitly disabled if [[ "$NO_PLAYWRIGHT" != "true" && "$NOINSTALL" != "true" ]]; then echo "Ensuring Playwright browsers are installed (browsers=$PLAYWRIGHT_BROWSERS)..." # Install package if missing in venv if ! "$VENV_PY" -c 'import importlib, sys; importlib.import_module("playwright")' >/dev/null 2>&1; then echo "'playwright' package not found in venv; installing via pip..." "$VENV_PY" -m pip install playwright fi # Compute install behavior: 'all' means install all engines, otherwise split comma list if [[ "$PLAYWRIGHT_BROWSERS" == "all" ]]; then echo "Installing all Playwright browsers..." "$VENV_PY" -m playwright install || echo "Warning: Playwright browser install failed" >&2 else IFS=',' read -ra PWB <<< "$PLAYWRIGHT_BROWSERS" for b in "${PWB[@]}"; do b_trimmed=$(echo "$b" | tr -d '[:space:]') if [[ -n "$b_trimmed" ]]; then echo "Installing Playwright browser: $b_trimmed" "$VENV_PY" -m playwright install "$b_trimmed" || echo "Warning: Playwright install for $b_trimmed failed" >&2 fi done fi fi else echo "Skipping install (--no-install)" fi # Install Deno (official installer) - installed automatically if command -v deno >/dev/null 2>&1; then echo "Deno already installed: $(deno --version | head -n 1)" else echo "Installing Deno via official installer (https://deno.land)..." if command -v curl >/dev/null 2>&1; then curl -fsSL https://deno.land/install.sh | sh elif command -v wget >/dev/null 2>&1; then wget -qO- https://deno.land/install.sh | sh else echo "ERROR: curl or wget is required to install Deno automatically; please install Deno manually." >&2 fi export DENO_INSTALL="${DENO_INSTALL:-$HOME/.deno}" export PATH="$DENO_INSTALL/bin:$PATH" if command -v deno >/dev/null 2>&1; then echo "Deno installed: $(deno --version | head -n 1)" else echo "Warning: Deno installer completed but 'deno' not found on PATH; add $HOME/.deno/bin to your PATH or restart your shell." >&2 fi fi if [[ "$DESKTOP" == "true" ]]; then echo "Creating desktop launcher..." EXEC_PATH="$VENV_PATH/bin/mm" if [[ ! -x "$EXEC_PATH" ]]; then # fallback to python -m EXEC_PATH="$VENV_PY -m medeia_macina.cli_entry" fi APPDIR="$HOME/.local/share/applications" mkdir -p "$APPDIR" DESKTOP_FILE="$APPDIR/medeia-macina.desktop" cat > "$DESKTOP_FILE" < "$USER_BIN/mm" </dev/null 2>&1; then echo "Global 'mm' launcher verified: $USER_BIN/mm runs correctly." else echo "Warning: Global 'mm' launcher failed to run in this shell. Ensure $USER_BIN is on your PATH and the venv is installed; try: $VENV/bin/python -m medeia_macina.cli_entry --help" >&2 fi # Ensure the user's bin is on PATH for future sessions by adding to ~/.profile if needed if ! echo ":$PATH:" | grep -q ":$USER_BIN:"; then PROFILE="$HOME/.profile" if [ ! -f "$PROFILE" ]; then if [ -f "$HOME/.bash_profile" ]; then PROFILE="$HOME/.bash_profile" elif [ -f "$HOME/.bashrc" ]; then PROFILE="$HOME/.bashrc" elif [ -f "$HOME/.zshrc" ]; then PROFILE="$HOME/.zshrc" else PROFILE="$HOME/.profile" fi fi if ! grep -q "ensure user local bin is on PATH" "$PROFILE" 2>/dev/null; then cat >> "$PROFILE" <