From 2231e945e3b6a1e370a44b7354ed44547d5b1a7e Mon Sep 17 00:00:00 2001
From: John Ajera <37360952+jajera@users.noreply.github.com>
Date: Thu, 11 Dec 2025 19:46:06 +0000
Subject: [PATCH] feat: create install python script
adds new script
---
README.md | 2 +
docs/install_python.md | 235 ++++++++++++++++++++++
scripts/install_python.sh | 399 ++++++++++++++++++++++++++++++++++++++
3 files changed, 636 insertions(+)
create mode 100644 docs/install_python.md
create mode 100755 scripts/install_python.sh
diff --git a/README.md b/README.md
index 391a3db..4a082a0 100644
--- a/README.md
+++ b/README.md
@@ -13,6 +13,7 @@ Command-line interface tools/scripts
- [install_kubectl.sh](docs/install_kubectl.md)
- [install_loki.sh](docs/install_loki.md)
- [install_promtail.sh](docs/install_promtail.md)
+- [install_python.sh](docs/install_python.md)
- [install_s3_mount.sh](docs/install_s3_mount.md)
- [install_ssm_agent.sh](docs/install_ssm_agent.md)
- [install_ssm_plugin.sh](docs/install_ssm_plugin.md)
@@ -41,6 +42,7 @@ Refer to the respective files for detailed usage instructions:
- **[install_kubectl.sh](docs/install_kubectl.md)**: Install or uninstall `kubectl` on a Linux system.
- **[install_loki.sh](docs/install_loki.md)**: Install or uninstall Grafana Loki log aggregation system.
- **[install_promtail.sh](docs/install_promtail.md)**: Install or uninstall Grafana Promtail log shipper with flexible configuration options.
+- **[install_python.sh](docs/install_python.md)**: Install or uninstall Python with version pinning support on Linux, macOS, and Windows.
- **[install_s3_mount.sh](docs/install_s3_mount.md)**: Install or uninstall AWS Mountpoint for Amazon S3 to mount S3 buckets as local filesystems.
- **[install_ssm_agent.sh](docs/install_ssm_agent.md)**: Install or uninstall the AWS Systems Manager (SSM) Agent on various Linux distributions, macOS, and Windows.
- **[install_ssm_plugin.sh](docs/install_ssm_plugin.md)**: Install or uninstall the AWS SSM Session Manager Plugin for remote EC2 access.
diff --git a/docs/install_python.md b/docs/install_python.md
new file mode 100644
index 0000000..aee0bee
--- /dev/null
+++ b/docs/install_python.md
@@ -0,0 +1,235 @@
+# install_python.sh
+
+This script installs or uninstalls Python on Linux, macOS, and Windows systems. It supports version pinning and automatically installs the latest version when no version is specified.
+
+## Usage
+
+```bash
+./install_python.sh [install|uninstall]
+```
+
+- **install** (default): Installs Python (latest version if not specified).
+- **uninstall**: Removes Python from the system.
+
+## Optional Environment Variables
+
+- **PYTHON_VERSION**: Pin specific version (e.g., `3.12` or `3.12.5`) or leave empty for latest
+
+## Example Usage
+
+To install the latest Python version:
+
+```bash
+./install_python.sh install
+```
+
+To install a specific major.minor version:
+
+```bash
+PYTHON_VERSION=3.12 ./install_python.sh install
+```
+
+To install a specific full version:
+
+```bash
+PYTHON_VERSION=3.11.5 ./install_python.sh install
+```
+
+To uninstall Python:
+
+```bash
+./install_python.sh uninstall
+```
+
+## Running Without Cloning
+
+```bash
+bash <(curl -s https://raw.githubusercontent.com/jdevto/cli-tools/main/scripts/install_python.sh) install
+```
+
+## Verification
+
+After installation, check the Python version:
+
+```bash
+python3 --version
+```
+
+Test Python functionality:
+
+```bash
+python3 --help
+```
+
+## Supported Operating Systems
+
+- **Linux** (Ubuntu/Debian, RHEL/CentOS/Fedora)
+- **macOS** (Intel and Apple Silicon)
+- **Windows** (limited support - provides instructions)
+
+## Installation Methods by OS
+
+### Linux (Ubuntu/Debian)
+
+- Uses system package manager (`apt`/`apt-get`)
+- For newer versions, automatically adds `deadsnakes` PPA (Ubuntu only)
+- **Note**: Deadsnakes PPA is Ubuntu-only; Debian systems will need to use pyenv or build from source
+- Installs `python3.x`, `python3.x-dev`, and `python3.x-venv`
+
+### Linux (RHEL/CentOS/Fedora)
+
+- Uses system package manager (`dnf`/`yum`)
+- Attempts EPEL repository for older RHEL/CentOS systems
+- Falls back to compilation instructions if version not available
+
+### macOS
+
+- Uses Homebrew to install `pyenv`
+- Installs Python via `pyenv` for better version management
+- Automatically detects shell type (zsh, bash, or fallback to `.profile`)
+- Configures shell profiles with pyenv integration (only if not already configured)
+
+### Windows
+
+- Provides manual installation instructions
+- Recommends using `pyenv-win` for version management
+
+## Features
+
+- Automatically detects platform and package manager
+- Intelligently handles sudo (works as root or with sudo)
+- Installs latest Python version when no version is specified
+- Supports version pinning (major.minor or full version)
+- Idempotent: skips installation if requested version is already installed
+- Automatically installs required dependencies (`curl`)
+- Handles multiple installation methods per OS
+- Verifies installation after completion
+- Validates Ubuntu-only requirements for deadsnakes PPA
+- Smart shell detection for pyenv configuration on macOS
+
+## Error Handling
+
+- If Python with the requested version is already installed, the script will skip reinstallation.
+- If an unsupported OS or architecture is detected, the script exits with an error message.
+- The script checks for required dependencies before attempting installation.
+- Missing dependencies are automatically installed.
+- Validates that sudo is available (or runs as root) before attempting package installation.
+- Checks if latest version detection succeeds before proceeding.
+- Validates Ubuntu-only requirement for deadsnakes PPA on Debian-based systems.
+
+## Version Detection
+
+The script normalizes version numbers:
+
+- `3.12.5` → `3.12` (major.minor)
+- `3.12` → `3.12` (unchanged)
+- Latest version is fetched from python.org
+
+## Prerequisites
+
+The script automatically installs required dependencies:
+
+- **curl** - for fetching latest version information
+
+### OS-Specific Prerequisites
+
+- **macOS**: Requires Homebrew (installed automatically if needed for pyenv)
+- **Linux**: Requires appropriate package manager (apt, dnf, yum)
+- **Windows**: Manual installation recommended
+
+## Idempotency
+
+The script is idempotent and will:
+
+- Check if Python is already installed
+- Compare installed version with requested version
+- Skip installation if versions match
+- Proceed with installation if version differs or Python is not installed
+
+## Troubleshooting
+
+### Common Issues
+
+1. **Python not found after installation**:
+ - Restart your shell or run `source ~/.bashrc` (or `source ~/.zshrc`)
+ - Verify PATH: `echo $PATH | grep python`
+ - Check installation: `which python3`
+
+2. **Installation fails on Ubuntu/Debian**:
+ - Ensure `software-properties-common` is installed
+ - Check if deadsnakes PPA was added: `ls /etc/apt/sources.list.d/ | grep deadsnakes`
+ - **Note**: Deadsnakes PPA is Ubuntu-only. On Debian, use pyenv or build from source
+
+3. **Installation fails on RHEL/CentOS**:
+ - Ensure EPEL repository is enabled: `sudo yum install epel-release`
+ - Some versions may require compilation from source
+
+4. **macOS installation issues**:
+ - Ensure Homebrew is installed: `brew --version`
+ - If pyenv fails, try: `brew install python@3.12` directly
+
+5. **Version not available**:
+ - System repositories may not have the latest Python versions
+ - Consider using `pyenv` for better version management
+ - Check available versions in your distribution's repositories
+
+### Logs and Debugging
+
+- **Check installed version**: `python3 --version`
+- **Check available versions (Ubuntu)**: `apt-cache search python3 | grep '^python3'`
+- **Check available versions (RHEL/Fedora)**: `dnf list available python3*`
+- **Check pyenv versions (macOS)**: `pyenv versions`
+
+### Manual Installation Methods
+
+If the script fails, you can install Python manually:
+
+**Ubuntu/Debian**:
+
+```bash
+sudo apt update
+sudo apt install software-properties-common
+sudo add-apt-repository ppa:deadsnakes/ppa
+sudo apt update
+sudo apt install python3.12 python3.12-dev python3.12-venv
+```
+
+**RHEL/CentOS/Fedora**:
+
+```bash
+sudo dnf install python3.12
+# Or for older systems:
+sudo yum install epel-release
+sudo yum install python312
+```
+
+**macOS**:
+
+```bash
+brew install pyenv
+pyenv install 3.12.5
+pyenv global 3.12.5
+```
+
+## Additional Resources
+
+- **Official Python Downloads**:
+- **pyenv (Linux/macOS)**:
+- **pyenv-win (Windows)**:
+- **deadsnakes PPA (Ubuntu)**:
+
+## Cleanup
+
+- Temporary installation files are automatically removed after execution.
+- The script uses `trap cleanup EXIT` to ensure cleanup even if interrupted.
+
+## Notes
+
+- System Python (usually `python3`) may be a system dependency and should not be removed
+- The script installs additional Python versions alongside system Python
+- On macOS, the script uses `pyenv` for better version management
+- Windows support is limited; manual installation is recommended
+- The script intelligently handles sudo - works when running as root or with sudo privileges
+- **Uninstall warning**: Removing Python packages can affect system tools on non-container systems
+- Deadsnakes PPA is Ubuntu-only; Debian users should use pyenv or build from source
+- Shell integration for pyenv is only added if not already present (prevents duplicates)
diff --git a/scripts/install_python.sh b/scripts/install_python.sh
new file mode 100755
index 0000000..d72ff06
--- /dev/null
+++ b/scripts/install_python.sh
@@ -0,0 +1,399 @@
+#!/bin/bash
+
+set -e
+
+PYTHON_VERSION="${PYTHON_VERSION:-}" # Optional: pin version (e.g., 3.12), otherwise uses latest
+TMP_DIR="/tmp/python-install"
+
+cleanup() {
+ rm -rf "$TMP_DIR"
+}
+trap cleanup EXIT
+
+# Decide how to run privileged commands
+SUDO="sudo"
+if [ "$(id -u)" -eq 0 ]; then
+ SUDO=""
+elif ! command -v sudo >/dev/null 2>&1; then
+ echo "sudo not found and you are not root; cannot install packages."
+ exit 1
+fi
+
+PACKAGE_MANAGER=""
+PLATFORM=""
+
+normalize_version() {
+ local version="$1"
+ # Convert 3.12.5 to 3.12, or keep 3.12 as is
+ echo "$version" | cut -d. -f1,2
+}
+
+get_installed_python_version() {
+ local python_cmd="${1:-python3}"
+ if command -v "$python_cmd" >/dev/null 2>&1; then
+ # python3 --version -> "Python 3.12.3", extract 3.12
+ "$python_cmd" --version 2>&1 | awk '{print $2}' | cut -d. -f1,2 | head -1
+ else
+ echo "none"
+ fi
+}
+
+get_latest_python_version() {
+ # Get latest stable Python version (major.minor) from python.org
+ curl -s "https://www.python.org/ftp/python/" \
+ | grep -E 'href="[0-9]+\.[0-9]+\.[0-9]+/' \
+ | sed -E 's/.*href="([0-9]+\.[0-9]+\.[0-9]+)\/".*/\1/' \
+ | sort -V \
+ | tail -1 \
+ | cut -d. -f1,2
+}
+
+detect_package_manager() {
+ if command -v dnf >/dev/null 2>&1; then
+ PACKAGE_MANAGER="dnf"
+ elif command -v yum >/dev/null 2>&1; then
+ PACKAGE_MANAGER="yum"
+ elif command -v apt >/dev/null 2>&1 || command -v apt-get >/dev/null 2>&1; then
+ if command -v apt >/dev/null 2>&1; then
+ PACKAGE_MANAGER="apt"
+ else
+ PACKAGE_MANAGER="apt-get"
+ fi
+ elif command -v brew >/dev/null 2>&1; then
+ PACKAGE_MANAGER="brew"
+ else
+ echo "Unsupported package manager. Exiting."
+ exit 1
+ fi
+}
+
+detect_platform() {
+ if [[ "$OSTYPE" == "linux-gnu"* ]]; then
+ PLATFORM="linux"
+ elif [[ "$OSTYPE" == "darwin"* ]]; then
+ PLATFORM="darwin"
+ elif [[ "$OSTYPE" == "msys" ]] || [[ "$OSTYPE" == "win32" ]]; then
+ PLATFORM="windows"
+ else
+ echo "Unsupported platform: $OSTYPE"
+ exit 1
+ fi
+}
+
+install_dependencies() {
+ detect_package_manager
+ echo "Detected package manager: $PACKAGE_MANAGER"
+
+ local missing_packages=()
+
+ if ! command -v curl >/dev/null 2>&1; then
+ missing_packages+=("curl")
+ fi
+
+ if [ ${#missing_packages[@]} -eq 0 ]; then
+ echo "All dependencies are already installed."
+ return 0
+ fi
+
+ echo "Installing missing packages: ${missing_packages[*]}"
+
+ case "$PACKAGE_MANAGER" in
+ apt|apt-get)
+ $SUDO $PACKAGE_MANAGER update
+ $SUDO $PACKAGE_MANAGER install -y "${missing_packages[@]}"
+ ;;
+ dnf)
+ $SUDO dnf install -y "${missing_packages[@]}"
+ ;;
+ yum)
+ $SUDO yum install -y "${missing_packages[@]}"
+ ;;
+ brew)
+ brew install "${missing_packages[@]}"
+ ;;
+ *)
+ echo "Unsupported package manager. Exiting."
+ exit 1
+ ;;
+ esac
+}
+
+install_python_linux_apt() {
+ local version="$1"
+ local major_minor
+ major_minor=$(normalize_version "$version")
+
+ echo "Installing Python ${version} on Debian/Ubuntu..."
+
+ local system_version
+ system_version=$(get_installed_python_version "python3")
+ if [ "$system_version" != "none" ]; then
+ local system_major_minor
+ system_major_minor=$(normalize_version "$system_version")
+ # If system Python is same or newer, use system repos
+ if [ "$(printf '%s\n' "$system_major_minor" "$major_minor" | sort -V | head -1)" = "$major_minor" ]; then
+ echo "Installing Python ${major_minor} from system repositories..."
+ $SUDO $PACKAGE_MANAGER update
+ $SUDO $PACKAGE_MANAGER install -y "python${major_minor}" "python${major_minor}-dev" "python${major_minor}-venv"
+ return 0
+ fi
+ fi
+
+ # For newer versions, use deadsnakes on Ubuntu only
+ if [ -f /etc/os-release ]; then
+ . /etc/os-release
+ if [[ "$ID" != "ubuntu" && "$ID_LIKE" != *"ubuntu"* ]]; then
+ echo "Deadsnakes PPA is Ubuntu only; cannot install Python ${major_minor} this way."
+ echo "Consider using pyenv or building from source."
+ exit 1
+ fi
+ fi
+
+ echo "Adding deadsnakes PPA for Python ${major_minor}..."
+ $SUDO $PACKAGE_MANAGER update
+ $SUDO $PACKAGE_MANAGER install -y software-properties-common
+ $SUDO add-apt-repository -y ppa:deadsnakes/ppa
+ $SUDO $PACKAGE_MANAGER update
+ $SUDO $PACKAGE_MANAGER install -y "python${major_minor}" "python${major_minor}-dev" "python${major_minor}-venv"
+}
+
+install_python_linux_dnf() {
+ local version="$1"
+ local major_minor
+ major_minor=$(normalize_version "$version")
+
+ echo "Installing Python ${version} on RHEL/CentOS/Fedora..."
+
+ # Try system repositories first
+ if $SUDO $PACKAGE_MANAGER install -y "python${major_minor}" 2>/dev/null; then
+ echo "Python ${major_minor} installed from system repositories."
+ return 0
+ fi
+
+ # For older RHEL/CentOS, try EPEL if using yum
+ if [ "$PACKAGE_MANAGER" = "yum" ]; then
+ echo "Installing EPEL repository..."
+ $SUDO yum install -y epel-release
+ if $SUDO yum install -y "python${major_minor}" 2>/dev/null; then
+ echo "Python ${major_minor} installed from EPEL."
+ return 0
+ fi
+ fi
+
+ echo "Warning: Python ${major_minor} not available in repositories."
+ echo "Consider using pyenv for version management: https://github.com/pyenv/pyenv"
+ exit 1
+}
+
+install_python_linux() {
+ detect_package_manager
+
+ if [ "$PACKAGE_MANAGER" = "apt" ] || [ "$PACKAGE_MANAGER" = "apt-get" ]; then
+ install_python_linux_apt "$1"
+ elif [ "$PACKAGE_MANAGER" = "dnf" ] || [ "$PACKAGE_MANAGER" = "yum" ]; then
+ install_python_linux_dnf "$1"
+ else
+ echo "Unsupported package manager for Linux: $PACKAGE_MANAGER"
+ exit 1
+ fi
+}
+
+configure_pyenv_shell_integration() {
+ local shell_rc
+
+ if [ -n "$ZSH_VERSION" ]; then
+ shell_rc="${HOME}/.zshrc"
+ elif [ -n "$BASH_VERSION" ]; then
+ shell_rc="${HOME}/.bash_profile"
+ else
+ shell_rc="${HOME}/.profile"
+ fi
+
+ if ! grep -q 'PYENV_ROOT="$HOME/.pyenv"' "$shell_rc" 2>/dev/null; then
+ {
+ echo 'export PYENV_ROOT="$HOME/.pyenv"'
+ echo 'command -v pyenv >/dev/null || export PATH="$PYENV_ROOT/bin:$PATH"'
+ echo 'eval "$(pyenv init -)"'
+ } >> "$shell_rc"
+ fi
+}
+
+install_python_darwin() {
+ local version="$1"
+ local major_minor
+ major_minor=$(normalize_version "$version")
+
+ echo "Installing Python ${version} on macOS..."
+
+ if ! command -v brew >/dev/null 2>&1; then
+ echo "Error: Homebrew is required for Python installation on macOS."
+ echo "Install Homebrew: https://brew.sh"
+ exit 1
+ fi
+
+ # Use pyenv via Homebrew for better version management
+ if ! command -v pyenv >/dev/null 2>&1; then
+ echo "Installing pyenv via Homebrew..."
+ brew install pyenv
+ fi
+
+ configure_pyenv_shell_integration
+
+ export PYENV_ROOT="$HOME/.pyenv"
+ export PATH="$PYENV_ROOT/bin:$PATH"
+ eval "$(pyenv init -)"
+
+ echo "Installing Python ${major_minor} via pyenv..."
+ pyenv install -s "${major_minor}"
+ pyenv global "${major_minor}" 2>/dev/null || true
+}
+
+install_python_windows() {
+ echo "Windows Python installation is not fully automated."
+ echo "Please install Python manually from: https://www.python.org/downloads/"
+ echo ""
+ echo "Or use pyenv-win: https://github.com/pyenv-win/pyenv-win"
+ exit 1
+}
+
+install_python() {
+ detect_platform
+ install_dependencies
+
+ # Determine version to install
+ if [ -z "$PYTHON_VERSION" ]; then
+ PYTHON_VERSION=$(get_latest_python_version)
+ if [ -z "$PYTHON_VERSION" ]; then
+ echo "Failed to detect latest Python version from python.org" >&2
+ exit 1
+ fi
+ echo "Latest Python version (major.minor): ${PYTHON_VERSION}"
+ else
+ echo "Using specified Python version: ${PYTHON_VERSION}"
+ fi
+
+ local major_minor
+ major_minor=$(normalize_version "$PYTHON_VERSION")
+ local installed_version
+ installed_version=$(get_installed_python_version "python3")
+
+ # Check if requested version is already installed
+ if [ "$installed_version" != "none" ]; then
+ local installed_major_minor
+ installed_major_minor=$(normalize_version "$installed_version")
+ if [ "$installed_major_minor" = "$major_minor" ]; then
+ echo "Python ${major_minor} is already installed (version ${installed_version})."
+ echo "Current version: $(python3 --version 2>&1)"
+ exit 0
+ else
+ echo "Python ${installed_major_minor} is installed, but ${major_minor} is requested."
+ fi
+ fi
+
+ echo "Installing Python ${PYTHON_VERSION}..."
+
+ case "$PLATFORM" in
+ linux)
+ install_python_linux "$PYTHON_VERSION"
+ ;;
+ darwin)
+ install_python_darwin "$PYTHON_VERSION"
+ ;;
+ windows)
+ install_python_windows
+ ;;
+ *)
+ echo "Unsupported platform: $PLATFORM"
+ exit 1
+ ;;
+ esac
+
+ # Verify installation
+ local verify_cmd="python3"
+ if [ "$PLATFORM" = "darwin" ] && command -v pyenv >/dev/null 2>&1; then
+ verify_cmd="python"
+ fi
+
+ if command -v "$verify_cmd" >/dev/null 2>&1; then
+ echo "Python installed successfully: $($verify_cmd --version 2>&1)"
+ else
+ echo "Warning: Python installation completed but binary not found in PATH"
+ echo "You may need to restart your shell or add Python to PATH manually"
+ fi
+}
+
+uninstall_python() {
+ detect_platform
+ detect_package_manager
+
+ local installed_version
+ installed_version=$(get_installed_python_version "python3")
+
+ if [ "$installed_version" = "none" ]; then
+ echo "Python is not installed. Skipping uninstallation."
+ exit 0
+ fi
+
+ echo "Uninstalling Python ${installed_version}..."
+
+ case "$PLATFORM" in
+ linux)
+ local major_minor
+ major_minor=$(normalize_version "$installed_version")
+ echo "Warning: removing Python packages can affect system tools on non-container systems."
+ case "$PACKAGE_MANAGER" in
+ apt|apt-get)
+ $SUDO $PACKAGE_MANAGER remove -y "python${major_minor}" "python${major_minor}-dev" "python${major_minor}-venv" 2>/dev/null || true
+ ;;
+ dnf|yum)
+ $SUDO $PACKAGE_MANAGER remove -y "python${major_minor}" 2>/dev/null || true
+ ;;
+ esac
+ ;;
+ darwin)
+ local major_minor
+ major_minor=$(normalize_version "$installed_version")
+ if command -v pyenv >/dev/null 2>&1; then
+ echo "Note: Python may be installed via pyenv. Use 'pyenv uninstall ${installed_version}' to remove."
+ else
+ echo "Note: Python may be installed via Homebrew. Use 'brew uninstall python@${major_minor}' to remove."
+ fi
+ ;;
+ windows)
+ echo "Please uninstall Python manually from Windows Settings or Control Panel."
+ ;;
+ esac
+
+ echo "Python uninstallation completed."
+ echo "Note: system Python (python3) may still be available if it is a system dependency."
+}
+
+usage() {
+ echo "Usage: $0 [install|uninstall]"
+ echo ""
+ echo "Optional environment variables:"
+ echo " PYTHON_VERSION - Pin specific version (e.g., 3.12 or 3.12.5) or leave empty for latest"
+ echo ""
+ echo "Examples:"
+ echo " $0 install # Install latest Python"
+ echo " PYTHON_VERSION=3.12 $0 install # Install Python 3.12"
+ echo " PYTHON_VERSION=3.11.5 $0 install # Install Python 3.11.5"
+ echo " $0 uninstall # Uninstall Python"
+ exit 1
+}
+
+if [ "$#" -eq 0 ]; then
+ install_python
+elif [ "$1" = "install" ]; then
+ install_python
+elif [ "$1" = "uninstall" ]; then
+ uninstall_python
+else
+ usage
+fi
+
+if [ "${1:-install}" != "uninstall" ]; then
+ echo ""
+ echo "Python installation completed (or already satisfied)."
+ echo "Run 'python3 --version' to verify installation."
+fi