Last active
December 29, 2025 07:44
-
-
Save NiklasRosenstein/545ab9233d1a239a401d0957edbafb42 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| set -euo pipefail | |
| # Colors for output | |
| RED='\033[0;31m' | |
| GREEN='\033[0;32m' | |
| YELLOW='\033[1;33m' | |
| NC='\033[0m' # No Color | |
| # Function to print colored messages | |
| print_info() { | |
| echo -e "${GREEN}[INFO]${NC} $1" | |
| } | |
| print_error() { | |
| echo -e "${RED}[ERROR]${NC} $1" | |
| } | |
| print_warning() { | |
| echo -e "${YELLOW}[WARNING]${NC} $1" | |
| } | |
| # Function to detect architecture for Docker Compose | |
| detect_arch_compose() { | |
| local arch | |
| arch=$(uname -m) | |
| case "$arch" in | |
| x86_64) | |
| echo "x86_64" | |
| ;; | |
| aarch64|arm64) | |
| echo "aarch64" | |
| ;; | |
| armv7l) | |
| echo "armv7" | |
| ;; | |
| *) | |
| print_error "Unsupported architecture: $arch" | |
| exit 1 | |
| ;; | |
| esac | |
| } | |
| # Function to detect architecture for Docker Buildx | |
| detect_arch_buildx() { | |
| local arch | |
| arch=$(uname -m) | |
| case "$arch" in | |
| x86_64) | |
| echo "amd64" | |
| ;; | |
| aarch64|arm64) | |
| echo "arm64" | |
| ;; | |
| armv7l) | |
| echo "arm-v7" | |
| ;; | |
| *) | |
| print_error "Unsupported architecture: $arch" | |
| exit 1 | |
| ;; | |
| esac | |
| } | |
| # Function to detect OS | |
| detect_os() { | |
| local os | |
| os=$(uname -s | tr '[:upper:]' '[:lower:]') | |
| case "$os" in | |
| linux) | |
| echo "linux" | |
| ;; | |
| darwin) | |
| echo "darwin" | |
| ;; | |
| *) | |
| print_error "Unsupported operating system: $os" | |
| exit 1 | |
| ;; | |
| esac | |
| } | |
| # Function to get latest release version from GitHub | |
| get_latest_release() { | |
| local repo=$1 | |
| local version | |
| # Try using GitHub API first | |
| version=$(curl -sL "https://api.github.com/repos/$repo/releases/latest" 2>/dev/null | \ | |
| grep '"tag_name":' | \ | |
| sed -E 's/.*"v?([^"]+)".*/\1/' | \ | |
| head -n1) | |
| # If API fails (rate limit or network issue), try scraping the releases page | |
| if [ -z "$version" ]; then | |
| print_warning "GitHub API failed, trying alternative method..." >&2 | |
| version=$(curl -sL "https://github.com/$repo/releases/latest" 2>/dev/null | \ | |
| grep -oP 'releases/tag/v?\K[0-9]+\.[0-9]+\.[0-9]+' | \ | |
| head -n1) | |
| fi | |
| # If still empty, try one more method: check the redirect location | |
| if [ -z "$version" ]; then | |
| print_warning "Trying redirect method..." >&2 | |
| version=$(curl -sLI -o /dev/null -w '%{url_effective}' "https://github.com/$repo/releases/latest" 2>/dev/null | \ | |
| grep -oP 'releases/tag/v?\K[0-9]+\.[0-9]+\.[0-9]+' | \ | |
| head -n1) | |
| fi | |
| if [ -z "$version" ]; then | |
| print_error "Failed to fetch latest version for $repo" >&2 | |
| print_error "This could be due to GitHub rate limiting or network issues." >&2 | |
| print_error "Please try again later or specify a version manually." >&2 | |
| return 1 | |
| fi | |
| echo "$version" | |
| } | |
| # Function to install plugin | |
| install_plugin() { | |
| local plugin_name=$1 | |
| local repo=$2 | |
| local binary_name=$3 | |
| local install_dir=$4 | |
| print_info "Installing $plugin_name..." | |
| # Get latest version | |
| local version | |
| if ! version=$(get_latest_release "$repo"); then | |
| print_error "Cannot proceed without version information" >&2 | |
| return 1 | |
| fi | |
| print_info "Latest version: v$version" | |
| # Detect system | |
| local os | |
| os=$(detect_os) | |
| print_info "Detected OS: $os" | |
| # Construct download URL based on plugin | |
| local download_url arch | |
| if [ "$plugin_name" == "Docker Compose" ]; then | |
| arch=$(detect_arch_compose) | |
| print_info "Detected architecture: $arch" | |
| download_url="https://github.com/$repo/releases/download/v${version}/docker-compose-${os}-${arch}" | |
| elif [ "$plugin_name" == "Docker Buildx" ]; then | |
| arch=$(detect_arch_buildx) | |
| print_info "Detected architecture: $arch" | |
| download_url="https://github.com/$repo/releases/download/v${version}/buildx-v${version}.${os}-${arch}" | |
| fi | |
| print_info "Downloading from: $download_url" | |
| # Create install directory if it doesn't exist | |
| mkdir -p "$install_dir" | |
| # Download the plugin with HTTP status code check | |
| local temp_file temp_error | |
| temp_file=$(mktemp) | |
| temp_error=$(mktemp) | |
| local http_code | |
| http_code=$(curl -sL -w "%{http_code}" -o "$temp_file" "$download_url" 2>"$temp_error") | |
| local curl_exit_code=$? | |
| # Check if curl command itself failed | |
| if [ $curl_exit_code -ne 0 ]; then | |
| print_error "curl command failed with exit code $curl_exit_code" >&2 | |
| if [ -s "$temp_error" ]; then | |
| print_error "curl error: $(cat "$temp_error")" >&2 | |
| fi | |
| print_error "URL: $download_url" >&2 | |
| rm -f "$temp_file" "$temp_error" | |
| return 1 | |
| fi | |
| # Validate http_code is a number | |
| if ! [[ "$http_code" =~ ^[0-9]+$ ]]; then | |
| print_error "Failed to download $plugin_name (invalid HTTP response)" >&2 | |
| print_error "URL: $download_url" >&2 | |
| print_error "Response code: '$http_code'" >&2 | |
| if [ -s "$temp_error" ]; then | |
| print_error "curl error: $(cat "$temp_error")" >&2 | |
| fi | |
| rm -f "$temp_file" "$temp_error" | |
| return 1 | |
| fi | |
| if [ "$http_code" -ne 200 ]; then | |
| print_error "Failed to download $plugin_name (HTTP $http_code)" >&2 | |
| print_error "URL: $download_url" >&2 | |
| print_error "This usually means the download URL is incorrect or the release doesn't exist for your platform." >&2 | |
| rm -f "$temp_file" "$temp_error" | |
| return 1 | |
| fi | |
| rm -f "$temp_error" | |
| # Check if downloaded file is valid (not an error page) | |
| local file_size | |
| file_size=$(wc -c < "$temp_file") | |
| if [ "$file_size" -lt 1000 ] && grep -q "Not Found" "$temp_file" 2>/dev/null; then | |
| print_error "Download resulted in error page (file size: ${file_size} bytes, contains 'Not Found')" >&2 | |
| print_error "URL: $download_url" >&2 | |
| rm -f "$temp_file" | |
| return 1 | |
| fi | |
| # Make it executable | |
| chmod +x "$temp_file" | |
| # Move to install directory | |
| local target_path="$install_dir/$binary_name" | |
| if [ -f "$target_path" ]; then | |
| print_warning "Removing existing $plugin_name at $target_path" | |
| rm -f "$target_path" | |
| fi | |
| mv "$temp_file" "$target_path" | |
| print_info "$plugin_name installed successfully to $target_path" | |
| # Verify installation | |
| if docker "$binary_name" version &>/dev/null; then | |
| local installed_version | |
| installed_version=$(docker "$binary_name" version --short 2>/dev/null || docker "$binary_name" version 2>/dev/null | head -n1) | |
| print_info "Verified: $installed_version" | |
| else | |
| print_warning "Could not verify installation. You may need to restart your shell or check Docker installation." | |
| fi | |
| } | |
| # Main script | |
| main() { | |
| print_info "Docker Plugin Installer" | |
| print_info "=======================" | |
| echo | |
| # Check GitHub API rate limit | |
| print_info "Checking GitHub API access..." | |
| local rate_limit | |
| rate_limit=$(curl -s "https://api.github.com/rate_limit" 2>/dev/null | grep -oP '"remaining":\s*\K[0-9]+' | head -n1) | |
| if [ -n "$rate_limit" ]; then | |
| if [ "$rate_limit" -lt 5 ]; then | |
| print_warning "GitHub API rate limit is low (remaining: $rate_limit)" | |
| print_warning "The script will use alternative methods to fetch version information" | |
| else | |
| print_info "GitHub API rate limit OK (remaining: $rate_limit)" | |
| fi | |
| fi | |
| echo | |
| # Check if Docker is installed | |
| if ! command -v docker &>/dev/null; then | |
| print_error "Docker is not installed. Please install Docker first." | |
| exit 1 | |
| fi | |
| # Determine installation directory | |
| local install_dir | |
| local install_mode | |
| if [ "${1:-}" == "--global" ] || [ "${1:-}" == "-g" ]; then | |
| install_dir="/usr/lib/docker/cli-plugins" | |
| install_mode="global" | |
| # Check if we have root privileges | |
| if [ "$EUID" -ne 0 ]; then | |
| print_error "Global installation requires root privileges. Please run with sudo." | |
| exit 1 | |
| fi | |
| else | |
| install_dir="$HOME/.docker/cli-plugins" | |
| install_mode="user" | |
| fi | |
| print_info "Installation mode: $install_mode" | |
| print_info "Installation directory: $install_dir" | |
| echo | |
| # Install Docker Compose | |
| if ! install_plugin "Docker Compose" "docker/compose" "docker-compose" "$install_dir"; then | |
| print_error "Docker Compose installation failed. Continuing with Buildx..." >&2 | |
| echo | |
| else | |
| echo | |
| fi | |
| # Install Docker Buildx | |
| if ! install_plugin "Docker Buildx" "docker/buildx" "docker-buildx" "$install_dir"; then | |
| print_error "Docker Buildx installation failed." >&2 | |
| echo | |
| exit 1 | |
| fi | |
| echo | |
| print_info "Installation complete!" | |
| print_info "" | |
| print_info "Installed plugins:" | |
| docker compose version 2>/dev/null || print_warning "docker compose: not available" | |
| docker buildx version 2>/dev/null || print_warning "docker buildx: not available" | |
| echo | |
| print_info "If plugins are not available, try restarting your shell or logging out and back in." | |
| } | |
| # Show usage | |
| if [ "${1:-}" == "--help" ] || [ "${1:-}" == "-h" ]; then | |
| echo "Usage: $0 [OPTIONS]" | |
| echo "" | |
| echo "Install the latest Docker Compose and Buildx plugins from GitHub" | |
| echo "" | |
| echo "Options:" | |
| echo " -g, --global Install to /usr/lib/docker/cli-plugins (requires sudo)" | |
| echo " -h, --help Show this help message" | |
| echo "" | |
| echo "Default: Install to ~/.docker/cli-plugins (user installation)" | |
| exit 0 | |
| fi | |
| main "$@" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment