- Add package_manager.sh for handling system package installations - Add profile_manager.sh for managing machine-specific configurations - Add update_checker.sh for version control and updates 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
590 lines
22 KiB
Bash
Executable File
590 lines
22 KiB
Bash
Executable File
#!/bin/bash
|
|
# Package Management Library for Dotfiles
|
|
# Handles installation and checking of various package types
|
|
|
|
# Global variables
|
|
PACKAGES_CONFIG="$HOME/.dotfiles/packages.yaml"
|
|
PACKAGE_LOG="$HOME/.dotfiles/.package.log"
|
|
|
|
# Function to log package operations
|
|
pkg_log() {
|
|
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> "$PACKAGE_LOG"
|
|
}
|
|
|
|
# Function to detect OS
|
|
detect_os() {
|
|
if [[ "$OSTYPE" == "linux-gnu"* ]]; then
|
|
if command -v lsb_release >/dev/null 2>&1; then
|
|
lsb_release -si | tr '[:upper:]' '[:lower:]'
|
|
elif [[ -f /etc/debian_version ]]; then
|
|
echo "debian"
|
|
elif [[ -f /etc/redhat-release ]]; then
|
|
if grep -q "Fedora" /etc/redhat-release; then
|
|
echo "fedora"
|
|
elif grep -q "CentOS" /etc/redhat-release; then
|
|
echo "centos"
|
|
else
|
|
echo "rhel"
|
|
fi
|
|
else
|
|
echo "linux"
|
|
fi
|
|
elif [[ "$OSTYPE" == "darwin"* ]]; then
|
|
echo "macos"
|
|
else
|
|
echo "unknown"
|
|
fi
|
|
}
|
|
|
|
# Function to parse YAML (basic implementation)
|
|
parse_yaml() {
|
|
local yaml_file="$1"
|
|
local section="$2"
|
|
|
|
if [[ ! -f "$yaml_file" ]]; then
|
|
pkg_log "ERROR: Package config file not found: $yaml_file"
|
|
return 1
|
|
fi
|
|
|
|
# Extract section from YAML (basic implementation)
|
|
awk -v section="$section" '
|
|
/^[a-zA-Z_][a-zA-Z0-9_]*:/ {
|
|
current_section = substr($1, 1, length($1)-1)
|
|
in_section = (current_section == section)
|
|
next
|
|
}
|
|
/^ - name:/ && in_section {
|
|
name = substr($3, 1)
|
|
getline; desc = substr($0, match($0, /: "/) + 3, length($0) - match($0, /: "/) - 3)
|
|
getline; check = substr($0, match($0, /: "/) + 3, length($0) - match($0, /: "/) - 3)
|
|
print name "|" desc "|" check
|
|
}
|
|
' "$yaml_file"
|
|
}
|
|
|
|
# Function to check if a package is installed
|
|
is_package_installed() {
|
|
local check_command="$1"
|
|
eval "$check_command" >/dev/null 2>&1
|
|
}
|
|
|
|
# Function to install system packages
|
|
install_system_package() {
|
|
local package_name="$1"
|
|
local os=$(detect_os)
|
|
|
|
pkg_log "Installing system package: $package_name for OS: $os"
|
|
|
|
case "$os" in
|
|
"debian"|"ubuntu")
|
|
case "$package_name" in
|
|
"sshuttle")
|
|
sudo apt update && sudo apt install -y sshuttle
|
|
;;
|
|
"curl")
|
|
sudo apt update && sudo apt install -y curl
|
|
;;
|
|
"git")
|
|
sudo apt update && sudo apt install -y git
|
|
;;
|
|
"vim")
|
|
sudo apt update && sudo apt install -y vim
|
|
;;
|
|
"docker")
|
|
curl -fsSL https://get.docker.com -o get-docker.sh && sudo sh get-docker.sh && rm get-docker.sh
|
|
;;
|
|
"docker-compose")
|
|
sudo apt update && sudo apt install -y docker-compose
|
|
;;
|
|
*)
|
|
pkg_log "Unknown system package: $package_name"
|
|
return 1
|
|
;;
|
|
esac
|
|
;;
|
|
"fedora")
|
|
case "$package_name" in
|
|
"sshuttle"|"curl"|"git"|"vim"|"docker"|"docker-compose")
|
|
sudo dnf install -y "$package_name"
|
|
;;
|
|
*)
|
|
pkg_log "Unknown system package: $package_name"
|
|
return 1
|
|
;;
|
|
esac
|
|
;;
|
|
"centos"|"rhel")
|
|
case "$package_name" in
|
|
"sshuttle"|"curl"|"git"|"vim"|"docker"|"docker-compose")
|
|
sudo yum install -y "$package_name"
|
|
;;
|
|
*)
|
|
pkg_log "Unknown system package: $package_name"
|
|
return 1
|
|
;;
|
|
esac
|
|
;;
|
|
"macos")
|
|
if ! command -v brew >/dev/null 2>&1; then
|
|
pkg_log "Homebrew not installed. Installing..."
|
|
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
|
|
fi
|
|
case "$package_name" in
|
|
"sshuttle"|"curl"|"git"|"vim"|"docker-compose")
|
|
brew install "$package_name"
|
|
;;
|
|
"docker")
|
|
brew install --cask docker
|
|
;;
|
|
*)
|
|
pkg_log "Unknown system package: $package_name"
|
|
return 1
|
|
;;
|
|
esac
|
|
;;
|
|
*)
|
|
pkg_log "Unsupported OS: $os"
|
|
return 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# Function to install GitHub packages
|
|
install_github_package() {
|
|
local package_name="$1"
|
|
local repo="$2"
|
|
local install_method="$3"
|
|
local install_path="$4"
|
|
local binary_name="$5"
|
|
local asset_pattern="$6"
|
|
local post_install="$7"
|
|
|
|
pkg_log "Installing GitHub package: $package_name from $repo"
|
|
|
|
# Create install directory if it doesn't exist
|
|
mkdir -p "$install_path"
|
|
|
|
case "$install_method" in
|
|
"clone_and_install")
|
|
echo " Cloning repository..."
|
|
if [[ -d "$install_path" ]]; then
|
|
rm -rf "$install_path"
|
|
fi
|
|
if git clone "https://github.com/$repo.git" "$install_path"; then
|
|
if [[ -n "$post_install" ]]; then
|
|
echo " Running post-install script..."
|
|
cd "$install_path" && eval "$post_install"
|
|
fi
|
|
return 0
|
|
else
|
|
pkg_log "Failed to clone repository: $repo"
|
|
return 1
|
|
fi
|
|
;;
|
|
"release_binary")
|
|
echo " Downloading latest release..."
|
|
local latest_url=$(curl -s "https://api.github.com/repos/$repo/releases/latest" | grep -o "https://github.com/$repo/releases/download/[^\"]*$asset_pattern[^\"]*")
|
|
if [[ -n "$latest_url" ]]; then
|
|
local temp_file="/tmp/${binary_name}_download"
|
|
if curl -L "$latest_url" -o "$temp_file"; then
|
|
# Handle different archive types
|
|
if [[ "$latest_url" == *.tar.gz ]]; then
|
|
tar -xzf "$temp_file" -C /tmp/
|
|
find /tmp -name "$binary_name" -type f -executable | head -1 | xargs -I {} mv {} "$install_path/$binary_name"
|
|
elif [[ "$latest_url" == *.zip ]]; then
|
|
unzip -q "$temp_file" -d /tmp/
|
|
find /tmp -name "$binary_name" -type f -executable | head -1 | xargs -I {} mv {} "$install_path/$binary_name"
|
|
else
|
|
# Assume it's a direct binary
|
|
mv "$temp_file" "$install_path/$binary_name"
|
|
fi
|
|
chmod +x "$install_path/$binary_name"
|
|
rm -f "$temp_file"
|
|
# Clean up extracted files
|
|
find /tmp -name "*$binary_name*" -type d -exec rm -rf {} + 2>/dev/null || true
|
|
return 0
|
|
else
|
|
pkg_log "Failed to download: $latest_url"
|
|
return 1
|
|
fi
|
|
else
|
|
pkg_log "Could not find release for: $repo with pattern: $asset_pattern"
|
|
return 1
|
|
fi
|
|
;;
|
|
*)
|
|
pkg_log "Unknown install method: $install_method"
|
|
return 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# Function to install binary packages
|
|
install_binary_package() {
|
|
local package_name="$1"
|
|
local install_type="$2"
|
|
local extra_params="$3"
|
|
|
|
pkg_log "Installing binary package: $package_name"
|
|
|
|
case "$install_type" in
|
|
"script")
|
|
case "$package_name" in
|
|
"claude-code")
|
|
echo "Installing Claude Code CLI..."
|
|
if [[ "$OSTYPE" == "linux-gnu"* ]] || [[ "$OSTYPE" == "darwin"* ]]; then
|
|
curl -fsSL https://storage.googleapis.com/anthropic-artifacts/claude-code/install.sh | bash
|
|
else
|
|
pkg_log "Unsupported OS for Claude Code CLI"
|
|
return 1
|
|
fi
|
|
;;
|
|
*)
|
|
pkg_log "Unknown script package: $package_name"
|
|
return 1
|
|
;;
|
|
esac
|
|
;;
|
|
"npm")
|
|
case "$package_name" in
|
|
"claude-code")
|
|
echo "Installing Claude Code CLI..."
|
|
if command -v npm >/dev/null 2>&1; then
|
|
npm install -g @anthropic-ai/claude-code
|
|
if [[ $? -eq 0 ]]; then
|
|
echo "✅ Claude Code installed successfully via npm"
|
|
echo "💡 Run 'claude doctor' to verify installation"
|
|
return 0
|
|
else
|
|
echo "⚠️ npm installation failed, trying binary installation..."
|
|
if [[ "$OSTYPE" == "linux-gnu"* ]] || [[ "$OSTYPE" == "darwin"* ]]; then
|
|
curl -fsSL https://claude.ai/install.sh | bash
|
|
else
|
|
pkg_log "Unsupported OS for Claude Code CLI"
|
|
return 1
|
|
fi
|
|
fi
|
|
else
|
|
echo "npm not found, trying binary installation..."
|
|
if [[ "$OSTYPE" == "linux-gnu"* ]] || [[ "$OSTYPE" == "darwin"* ]]; then
|
|
curl -fsSL https://claude.ai/install.sh | bash
|
|
else
|
|
pkg_log "npm and binary installation not available"
|
|
return 1
|
|
fi
|
|
fi
|
|
;;
|
|
"gemini-cli")
|
|
echo "Installing Gemini CLI..."
|
|
local installed=false
|
|
|
|
# Try npm first (requires Node.js 20+)
|
|
if command -v npm >/dev/null 2>&1; then
|
|
echo " Trying npm installation..."
|
|
if npm install -g @google/gemini-cli; then
|
|
echo "✅ Gemini CLI installed successfully via npm"
|
|
installed=true
|
|
else
|
|
echo "⚠️ npm global installation failed"
|
|
fi
|
|
fi
|
|
|
|
# Try homebrew on macOS if npm failed
|
|
if [[ "$installed" != "true" ]] && [[ "$OSTYPE" == "darwin"* ]] && command -v brew >/dev/null 2>&1; then
|
|
echo " Trying Homebrew installation..."
|
|
if brew install gemini-cli; then
|
|
echo "✅ Gemini CLI installed successfully via Homebrew"
|
|
installed=true
|
|
else
|
|
echo "⚠️ Homebrew installation failed"
|
|
fi
|
|
fi
|
|
|
|
# Inform about npx option if all else fails
|
|
if [[ "$installed" != "true" ]]; then
|
|
echo "❌ Global installation failed"
|
|
echo "💡 You can still use Gemini CLI with: npx https://github.com/google-gemini/gemini-cli"
|
|
pkg_log "Gemini CLI global installation failed, npx option available"
|
|
return 1
|
|
fi
|
|
;;
|
|
*)
|
|
pkg_log "Unknown npm package: $package_name"
|
|
return 1
|
|
;;
|
|
esac
|
|
;;
|
|
"github")
|
|
# This would need to parse more details from the config
|
|
echo "⚠️ GitHub installation for $package_name needs configuration"
|
|
pkg_log "GitHub installation not yet implemented for: $package_name"
|
|
return 1
|
|
;;
|
|
*)
|
|
pkg_log "Unknown install type: $install_type"
|
|
return 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# Function to install Python packages
|
|
install_python_package() {
|
|
local package_name="$1"
|
|
|
|
pkg_log "Installing Python package: $package_name"
|
|
|
|
if command -v pip3 >/dev/null 2>&1; then
|
|
pip3 install "$package_name"
|
|
elif command -v pip >/dev/null 2>&1; then
|
|
pip install "$package_name"
|
|
else
|
|
pkg_log "pip not found. Cannot install Python package: $package_name"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Function to install npm packages
|
|
install_npm_package() {
|
|
local package_name="$1"
|
|
|
|
pkg_log "Installing npm package: $package_name"
|
|
|
|
if command -v npm >/dev/null 2>&1; then
|
|
npm install -g "$package_name"
|
|
else
|
|
pkg_log "npm not found. Cannot install npm package: $package_name"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Function to install zsh plugins
|
|
install_zsh_plugin() {
|
|
local plugin_name="$1"
|
|
|
|
if [[ ! -d "$HOME/.oh-my-zsh" ]]; then
|
|
pkg_log "Oh My Zsh not installed. Skipping zsh plugin: $plugin_name"
|
|
return 1
|
|
fi
|
|
|
|
pkg_log "Installing zsh plugin: $plugin_name"
|
|
|
|
case "$plugin_name" in
|
|
"zsh-syntax-highlighting")
|
|
if [[ ! -d "${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting" ]]; then
|
|
git clone https://github.com/zsh-users/zsh-syntax-highlighting.git "${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting"
|
|
fi
|
|
;;
|
|
"zsh-autosuggestions")
|
|
if [[ ! -d "${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/plugins/zsh-autosuggestions" ]]; then
|
|
git clone https://github.com/zsh-users/zsh-autosuggestions "${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/plugins/zsh-autosuggestions"
|
|
fi
|
|
;;
|
|
*)
|
|
pkg_log "Unknown zsh plugin: $plugin_name"
|
|
return 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# Function to check and install all packages
|
|
install_all_packages() {
|
|
local skip_optional=${1:-false}
|
|
|
|
# Source profile manager if available
|
|
if [[ -f "$HOME/.dotfiles/lib/profile_manager.sh" ]]; then
|
|
source "$HOME/.dotfiles/lib/profile_manager.sh"
|
|
local current_profile=$(get_current_profile)
|
|
pkg_log "Starting package installation for profile: $current_profile"
|
|
echo "🔧 Checking and installing packages for profile: $current_profile"
|
|
else
|
|
pkg_log "Starting package installation process (no profile system)"
|
|
echo "🔧 Checking and installing packages..."
|
|
fi
|
|
|
|
# System packages (profile-aware)
|
|
echo "📦 Checking system packages..."
|
|
local system_packages
|
|
if command -v get_profile_packages >/dev/null 2>&1; then
|
|
system_packages=$(get_profile_packages "system")
|
|
else
|
|
system_packages="curl git vim sshuttle" # Fallback
|
|
fi
|
|
|
|
for package in $system_packages; do
|
|
case "$package" in
|
|
"sshuttle") check_cmd="which sshuttle" ;;
|
|
"curl") check_cmd="which curl" ;;
|
|
"git") check_cmd="which git" ;;
|
|
"vim") check_cmd="which vim" ;;
|
|
"docker") check_cmd="which docker" ;;
|
|
"docker-compose") check_cmd="which docker-compose" ;;
|
|
esac
|
|
|
|
if ! is_package_installed "$check_cmd"; then
|
|
echo " Installing $package..."
|
|
if install_system_package "$package"; then
|
|
echo " ✅ $package installed successfully"
|
|
else
|
|
echo " ❌ Failed to install $package"
|
|
fi
|
|
else
|
|
echo " ✅ $package already installed"
|
|
fi
|
|
done
|
|
|
|
# Binary packages (profile-aware)
|
|
echo "⚡ Checking binary packages..."
|
|
local binary_packages
|
|
if command -v get_profile_packages >/dev/null 2>&1; then
|
|
binary_packages=$(get_profile_packages "binary")
|
|
else
|
|
binary_packages="claude-code gemini-cli" # Fallback
|
|
fi
|
|
|
|
for package in $binary_packages; do
|
|
case "$package" in
|
|
"claude-code")
|
|
if ! is_package_installed "which claude"; then
|
|
echo " Installing claude-code..."
|
|
if install_binary_package "claude-code" "npm"; then
|
|
echo " ✅ claude-code installed successfully"
|
|
else
|
|
echo " ⚠️ Failed to install claude-code"
|
|
fi
|
|
else
|
|
echo " ✅ claude-code already installed"
|
|
fi
|
|
;;
|
|
"gemini-cli")
|
|
if ! is_package_installed "which gemini"; then
|
|
echo " Installing gemini-cli..."
|
|
if install_binary_package "gemini-cli" "npm"; then
|
|
echo " ✅ gemini-cli installed successfully"
|
|
else
|
|
echo " ⚠️ Failed to install gemini-cli"
|
|
fi
|
|
else
|
|
echo " ✅ gemini-cli already installed"
|
|
fi
|
|
;;
|
|
"task-master")
|
|
if ! is_package_installed "which task-master"; then
|
|
echo " Installing task-master..."
|
|
if command -v npm >/dev/null 2>&1; then
|
|
if npm install -g task-master-ai; then
|
|
echo " ✅ task-master installed successfully"
|
|
echo " 💡 Initialize with: task-master init"
|
|
else
|
|
echo " ⚠️ Failed to install task-master"
|
|
fi
|
|
else
|
|
echo " ⚠️ npm not found. Cannot install task-master"
|
|
fi
|
|
else
|
|
echo " ✅ task-master already installed"
|
|
fi
|
|
;;
|
|
esac
|
|
done
|
|
|
|
# GitHub packages (profile-aware)
|
|
echo "🐙 Checking GitHub packages..."
|
|
local github_packages
|
|
if command -v get_profile_packages >/dev/null 2>&1; then
|
|
github_packages=$(get_profile_packages "github")
|
|
else
|
|
github_packages="fzf bat ripgrep fd delta" # Fallback
|
|
fi
|
|
|
|
for package in $github_packages; do
|
|
case "$package" in
|
|
"fzf")
|
|
if ! is_package_installed "which fzf"; then
|
|
echo " Installing fzf..."
|
|
if install_github_package "fzf" "junegunn/fzf" "clone_and_install" "$HOME/.fzf" "" "" "$HOME/.fzf/install --all"; then
|
|
echo " ✅ fzf installed successfully"
|
|
else
|
|
echo " ⚠️ Failed to install fzf"
|
|
fi
|
|
else
|
|
echo " ✅ fzf already installed"
|
|
fi
|
|
;;
|
|
"bat"|"ripgrep"|"fd"|"delta")
|
|
local cmd_name="$package"
|
|
[[ "$package" == "ripgrep" ]] && cmd_name="rg"
|
|
|
|
if ! is_package_installed "which $cmd_name"; then
|
|
echo " Installing $package..."
|
|
local pattern="x86_64-unknown-linux-gnu"
|
|
if [[ "$OSTYPE" == "darwin"* ]]; then
|
|
pattern="x86_64-apple-darwin"
|
|
fi
|
|
|
|
local repo=""
|
|
case "$package" in
|
|
"bat") repo="sharkdp/bat" ;;
|
|
"ripgrep") repo="BurntSushi/ripgrep" ;;
|
|
"fd") repo="sharkdp/fd" ;;
|
|
"delta") repo="dandavison/delta" ;;
|
|
esac
|
|
|
|
if install_github_package "$package" "$repo" "release_binary" "$HOME/.local/bin" "$cmd_name" "$pattern"; then
|
|
echo " ✅ $package installed successfully"
|
|
else
|
|
echo " ⚠️ Failed to install $package"
|
|
fi
|
|
else
|
|
echo " ✅ $package already installed"
|
|
fi
|
|
;;
|
|
esac
|
|
done
|
|
|
|
# Zsh plugins (only if zsh is being used)
|
|
if [[ -d "$HOME/.oh-my-zsh" ]]; then
|
|
echo "🎨 Checking zsh plugins..."
|
|
for plugin in zsh-syntax-highlighting zsh-autosuggestions; do
|
|
case "$plugin" in
|
|
"zsh-syntax-highlighting") check_path="${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting" ;;
|
|
"zsh-autosuggestions") check_path="${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/plugins/zsh-autosuggestions" ;;
|
|
esac
|
|
|
|
if [[ ! -d "$check_path" ]]; then
|
|
echo " Installing $plugin..."
|
|
if install_zsh_plugin "$plugin"; then
|
|
echo " ✅ $plugin installed successfully"
|
|
else
|
|
echo " ❌ Failed to install $plugin"
|
|
fi
|
|
else
|
|
echo " ✅ $plugin already installed"
|
|
fi
|
|
done
|
|
fi
|
|
|
|
# Optional packages
|
|
if [[ "$skip_optional" != "true" ]]; then
|
|
echo "🔧 Checking optional packages..."
|
|
for package in docker docker-compose; do
|
|
case "$package" in
|
|
"docker") check_cmd="which docker" ;;
|
|
"docker-compose") check_cmd="which docker-compose" ;;
|
|
esac
|
|
|
|
if ! is_package_installed "$check_cmd"; then
|
|
echo " Installing optional package $package..."
|
|
if install_system_package "$package"; then
|
|
echo " ✅ $package installed successfully"
|
|
else
|
|
echo " ⚠️ Failed to install optional package $package"
|
|
fi
|
|
else
|
|
echo " ✅ $package already installed"
|
|
fi
|
|
done
|
|
fi
|
|
|
|
pkg_log "Package installation process completed"
|
|
echo "🎉 Package check and installation completed!"
|
|
} |