#!/bin/bash # Git Credential Manager Installer # Supports Debian, Ubuntu, RHEL, CentOS, Fedora, and other Linux distributions # Downloads and installs the latest version from GitHub releases set -e # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color # Configuration GITHUB_REPO="git-ecosystem/git-credential-manager" INSTALL_DIR="/usr/local/bin" TEMP_DIR="/tmp/gcm-install" # Function to print colored output print_status() { echo -e "${BLUE}[INFO]${NC} $1" } print_success() { echo -e "${GREEN}[SUCCESS]${NC} $1" } print_warning() { echo -e "${YELLOW}[WARNING]${NC} $1" } print_error() { echo -e "${RED}[ERROR]${NC} $1" } # Function to check if command exists command_exists() { command -v "$1" >/dev/null 2>&1 } # Function to detect OS and package manager detect_os() { if [[ -f /etc/os-release ]]; then . /etc/os-release OS="$ID" OS_VERSION="$VERSION_ID" else print_error "Cannot detect operating system" exit 1 fi # Detect package manager if command_exists apt; then PKG_MANAGER="apt" elif command_exists yum; then PKG_MANAGER="yum" elif command_exists dnf; then PKG_MANAGER="dnf" elif command_exists pacman; then PKG_MANAGER="pacman" elif command_exists zypper; then PKG_MANAGER="zypper" else PKG_MANAGER="unknown" fi print_status "Detected OS: $OS $OS_VERSION" print_status "Package manager: $PKG_MANAGER" } # Function to get system architecture get_arch() { ARCH=$(uname -m) case $ARCH in x86_64) echo "amd64" ;; aarch64|arm64) echo "arm64" ;; armv7l) echo "arm" ;; *) print_error "Unsupported architecture: $ARCH" exit 1 ;; esac } # Function to get current installed version get_current_version() { if command_exists git-credential-manager; then # Extract version from output like "2.6.1+786ab03440ddc82e807a97c0e540f5247e44cec6" local version_output=$(git-credential-manager --version 2>/dev/null) CURRENT_VERSION=$(echo "$version_output" | grep -o '[0-9]\+\.[0-9]\+\.[0-9]\+' | head -n 1) if [[ -n "$CURRENT_VERSION" ]]; then CURRENT_VERSION="v$CURRENT_VERSION" else CURRENT_VERSION="unknown" fi else CURRENT_VERSION="not_installed" fi echo "$CURRENT_VERSION" } # Function to compare versions (returns 0 if first >= second, 1 if first < second) compare_versions() { local version1="$1" local version2="$2" # Remove 'v' prefix if present version1=${version1#v} version2=${version2#v} # Split versions into arrays IFS='.' read -ra V1 <<< "$version1" IFS='.' read -ra V2 <<< "$version2" # Compare major, minor, patch for i in {0..2}; do local v1=${V1[$i]:-0} local v2=${V2[$i]:-0} if (( v1 > v2 )); then return 0 # version1 > version2 elif (( v1 < v2 )); then return 1 # version1 < version2 fi done return 0 # versions are equal } # Function to get latest release info get_latest_release() { print_status "Fetching latest release information..." if command_exists curl; then RELEASE_INFO=$(curl -s "https://api.github.com/repos/$GITHUB_REPO/releases/latest") elif command_exists wget; then RELEASE_INFO=$(wget -qO- "https://api.github.com/repos/$GITHUB_REPO/releases/latest") else print_error "curl or wget is required" exit 1 fi if [[ -z "$RELEASE_INFO" ]]; then print_error "Failed to fetch release information" exit 1 fi # Extract version and download URLs LATEST_VERSION=$(echo "$RELEASE_INFO" | grep -o '"tag_name": *"[^"]*"' | sed -E 's/.*"([^"]+)".*/\1/') if [[ -z "$LATEST_VERSION" ]]; then print_error "Failed to parse release information" exit 1 fi print_success "Latest version: $LATEST_VERSION" } # Function to check if update is needed check_update_needed() { local current_version=$(get_current_version) local latest_version="$1" print_status "Current version: $current_version" print_status "Latest version: $latest_version" if [[ "$current_version" == "not_installed" ]]; then print_status "git-credential-manager is not installed" return 0 # Need to install fi if [[ "$current_version" == "unknown" ]]; then print_warning "Cannot determine current version, proceeding with update" return 0 # Assume update needed fi if compare_versions "$latest_version" "$current_version"; then if [[ "$latest_version" == "$current_version" ]]; then print_success "You already have the latest version ($current_version)" if [[ "$force_reinstall" == "true" ]]; then print_status "Force reinstall requested, proceeding..." return 0 else return 1 # No update needed fi else print_status "Newer version available: $latest_version > $current_version" return 0 # Update needed fi else print_success "Your version ($current_version) is newer than latest release ($latest_version)" return 1 # No update needed fi } # Function to download file download_file() { local url="$1" local filename="$2" print_status "Downloading $filename..." if command_exists curl; then curl -L -o "$TEMP_DIR/$filename" "$url" elif command_exists wget; then wget -O "$TEMP_DIR/$filename" "$url" else print_error "curl or wget is required" exit 1 fi if [[ ! -f "$TEMP_DIR/$filename" ]]; then print_error "Failed to download $filename" exit 1 fi print_success "Downloaded $filename" } # Function to install from package manager install_from_package_manager() { print_status "Attempting to install from package manager..." case $PKG_MANAGER in apt) # Check if git-credential-manager is available in repos if apt-cache show git-credential-manager >/dev/null 2>&1; then print_status "Installing git-credential-manager from apt repository..." sudo apt update sudo apt install -y git-credential-manager return 0 else print_warning "git-credential-manager not available in apt repositories" return 1 fi ;; yum|dnf) # Check EPEL repositories for RHEL/CentOS/Fedora if command_exists git-credential-manager; then print_success "git-credential-manager is already installed" return 0 else print_warning "git-credential-manager not available in $PKG_MANAGER repositories" return 1 fi ;; pacman) # Arch Linux has git-credential-manager in community repo if pacman -Si git-credential-manager >/dev/null 2>&1; then print_status "Installing git-credential-manager from pacman repository..." sudo pacman -S --noconfirm git-credential-manager return 0 else print_warning "git-credential-manager not available in pacman repositories" return 1 fi ;; *) print_warning "Package manager $PKG_MANAGER not supported for automatic installation" return 1 ;; esac } # Function to install from DEB package install_from_deb() { local arch="$1" local version="$2" local deb_name="gcm-linux_${arch}.${version#v}.deb" local download_url="https://github.com/$GITHUB_REPO/releases/download/$version/$deb_name" download_file "$download_url" "$deb_name" print_status "Installing DEB package..." sudo dpkg -i "$TEMP_DIR/$deb_name" || { print_warning "dpkg failed, attempting to fix dependencies..." sudo apt-get install -f -y } print_success "DEB package installed successfully" } # Function to install from tar.gz install_from_tarball() { local arch="$1" local version="$2" local tar_name="gcm-linux_${arch}.${version#v}.tar.gz" local download_url="https://github.com/$GITHUB_REPO/releases/download/$version/$tar_name" download_file "$download_url" "$tar_name" print_status "Extracting tarball..." cd "$TEMP_DIR" tar -xzf "$tar_name" # Check if binary was extracted directly if [[ -f "git-credential-manager" ]]; then print_status "Installing binary to $INSTALL_DIR..." sudo install -m 755 git-credential-manager "$INSTALL_DIR/git-credential-manager" sudo ln -sf "$INSTALL_DIR/git-credential-manager" "$INSTALL_DIR/git-credential-manager-core" # Install shared libraries if present for lib in lib*.so; do if [[ -f "$lib" ]]; then sudo install -m 644 "$lib" "$INSTALL_DIR/" print_status "Installed library: $lib" fi done else # Try to find extracted directory (fallback) local extracted_dir=$(find . -maxdepth 1 -type d -name "git-credential-manager*" | head -n 1) if [[ -z "$extracted_dir" ]]; then print_error "Could not find git-credential-manager binary after extraction" exit 1 fi cd "$extracted_dir" print_status "Installing binary to $INSTALL_DIR..." sudo install -m 755 git-credential-manager "$INSTALL_DIR/git-credential-manager" sudo ln -sf "$INSTALL_DIR/git-credential-manager" "$INSTALL_DIR/git-credential-manager-core" fi print_success "Binary installed successfully" } # Function to check and configure Git presets configure_git_presets() { print_status "Checking and configuring Git presets..." local presets_configured=0 local presets_needed=() # Check global credential helper local current_helper="" current_helper=$(git config --global --get credential.helper 2>/dev/null) || current_helper="" if [[ "$current_helper" == "manager" ]] || [[ "$current_helper" == "git-credential-manager" ]]; then print_success "✓ Global credential helper: $current_helper" presets_configured=$((presets_configured + 1)) else print_warning "✗ Global credential helper not set to manager" presets_needed+=("git config --global credential.helper manager") fi # Check GitHub-specific configuration local github_helper="" github_helper=$(git config --global --get credential.https://github.com.helper 2>/dev/null) || github_helper="" if [[ -n "$github_helper" ]]; then print_success "✓ GitHub credential helper configured ($github_helper)" presets_configured=$((presets_configured + 1)) else print_warning "✗ GitHub credential helper not configured" presets_needed+=("git config --global credential.https://github.com.helper manager") fi # Check GitLab-specific configuration local gitlab_helper="" gitlab_helper=$(git config --global --get credential.https://gitlab.com.helper 2>/dev/null) || gitlab_helper="" if [[ -n "$gitlab_helper" ]]; then print_success "✓ GitLab credential helper configured" presets_configured=$((presets_configured + 1)) else print_warning "✗ GitLab credential helper not configured" presets_needed+=("git config --global credential.https://gitlab.com.helper manager") fi # Check Azure DevOps configuration local azure_useHttpPath="" azure_useHttpPath=$(git config --global --get credential.https://dev.azure.com.useHttpPath 2>/dev/null) || azure_useHttpPath="" if [[ "$azure_useHttpPath" == "true" ]]; then print_success "✓ Azure DevOps useHttpPath configured" presets_configured=$((presets_configured + 1)) else print_warning "✗ Azure DevOps useHttpPath not configured" presets_needed+=("git config --global credential.https://dev.azure.com.useHttpPath true") fi # Check Bitbucket configuration local bitbucket_helper="" bitbucket_helper=$(git config --global --get credential.https://bitbucket.org.helper 2>/dev/null) || bitbucket_helper="" if [[ -n "$bitbucket_helper" ]]; then print_success "✓ Bitbucket credential helper configured" presets_configured=$((presets_configured + 1)) else print_warning "✗ Bitbucket credential helper not configured" presets_needed+=("git config --global credential.https://bitbucket.org.helper manager") fi # Check specific Gitea configurations local gitea_configured=0 local gitea_needed=() # Check go-gitea.mywire.org local gitea1_helper="" gitea1_helper=$(git config --global --get credential.https://go-gitea.mywire.org.helper 2>/dev/null) || gitea1_helper="" if [[ "$gitea1_helper" == "manager" ]]; then print_success "✓ Gitea (go-gitea.mywire.org) credential helper configured" gitea_configured=$((gitea_configured + 1)) else print_warning "✗ Gitea (go-gitea.mywire.org) credential helper not configured" gitea_needed+=("git config --global credential.https://go-gitea.mywire.org.helper manager") fi # Check 192.168.88.97:3000 local gitea2_helper="" gitea2_helper=$(git config --global --get credential.https://192.168.88.97:3000.helper 2>/dev/null) || gitea2_helper="" if [[ "$gitea2_helper" == "manager" ]]; then print_success "✓ Gitea (192.168.88.97:3000) credential helper configured" gitea_configured=$((gitea_configured + 1)) else print_warning "✗ Gitea (192.168.88.97:3000) credential helper not configured" gitea_needed+=("git config --global credential.https://192.168.88.97:3000.helper manager") fi # Check for other Gitea configurations local other_gitea_urls=0 other_gitea_urls=$(git config --global --get-regexp "credential.*gitea.*helper" 2>/dev/null | grep -v "go-gitea.mywire.org\|192.168.88.97:3000" | wc -l) || other_gitea_urls=0 if [[ $other_gitea_urls -gt 0 ]]; then print_success "✓ Additional Gitea credential helpers configured ($other_gitea_urls found)" fi if [[ $gitea_configured -eq 2 ]]; then presets_configured=$((presets_configured + 1)) fi # Add needed Gitea configs to presets_needed if [[ ${#gitea_needed[@]} -gt 0 ]]; then presets_needed+=("# Configure Gitea credential helpers:") presets_needed+=("${gitea_needed[@]}") fi # Summary echo print_status "Git Credential Manager Configuration Summary:" print_status "Presets configured: $presets_configured/7" if [[ ${#presets_needed[@]} -eq 0 ]]; then print_success "All recommended presets are configured!" echo print_status "Your Git Credential Manager is fully configured and ready to use." echo "When you run Git commands that require authentication, GCM will:" echo "1. Prompt for credentials on first use" echo "2. Securely store them in your system's credential store" echo "3. Automatically provide them for subsequent operations" else print_warning "Some presets need to be configured manually." echo print_status "Run these commands to complete the configuration:" for cmd in "${presets_needed[@]}"; do echo " $cmd" done echo print_status "After configuring, test with:" echo " git config --global --list | grep credential" fi return 0 } # Function to verify installation verify_installation() { print_status "Verifying installation..." if command_exists git-credential-manager; then local version=$(git-credential-manager --version 2>/dev/null || echo "unknown") print_success "git-credential-manager installed: $version" # Configure basic Git settings print_status "Configuring basic Git settings..." git config --global credential.helper manager print_success "Basic Git configuration completed" # Check all presets configure_git_presets return 0 else print_error "git-credential-manager installation verification failed" return 1 fi } # Function to cleanup cleanup() { if [[ -d "$TEMP_DIR" ]]; then rm -rf "$TEMP_DIR" print_status "Cleaned up temporary files" fi } # Function to show usage show_usage() { echo "Git Credential Manager Installer" echo echo "Usage: $0 [OPTIONS]" echo echo "Options:" echo " -h, --help Show this help message" echo " -v, --version Install specific version (default: latest)" echo " -f, --force Force reinstall even if already installed" echo " -p, --package Force installation from package manager" echo " -d, --deb Force installation from DEB package" echo " -t, --tarball Force installation from tarball" echo " -c, --checks Check Git credential configuration without installing" echo echo "Examples:" echo " $0 # Install latest version using best method" echo " $0 -v v2.6.0 # Install specific version" echo " $0 --package # Force installation from package manager" echo " $0 --deb # Force installation from DEB package" echo " $0 --checks # Check configuration without installing" } # Function to run configuration checks only run_checks_only() { echo "========================================" echo "Git Credential Manager Configuration Check" echo "========================================" echo # Check if GCM is installed if ! command_exists git-credential-manager; then print_error "Git Credential Manager is not installed" echo print_status "To install Git Credential Manager, run:" echo " $0" echo print_status "Or to check configuration after installation:" echo " $0 --checks" exit 1 fi # Run configuration checks configure_git_presets echo print_success "Configuration check completed!" } # Main installation function main() { local target_version="" local force_reinstall=false local install_method="auto" local checks_only=false # Parse command line arguments while [[ $# -gt 0 ]]; do case $1 in -h|--help) show_usage exit 0 ;; -v|--version) target_version="$2" shift 2 ;; -f|--force) force_reinstall=true shift ;; -p|--package) install_method="package" shift ;; -d|--deb) install_method="deb" shift ;; -t|--tarball) install_method="tarball" shift ;; -c|--checks) checks_only=true shift ;; *) print_error "Unknown option: $1" show_usage exit 1 ;; esac done # If checks only, run checks and exit if [[ "$checks_only" == "true" ]]; then run_checks_only exit 0 fi echo "========================================" echo "Git Credential Manager Installer" echo "========================================" echo # Get latest version if not specified if [[ -z "$target_version" ]]; then get_latest_release target_version="$LATEST_VERSION" else print_status "Using specified version: $target_version" fi # Check if update is needed if ! check_update_needed "$target_version"; then print_success "No installation needed. Exiting." exit 0 fi # Detect system detect_os ARCH=$(get_arch) # Create temporary directory mkdir -p "$TEMP_DIR" # Trap cleanup on exit trap cleanup EXIT # Installation logic based on method case $install_method in auto) # Try package manager first if install_from_package_manager; then verify_installation exit 0 fi # Fall back to distribution-specific methods case $OS in ubuntu|debian|linuxmint) install_from_deb "$ARCH" "$target_version" ;; *) install_from_tarball "$ARCH" "$target_version" ;; esac ;; package) if ! install_from_package_manager; then print_error "Package manager installation failed" exit 1 fi ;; deb) install_from_deb "$ARCH" "$target_version" ;; tarball) install_from_tarball "$ARCH" "$target_version" ;; esac # Verify installation if verify_installation; then echo print_success "Installation completed successfully!" echo print_status "Test the installation:" echo " git-credential-manager --version" echo " git config --global --list | grep credential" else print_error "Installation failed" exit 1 fi } # Run main function main "$@"