#!/usr/bin/env bash set -euo pipefail # replicate_venv.sh # Export and recreate a Python virtual environment across machines. # # Quick start: # 1) On source machine: # ./Scripts/replicate_venv.sh export --venv /path/to/.venv --out .venv-lock # 2) Copy .venv-lock and this script to target machine. # 3) On target machine: # ./Scripts/replicate_venv.sh recreate --venv .venv --from .venv-lock --python python3 # # The lock folder contains: # - requirements.txt pip freeze output (all Python libraries) # - python-version.txt full Python version from source venv # - platform.txt source OS/platform info # - bootstrap.txt recreation command and dependency notes usage() { cat <<'EOF' Usage: replicate_venv.sh export --venv [--out ] replicate_venv.sh recreate --venv [--from ] [--python ] Options: --venv Path to source/target virtual environment directory. --out Export destination directory (default: .venv-lock). --from Input lock directory (default: .venv-lock). --python Python command to create new venv (default: python3). -h, --help Show this help. Quick Start: Source machine: ./Scripts/replicate_venv.sh export --venv /path/to/.venv --out .venv-lock Target machine: ./Scripts/replicate_venv.sh recreate --venv .venv --from .venv-lock --python python3 Dependencies required on target machine: 1. Python 3 with venv support. 2. Internet access to install pip packages from requirements.txt (or access to your package mirror). 3. Build tools for packages that compile native extensions (when needed by your requirements). Notes: - This script recreates Python package dependencies; it does not install system packages for you. - It will warn if source and target Python versions differ. EOF } abort() { echo "Error: $*" >&2 exit 1 } require_cmd() { command -v "$1" >/dev/null 2>&1 || abort "Required command not found: $1" } ensure_python_has_venv() { local python_cmd="$1" "$python_cmd" -c 'import venv' >/dev/null 2>&1 || abort "Python does not include the venv module. Install Python with venv support and retry." } ensure_python_has_pip_bootstrap() { local python_cmd="$1" "$python_cmd" -m ensurepip --upgrade >/dev/null 2>&1 || true } find_venv_python() { local venv_path="$1" if [[ -x "$venv_path/bin/python" ]]; then echo "$venv_path/bin/python" elif [[ -x "$venv_path/Scripts/python.exe" ]]; then # Windows-style venv path support. echo "$venv_path/Scripts/python.exe" else abort "Could not find Python inside virtualenv at: $venv_path" fi } mode="${1:-}" [[ -n "$mode" ]] || { usage; exit 1; } shift || true case "$mode" in export) venv_path="" out_dir=".venv-lock" while [[ $# -gt 0 ]]; do case "$1" in --venv) venv_path="${2:-}" shift 2 ;; --out) out_dir="${2:-}" shift 2 ;; -h|--help) usage exit 0 ;; *) abort "Unknown option for export: $1" ;; esac done [[ -n "$venv_path" ]] || abort "Missing required --venv for export" [[ -d "$venv_path" ]] || abort "Venv path does not exist: $venv_path" py_bin="$(find_venv_python "$venv_path")" mkdir -p "$out_dir" "$py_bin" -m pip freeze > "$out_dir/requirements.txt" "$py_bin" -c 'import platform; print(platform.python_version())' > "$out_dir/python-version.txt" "$py_bin" -c 'import platform; print(platform.platform())' > "$out_dir/platform.txt" cat > "$out_dir/bootstrap.txt" <<'EOF' Recreate command: ./Scripts/replicate_venv.sh recreate --venv .venv --from .venv-lock --python python3 Dependencies needed on target machine: - Python 3 with venv module enabled - Network access (or internal package mirror) for pip installs - Build toolchain for any packages requiring native compilation If Python differs from source version, recreate may still work but package compatibility can vary. EOF echo "Export complete." echo "Lock dir: $out_dir" echo "Files: $out_dir/requirements.txt, $out_dir/python-version.txt, $out_dir/platform.txt, $out_dir/bootstrap.txt" ;; recreate) target_venv="" from_dir=".venv-lock" python_cmd="python3" while [[ $# -gt 0 ]]; do case "$1" in --venv) target_venv="${2:-}" shift 2 ;; --from) from_dir="${2:-}" shift 2 ;; --python) python_cmd="${2:-}" shift 2 ;; -h|--help) usage exit 0 ;; *) abort "Unknown option for recreate: $1" ;; esac done [[ -n "$target_venv" ]] || abort "Missing required --venv for recreate" [[ -f "$from_dir/requirements.txt" ]] || abort "Missing lock file: $from_dir/requirements.txt" require_cmd "$python_cmd" ensure_python_has_venv "$python_cmd" ensure_python_has_pip_bootstrap "$python_cmd" if [[ -d "$target_venv" ]]; then abort "Target venv already exists at $target_venv. Remove it first or use a different path." fi "$python_cmd" -m venv "$target_venv" py_bin="$(find_venv_python "$target_venv")" "$py_bin" -m pip --version >/dev/null 2>&1 || abort "pip is unavailable in the target venv. Ensure your Python installation supports ensurepip." "$py_bin" -m pip install --upgrade pip setuptools wheel "$py_bin" -m pip install -r "$from_dir/requirements.txt" if [[ -f "$from_dir/python-version.txt" ]]; then source_ver="$(cat "$from_dir/python-version.txt")" target_ver="$($py_bin -c 'import platform; print(platform.python_version())')" if [[ "$source_ver" != "$target_ver" ]]; then echo "Warning: Source Python version ($source_ver) differs from target ($target_ver)." fi fi echo "Recreate complete at: $target_venv" ;; -h|--help) usage ;; *) abort "Unknown mode: $mode" ;; esac