Skip to content

Instantly share code, notes, and snippets.

@NiklasRosenstein
Last active December 29, 2025 07:44
Show Gist options
  • Select an option

  • Save NiklasRosenstein/545ab9233d1a239a401d0957edbafb42 to your computer and use it in GitHub Desktop.

Select an option

Save NiklasRosenstein/545ab9233d1a239a401d0957edbafb42 to your computer and use it in GitHub Desktop.
#!/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