diff options
Diffstat (limited to 'linux/home/install.sh')
| -rwxr-xr-x | linux/home/install.sh | 2462 |
1 files changed, 0 insertions, 2462 deletions
diff --git a/linux/home/install.sh b/linux/home/install.sh deleted file mode 100755 index 60abd0b..0000000 --- a/linux/home/install.sh +++ /dev/null @@ -1,2462 +0,0 @@ -#!/usr/bin/env bash - -# Created By: srdusr -# Created On: Tue 06 Sep 2025 16:20:52 PM CAT -# Project: Dotfiles installation script - -# Dependencies: git, curl - -set -euo pipefail # Exit on error, undefined vars, pipe failures - -#====================================== -# Variables & Configuration -#====================================== - -# Color definitions for pretty UI -NOCOLOR='\033[0m' -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[0;33m' -BLUE='\033[0;34m' -MAGENTA='\033[0;35m' -CYAN='\033[0;36m' -WHITE='\033[0;37m' -BOLD='\033[1m' - -# Dotfiles configuration -DOTFILES_URL='https://github.com/srdusr/dotfiles.git' -DOTFILES_DIR="$HOME/.cfg" -LOG_FILE="$HOME/.local/share/dotfiles_install.log" -STATE_FILE="$HOME/.local/share/dotfiles_install_state" -BACKUP_DIR="$HOME/.dotfiles-backup-$(date +%Y%m%d-%H%M%S)" -PACKAGES_FILE="packages.yml" - -# Network connectivity check -CONNECTIVITY_CHECKED=false -INTERNET_AVAILABLE=false - -# Installation tracking -INSTALL_SUMMARY=() -FAILED_ITEMS=() -SKIPPED_ITEMS=() -COMPLETED_STEPS=() - -# Script options -RESUME_MODE=false -UPDATE_MODE=false -VERBOSE_MODE=false -DRY_RUN=false -FORCE_MODE=false -INSTALL_MODE="ask" # ask, essentials, full, profile - -# Global variables for system detection -CFG_OS="" -DISTRO="" -PACKAGE_MANAGER="" -PRIVILEGE_TOOL="" -PRIVILEGE_CACHED=false - -# Essential tools needed by this script -ESSENTIAL_TOOLS=("git" "curl" "wget") -PACKAGE_TOOLS=("yq" "jq") - -# Installation profiles -declare -A INSTALLATION_PROFILES=( - ["essentials"]="Essential packages only (git, curl, wget, vim, zsh)" - ["minimal"]="Minimal setup for basic development" - ["dev"]="Full development environment" - ["server"]="Server configuration" - ["full"]="Complete installation with all packages" -) - -# Installation steps configuration -declare -A INSTALLATION_STEPS=( - ["setup_environment"]="Setup installation environment" - ["check_connectivity"]="Check internet connectivity" - ["install_dependencies"]="Install dependencies" - ["install_dotfiles"]="Install dotfiles repository" - ["setup_user_dirs"]="Setup user directories" - ["install_essentials"]="Install essential tools" - ["install_packages"]="Install system packages" - ["setup_shell"]="Setup shell environment" - ["setup_ssh"]="Setup SSH configuration" - ["configure_services"]="Configure system services" - ["setup_development"]="Setup development environment" - ["apply_tweaks"]="Apply system tweaks" - ["deploy_config"]="Deploy config command and dotfiles" -) - -# Step order (important for dependencies) -STEP_ORDER=( - "setup_environment" - "check_connectivity" - "install_dependencies" - "install_dotfiles" - "setup_user_dirs" - "install_essentials" - "install_packages" - "setup_shell" - "setup_ssh" - "configure_services" - "setup_development" - "apply_tweaks" - "deploy_config" -) - -#====================================== -# State Management Functions -#====================================== - -save_state() { - local current_step="$1" - local status="$2" - - mkdir -p "$(dirname "$STATE_FILE")" - - { - echo "LAST_STEP=$current_step" - echo "STEP_STATUS=$status" - echo "TIMESTAMP=$(date +%s)" - echo "RESUME_AVAILABLE=true" - echo "PRIVILEGE_CACHED=$PRIVILEGE_CACHED" - echo "INSTALL_MODE=$INSTALL_MODE" - echo "COMPLETED_STEPS=(${COMPLETED_STEPS[*]})" - echo "CFG_OS=$CFG_OS" - echo "DISTRO=${DISTRO:-}" - echo "PACKAGE_MANAGER=${PACKAGE_MANAGER:-}" - echo "PRIVILEGE_TOOL=${PRIVILEGE_TOOL:-}" - echo "CONNECTIVITY_CHECKED=$CONNECTIVITY_CHECKED" - echo "INTERNET_AVAILABLE=$INTERNET_AVAILABLE" - } > "$STATE_FILE" -} - -load_state() { - if [[ -f "$STATE_FILE" ]]; then - source "$STATE_FILE" - return 0 - else - return 1 - fi -} - -clear_state() { - [[ -f "$STATE_FILE" ]] && rm -f "$STATE_FILE" -} - -is_step_completed() { - local step="$1" - [[ " ${COMPLETED_STEPS[*]} " =~ " ${step} " ]] -} - -mark_step_completed() { - local step="$1" - if ! is_step_completed "$step"; then - COMPLETED_STEPS+=("$step") - fi - save_state "$step" "completed" -} - -mark_step_failed() { - local step="$1" - save_state "$step" "failed" -} - -#====================================== -# UI Functions -#====================================== - -print_color() { - local color="$1" - local message="$2" - echo -e "${color}${message}${NOCOLOR}" - - if [[ -n "${LOG_FILE:-}" && -f "$LOG_FILE" ]]; then - echo "$(date +'%Y-%m-%d %H:%M:%S') - $message" >> "$LOG_FILE" - fi -} - -print_header() { - local title="$1" - local border_char="=" - local border_length=60 - - echo - print_color "$CYAN" "$(printf '%*s' $border_length '' | tr ' ' "$border_char")" - print_color "$CYAN$BOLD" "$(printf '%*s' $(((border_length + ${#title}) / 2)) "$title")" - print_color "$CYAN" "$(printf '%*s' $border_length '' | tr ' ' "$border_char")" - echo -} - -print_section() { - local title="$1" - echo - print_color "$BLUE$BOLD" "▶ $title" - print_color "$BLUE" "$(printf '%*s' $((${#title} + 2)) '' | tr ' ' '-')" -} - -print_success() { - local message="$1" - print_color "$GREEN" "✓ $message" - INSTALL_SUMMARY+=("✓ $message") -} - -print_error() { - local message="$1" - print_color "$RED" "✗ $message" >&2 - FAILED_ITEMS+=("✗ $message") -} - -print_warning() { - local message="$1" - print_color "$YELLOW" "⚠ $message" -} - -print_info() { - local message="$1" - if [[ "$VERBOSE_MODE" == true ]] || [[ "${2:-}" == "always" ]]; then - print_color "$CYAN" "ℹ $message" - fi -} - -print_skip() { - local message="$1" - print_color "$YELLOW" "⏭ $message" - SKIPPED_ITEMS+=("⏭ $message") -} - -print_dry_run() { - local message="$1" - print_color "$MAGENTA" "[DRY RUN] $message" -} - -#====================================== -# Network Connectivity Functions -#====================================== - -check_internet_connectivity() { - if [[ "$CONNECTIVITY_CHECKED" == true ]]; then - return $([[ "$INTERNET_AVAILABLE" == true ]] && echo 0 || echo 1) - fi - - print_section "Checking Internet Connectivity" - - local test_urls=("8.8.8.8" "1.1.1.1" "google.com" "github.com") - - for url in "${test_urls[@]}"; do - if ping -c 1 -W 2 "$url" &>/dev/null || curl -s --connect-timeout 5 "https://$url" &>/dev/null; then - INTERNET_AVAILABLE=true - CONNECTIVITY_CHECKED=true - print_success "Internet connectivity confirmed" - return 0 - fi - done - - INTERNET_AVAILABLE=false - CONNECTIVITY_CHECKED=true - print_error "No internet connectivity detected" - - # Try to connect to WiFi or prompt user - attempt_network_connection - - return 1 -} - -attempt_network_connection() { - print_warning "Attempting to establish network connection..." - - # Try NetworkManager - if command_exists nmcli; then - print_info "Available WiFi networks:" - nmcli device wifi list 2>/dev/null || print_warning "Could not list WiFi networks" - - if prompt_user "Would you like to connect to a WiFi network?"; then - print_color "$YELLOW" "Enter WiFi network name (SSID): " - read -r wifi_ssid - if [[ -n "$wifi_ssid" ]]; then - print_color "$YELLOW" "Enter WiFi password: " - read -rs wifi_password - echo - - if execute_with_privilege "nmcli device wifi connect '$wifi_ssid' password '$wifi_password'"; then - print_success "Connected to WiFi network: $wifi_ssid" - # Re-check connectivity - CONNECTIVITY_CHECKED=false - check_internet_connectivity - return $? - else - print_error "Failed to connect to WiFi network" - fi - fi - fi - fi - - # Try other connection methods - if command_exists iwctl; then - print_info "You can also connect manually using iwctl" - fi - - return 1 -} - -#====================================== -# System Detection Functions -#====================================== - -detect_os() { - case "$(uname -s)" in - Linux) CFG_OS="linux" ;; - Darwin) CFG_OS="macos" ;; - MINGW*|MSYS*|CYGWIN*) CFG_OS="windows" ;; - *) CFG_OS="unknown" ;; - esac - - print_info "Detected OS: $CFG_OS" "always" -} - -detect_privilege_tools() { - if [[ "$(id -u)" -eq 0 ]]; then - PRIVILEGE_TOOL="" - print_info "Running as root, no privilege escalation needed" - return 0 - fi - - for tool in sudo doas pkexec; do - if command -v "$tool" &>/dev/null; then - PRIVILEGE_TOOL="$tool" - print_success "Using privilege escalation tool: $PRIVILEGE_TOOL" - return 0 - fi - done - - print_warning "No privilege escalation tool found (sudo, doas, pkexec)" - PRIVILEGE_TOOL="" - return 1 -} - -test_privilege_access() { - if [[ "$PRIVILEGE_CACHED" == true ]]; then - return 0 - fi - - if [[ -z "$PRIVILEGE_TOOL" ]]; then - return 0 # Running as root or no privilege needed - fi - - print_info "Testing privilege access..." - if "$PRIVILEGE_TOOL" -v &>/dev/null || echo "test" | "$PRIVILEGE_TOOL" -S true &>/dev/null; then - PRIVILEGE_CACHED=true - print_success "Privilege access confirmed" - return 0 - else - print_error "Failed to obtain privilege access" - return 1 - fi -} - -detect_package_manager() { - # First try to detect from OS release files - if [[ "$CFG_OS" == "linux" && -f /etc/os-release ]]; then - source /etc/os-release - case "$ID" in - arch|manjaro|endeavouros|artix) - DISTRO="$ID" - PACKAGE_MANAGER="pacman" ;; - debian|ubuntu|mint|pop|elementary|zorin) - DISTRO="$ID" - PACKAGE_MANAGER="apt" ;; - fedora|rhel|centos|rocky|almalinux) - DISTRO="$ID" - PACKAGE_MANAGER="dnf" ;; - opensuse*|sles) - DISTRO="$ID" - PACKAGE_MANAGER="zypper" ;; - gentoo|funtoo) - DISTRO="$ID" - PACKAGE_MANAGER="portage" ;; - alpine) - DISTRO="$ID" - PACKAGE_MANAGER="apk" ;; - void) - DISTRO="$ID" - PACKAGE_MANAGER="xbps" ;; - nixos) - DISTRO="$ID" - PACKAGE_MANAGER="nix" ;; - *) - print_warning "Unknown distribution: $ID, trying to detect package manager directly" - ;; - esac - elif [[ "$CFG_OS" == "macos" ]]; then - DISTRO="macos" - if command -v brew &>/dev/null; then - PACKAGE_MANAGER="brew" - else - PACKAGE_MANAGER="brew-install" # Will install homebrew - fi - fi - - # Fallback: detect by available commands - if [[ -z "$PACKAGE_MANAGER" ]]; then - local managers=( - "pacman:pacman" - "apt:apt" - "dnf:dnf" - "yum:yum" - "zypper:zypper" - "emerge:portage" - "apk:apk" - "xbps-install:xbps" - "nix-env:nix" - "pkg:pkg" - "brew:brew" - ) - - for manager in "${managers[@]}"; do - local cmd="${manager%:*}" - local name="${manager#*:}" - if command -v "$cmd" &>/dev/null; then - PACKAGE_MANAGER="$name" - break - fi - done - fi - - if [[ -n "$PACKAGE_MANAGER" ]]; then - print_success "Detected package manager: $PACKAGE_MANAGER" - [[ -n "$DISTRO" ]] && print_info "Distribution: $DISTRO" - return 0 - else - print_error "Could not detect package manager" - return 1 - fi -} - -#====================================== -# Utility Functions -#====================================== - -command_exists() { - command -v "$1" &>/dev/null -} - -execute_command() { - local cmd="$*" - - if [[ "$DRY_RUN" == true ]]; then - print_dry_run "$cmd" - return 0 - fi - - if [[ "$VERBOSE_MODE" == true ]]; then - print_info "Running: $cmd" - fi - - eval "$cmd" -} - -execute_with_privilege() { - local cmd="$*" - - if [[ "$DRY_RUN" == true ]]; then - if [[ -n "$PRIVILEGE_TOOL" ]]; then - print_dry_run "$PRIVILEGE_TOOL $cmd" - else - print_dry_run "$cmd" - fi - return 0 - fi - - if [[ -n "$PRIVILEGE_TOOL" ]]; then - if [[ "$PRIVILEGE_CACHED" != true ]]; then - test_privilege_access || return 1 - fi - eval "$PRIVILEGE_TOOL $cmd" - else - eval "$cmd" - fi -} - -prompt_user() { - local question="$1" - local default="${2:-Y}" - local response - - if [[ "$FORCE_MODE" == true ]]; then - print_info "Auto-answering '$question' with: $default" - [[ "$default" =~ ^[Yy] ]] && return 0 || return 1 - fi - - while true; do - if [[ "$default" == "Y" ]]; then - print_color "$YELLOW" "$question [Y/n]: " - else - print_color "$YELLOW" "$question [y/N]: " - fi - - read -r response - - if [[ -z "$response" ]]; then - response="$default" - fi - - case "${response^^}" in - Y|YES) return 0 ;; - N|NO) return 1 ;; - *) print_warning "Please answer Y/yes or N/no" ;; - esac - done -} - -create_dir() { - local dir="$1" - local permissions="${2:-755}" - - if [[ "$DRY_RUN" == true ]]; then - print_dry_run "Create directory: $dir (mode: $permissions)" - return 0 - fi - - if [[ ! -d "$dir" ]]; then - mkdir -p "$dir" || { - print_error "Failed to create directory: $dir" - return 1 - } - chmod "$permissions" "$dir" - print_success "Created directory: $dir" - else - print_info "Directory already exists: $dir" - fi -} - -setup_logging() { - local log_dir - log_dir="$(dirname "$LOG_FILE")" - - if [[ ! -d "$log_dir" ]]; then - mkdir -p "$log_dir" || { - print_error "Failed to create log directory: $log_dir" - exit 1 - } - fi - - { - echo "=======================================" - echo "Dotfiles Installation Log" - echo "Date: $(date)" - echo "User: $USER" - echo "Host: ${HOSTNAME:-$(hostname)}" - echo "OS: $(uname -s)" - echo "Install Mode: $INSTALL_MODE" - echo "=======================================" - echo - } > "$LOG_FILE" - - print_info "Log file initialized: $LOG_FILE" "always" -} - -get_package_name() { - local package="$1" - local packages_file="${2:-}" - - # If packages.yml is available, check for distribution-specific mappings - if [[ -n "$packages_file" ]] && [[ -f "$packages_file" ]] && command_exists yq; then - local distro_package="" - - # Try to get package name for current distribution - case "$DISTRO" in - arch|manjaro|endeavouros|artix) - distro_package=$(yq eval ".arch.$package" "$packages_file" 2>/dev/null | grep -v "^null$" || echo "") - ;; - debian|ubuntu|mint|pop|elementary|zorin) - distro_package=$(yq eval ".debian.$package" "$packages_file" 2>/dev/null | grep -v "^null$" || echo "") - ;; - fedora|rhel|centos|rocky|almalinux) - distro_package=$(yq eval ".rhel.$package" "$packages_file" 2>/dev/null | grep -v "^null$" || echo "") - ;; - opensuse*|sles) - distro_package=$(yq eval ".opensuse.$package" "$packages_file" 2>/dev/null | grep -v "^null$" || echo "") - ;; - gentoo|funtoo) - distro_package=$(yq eval ".gentoo.$package" "$packages_file" 2>/dev/null | grep -v "^null$" || echo "") - ;; - macos) - distro_package=$(yq eval ".macos[]" "$packages_file" 2>/dev/null | grep "^$package$" || echo "") - ;; - esac - - # Return the distribution-specific package name if found - if [[ -n "$distro_package" ]]; then - echo "$distro_package" - return 0 - fi - fi - - # Fallback to original package name - echo "$package" -} - -get_package_use_flags() { - local package="$1" - local packages_file="${2:-}" - - # Only relevant for Gentoo/Portage - if [[ "$PACKAGE_MANAGER" != "portage" ]]; then - echo "" - return 0 - fi - - if [[ -n "$packages_file" ]] && [[ -f "$packages_file" ]] && command_exists yq; then - local use_flags - use_flags=$(yq eval ".gentoo_use_flags.$package" "$packages_file" 2>/dev/null | grep -v "^null$" || echo "") - echo "$use_flags" - else - echo "" - fi -} - -#====================================== -# Dependency Installation Functions -#====================================== - -install_dependencies_if_missing() { - print_section "Checking for dependencies git, wget/curl" - save_state "install_dependencies" "started" - - local missing_deps=() - local failed_deps=() - - # Check for missing essential tools - for tool in "${ESSENTIAL_TOOLS[@]}"; do - if ! command_exists "$tool"; then - missing_deps+=("$tool") - fi - done - - # If no internet and dependencies are missing, try offline packages - if [[ "$INTERNET_AVAILABLE" != true ]] && [[ ${#missing_deps[@]} -gt 0 ]]; then - print_warning "No internet connection available" - print_info "Attempting to install dependencies from local packages..." - - # Try to install from local package cache - for tool in "${missing_deps[@]}"; do - if install_package_offline "$tool"; then - print_success "Installed $tool from local cache" - else - failed_deps+=("$tool") - fi - done - elif [[ ${#missing_deps[@]} -gt 0 ]]; then - # Online installation - print_info "Installing missing dependencies: ${missing_deps[*]}" - update_package_database - - for tool in "${missing_deps[@]}"; do - if install_single_package "$tool" "dependency"; then - print_success "Installed dependency: $tool" - else - failed_deps+=("$tool") - fi - done - fi - - if [[ ${#failed_deps[@]} -gt 0 ]]; then - print_error "Failed to install dependencies: ${failed_deps[*]}" - mark_step_failed "install_dependencies" - return 1 - else - mark_step_completed "install_dependencies" - return 0 - fi -} - -install_package_offline() { - local package="$1" - - case "$PACKAGE_MANAGER" in - pacman) - # Check if package is in cache - if execute_with_privilege "pacman -U /var/cache/pacman/pkg/${package}-*.pkg.tar.*" 2>/dev/null; then - return 0 - fi - ;; - apt) - # Try from local cache - if execute_with_privilege "apt-get install --no-download '$package'" 2>/dev/null; then - return 0 - fi - ;; - esac - - return 1 -} - -#====================================== -# Package Management Functions -#====================================== - -install_single_package() { - local package="$1" - local package_type="${2:-system}" - local packages_file="${3:-}" - - # Get the correct package name for this distro - local pkg_name - pkg_name=$(get_package_name "$package" "$packages_file") - - # Get USE flags for Gentoo - local use_flags - use_flags=$(get_package_use_flags "$package" "$packages_file") - - print_info "Installing $package_type package: $pkg_name" - - case "$PACKAGE_MANAGER" in - pacman) - execute_with_privilege "pacman -S --noconfirm '$pkg_name'" ;; - apt) - execute_with_privilege "apt-get install -y '$pkg_name'" ;; - dnf) - execute_with_privilege "dnf install -y '$pkg_name'" ;; - yum) - execute_with_privilege "yum install -y '$pkg_name'" ;; - zypper) - execute_with_privilege "zypper install -y '$pkg_name'" ;; - portage) - local emerge_cmd="emerge" - if [[ -n "$use_flags" ]]; then - emerge_cmd="USE='$use_flags' emerge" - print_info "Using USE flags for $pkg_name: $use_flags" - fi - execute_with_privilege "$emerge_cmd '$pkg_name'" ;; - apk) - execute_with_privilege "apk add '$pkg_name'" ;; - xbps) - execute_with_privilege "xbps-install -y '$pkg_name'" ;; - nix) - execute_command "nix-env -iA nixpkgs.$pkg_name" ;; - brew) - execute_command "brew install '$pkg_name'" ;; - brew-install) - print_error "Homebrew not installed. Please install it first." - return 1 ;; - *) - print_error "Package manager '$PACKAGE_MANAGER' not supported" - return 1 ;; - esac -} - -update_package_database() { - print_info "Updating package database..." - - case "$PACKAGE_MANAGER" in - pacman) - execute_with_privilege "pacman -Sy" ;; - apt) - execute_with_privilege "apt-get update" ;; - dnf) - execute_with_privilege "dnf check-update" || true ;; - yum) - execute_with_privilege "yum check-update" || true ;; - zypper) - execute_with_privilege "zypper refresh" ;; - portage) - execute_with_privilege "emerge --sync" ;; - apk) - execute_with_privilege "apk update" ;; - xbps) - execute_with_privilege "xbps-install -S" ;; - brew) - execute_command "brew update" ;; - *) - print_info "Package database update not needed for $PACKAGE_MANAGER" ;; - esac -} - -install_homebrew() { - if command_exists brew; then - print_info "Homebrew already installed" - return 0 - fi - - print_info "Installing Homebrew..." - if execute_command '/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"'; then - print_success "Homebrew installed" - PACKAGE_MANAGER="brew" - - # Add to PATH for current session - if [[ -f "/opt/homebrew/bin/brew" ]]; then - eval "$(/opt/homebrew/bin/brew shellenv)" - elif [[ -f "/usr/local/bin/brew" ]]; then - eval "$(/usr/local/bin/brew shellenv)" - fi - return 0 - else - print_error "Failed to install Homebrew" - return 1 - fi -} - -install_yq() { - if command_exists yq; then - print_info "yq already installed" - return 0 - fi - - print_info "Installing yq..." - - local bin_dir="$HOME/.local/bin" - create_dir "$bin_dir" - - local yq_path="$bin_dir/yq" - local yq_url="" - - case "$(uname -m)" in - x86_64|amd64) - yq_url="https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64" ;; - aarch64|arm64) - yq_url="https://github.com/mikefarah/yq/releases/latest/download/yq_linux_arm64" ;; - armv7l) - yq_url="https://github.com/mikefarah/yq/releases/latest/download/yq_linux_arm" ;; - *) - print_error "Unsupported architecture: $(uname -m)" - return 1 ;; - esac - - if execute_command "curl -L '$yq_url' -o '$yq_path'"; then - execute_command "chmod +x '$yq_path'" - - # Add to PATH if not already there - if [[ ":$PATH:" != *":$bin_dir:"* ]]; then - export PATH="$bin_dir:$PATH" - fi - - print_success "yq installed successfully" - return 0 - else - print_error "Failed to install yq" - return 1 - fi -} - -parse_packages_from_yaml() { - local packages_file="$1" - local section="$2" - local packages=() - - if [[ ! -f "$packages_file" ]]; then - print_warning "Package file not found: $packages_file" - return 1 - fi - - if ! command_exists yq; then - print_error "yq not available for parsing packages.yml" - return 1 - fi - - # Try to parse packages from the specified section - if yq eval ".$section" "$packages_file" &>/dev/null; then - mapfile -t packages < <(yq eval ".$section[]" "$packages_file" 2>/dev/null | grep -v "^null$" || true) - fi - - # Output packages - printf '%s\n' "${packages[@]}" -} - -install_packages_from_yaml() { - local packages_file="$1" - local profile="${2:-essentials}" - local failed_packages=() - local installed_count=0 - - print_section "Installing Packages (Profile: $profile)" - - if [[ ! -f "$packages_file" ]]; then - print_warning "Package file not found: $packages_file, skipping package installation" - return 0 - fi - - # Define sections to install based on profile - local sections=() - case "$profile" in - essentials) - sections=("common" "essentials") ;; - minimal) - sections=("common" "essentials" "minimal") ;; - dev) - sections=("common" "essentials" "dev") ;; - server) - sections=("common" "essentials" "server") ;; - full) - sections=("common" "essentials" "dev" "server" "desktop") ;; - *) - if [[ -f "profiles/$profile.yml" ]]; then - packages_file="profiles/$profile.yml" - sections=("packages") - else - print_error "Unknown profile: $profile" - return 1 - fi - ;; - esac - - # Install packages from each section - for section in "${sections[@]}"; do - print_info "Installing packages from section: $section" - - local packages - mapfile -t packages < <(parse_packages_from_yaml "$packages_file" "$section") - - if [[ ${#packages[@]} -eq 0 ]]; then - print_info "No packages found in section: $section" - continue - fi - - print_info "Found ${#packages[@]} packages in section $section" - - for package in "${packages[@]}"; do - [[ -z "$package" ]] && continue - - if install_single_package "$package" "$section" "$packages_file"; then - print_success "Installed: $package" - ((installed_count++)) - else - print_error "Failed to install: $package" - failed_packages+=("$package") - fi - done - done - - print_info "Package installation summary:" - print_color "$GREEN" " Installed: $installed_count" - print_color "$RED" " Failed: ${#failed_packages[@]}" - - if [[ ${#failed_packages[@]} -gt 0 ]]; then - print_warning "Failed packages: ${failed_packages[*]}" - print_info "Failed packages will be listed in the final summary" - return 0 - else - print_success "All packages installed successfully" - return 0 - fi -} - -#====================================== -# Dotfiles Management System (Config Command) -#====================================== - -install_config_command() { - print_info "Installing config command for dotfiles management" - - # Known function files where cfg might already be defined - local function_files=( - "$HOME/.config/zsh/user/functions.zsh" - "$HOME/.bashrc" - ) - - # Check if cfg is already defined - local cfg_defined=false - for f in "${function_files[@]}"; do - if [[ -f "$f" ]] && grep -q '^\s*cfg\s*()' "$f"; then - cfg_defined=true - # Source the file to make cfg available in current session - # Only source if not already sourced - if ! type cfg >/dev/null 2>&1; then - # shellcheck disable=SC1090 - source "$f" - print_info "Sourced cfg from $f" - fi - break - fi - done - - if [[ "$cfg_defined" == true ]]; then - print_info "cfg function already defined, no need to append" - return - fi - - # Determine current shell - local current_shell - current_shell=$(basename "$SHELL") - - local profile_files=() - - case "$current_shell" in - bash) - profile_files+=("$HOME/.bashrc") - [[ -f "$HOME/.profile" ]] && profile_files+=("$HOME/.profile") - ;; - zsh) - profile_files+=("$HOME/.zshrc") - [[ -f "$HOME/.config/zsh/.zshrc" ]] && profile_files+=("$HOME/.config/zsh/.zshrc") - [[ -f "$HOME/.profile" ]] && profile_files+=("$HOME/.profile") - ;; - *) - [[ -f "$HOME/.profile" ]] && profile_files+=("$HOME/.profile") - ;; - esac - - # If no profile files exist, create .bashrc - if [[ ${#profile_files[@]} -eq 0 ]]; then - profile_files+=("$HOME/.bashrc") - touch "$HOME/.bashrc" - fi - - # Append cfg function to profiles if not already present - for profile in "${profile_files[@]}"; do - if [[ -w "$profile" ]] && ! grep -q "# Dotfiles config function" "$profile" 2>/dev/null; then - cat >> "$profile" << 'EOF' - -# Dotfiles Management System -if [[ -d "$HOME/.cfg" && -d "$HOME/.cfg/refs" ]]; then - # Core git wrapper with repository as work-tree - _config() { - git --git-dir="$HOME/.cfg" --work-tree="$HOME/.cfg" "$@" - } - - # Detect OS - case "$(uname -s)" in - Linux) CFG_OS="linux" ;; - Darwin) CFG_OS="macos" ;; - MINGW*|MSYS*|CYGWIN*) CFG_OS="windows" ;; - *) CFG_OS="other" ;; - esac - - # Map system path to repository path - _repo_path() { - local f="$1" - - # If it's an absolute path that's not in HOME, handle it specially - if [[ "$f" == /* && "$f" != "$HOME/"* ]]; then - echo "$CFG_OS/${f#/}" - return - fi - - # Check for paths that should go to the repository root - case "$f" in - common/*|linux/*|macos/*|windows/*|profile/*|README.md) - echo "$f" - return - ;; - "$HOME/"*) - f="${f#$HOME/}" - ;; - esac - - # Default: put under OS-specific home - echo "$CFG_OS/home/$f" - } - - _sys_path() { - local repo_path="$1" - local os_path_pattern="$CFG_OS/" - - # Handle OS-specific files that are not in the home subdirectory - if [[ "$repo_path" == "$os_path_pattern"* && "$repo_path" != */home/* ]]; then - echo "/${repo_path#$os_path_pattern}" - return - fi - - case "$repo_path" in - # Common configs → OS-specific config dirs - common/config/*) - case "$CFG_OS" in - linux) - local base="${XDG_CONFIG_HOME:-$HOME/.config}" - echo "$base/${repo_path#common/config/}" - ;; - macos) - echo "$HOME/Library/Application Support/${repo_path#common/config/}" - ;; - windows) - echo "$LOCALAPPDATA\\${repo_path#common/config/}" - ;; - *) - echo "$HOME/.config/${repo_path#common/config/}" - ;; - esac - ;; - - # Common assets → stay in repo - common/assets/*) - echo "$HOME/.cfg/$repo_path" - ;; - - # Other common files (dotfiles like .bashrc, .gitconfig, etc.) → $HOME - common/*) - echo "$HOME/${repo_path#common/}" - ;; - - # OS-specific home - */home/*) - echo "$HOME/${repo_path#*/home/}" - ;; - - # Profile configs and README → stay in repo - profile/*|README.md) - echo "$HOME/.cfg/$repo_path" - ;; - - # Default fallback - *) - echo "$HOME/.cfg/$repo_path" - ;; - - esac - } - - # Prompts for sudo if needed and runs the command - _sudo_prompt() { - if [[ $EUID -eq 0 ]]; then - "$@" - else - if command -v sudo >/dev/null; then - sudo "$@" - elif command -v doas >/dev/null; then - doas "$@" - elif command -v pkexec >/dev/null; then - pkexec "$@" - else - echo "Error: No privilege escalation tool found." - return 1 - fi - fi - } - - # Main config command - config() { - local cmd="$1"; shift - local target_dir="" - # Parse optional --target flag for add - if [[ "$cmd" == "add" ]]; then - while [[ "$1" == --* ]]; do - case "$1" in - --target|-t) - target_dir="$2" - shift 2 - ;; - *) - echo "Unknown option: $1" - return 1 - ;; - esac - done - fi - - case "$cmd" in - add) - local file_path - for file_path in "$@"; do - local repo_path - if [[ -n "$target_dir" ]]; then - local rel_path - if [[ "$file_path" == /* ]]; then - rel_path="$(basename "$file_path")" - else - rel_path="$file_path" - fi - repo_path="$target_dir/$rel_path" - else - repo_path="$(_repo_path "$file_path")" - fi - - local full_repo_path="$HOME/.cfg/$repo_path" - mkdir -p "$(dirname "$full_repo_path")" - cp -a "$file_path" "$full_repo_path" - - git --git-dir="$HOME/.cfg" --work-tree="$HOME/.cfg" add "$repo_path" - - echo "Added: $file_path -> $repo_path" - done - ;; - rm) - local rm_opts="" - local file_path_list=() - - for arg in "$@"; do - if [[ "$arg" == "-"* ]]; then - rm_opts+=" $arg" - else - file_path_list+=("$arg") - fi - done - - for file_path in "${file_path_list[@]}"; do - local repo_path="$(_repo_path "$file_path")" - - if [[ "$rm_opts" == *"-r"* ]]; then - _config rm --cached -r "$repo_path" - else - _config rm --cached "$repo_path" - fi - - eval "rm $rm_opts \"$file_path\"" - echo "Removed: $file_path" - done - ;; - sync) - local direction="${1:-to-repo}"; shift - _config ls-files | while read -r repo_file; do - local sys_file="$(_sys_path "$repo_file")" - local full_repo_path="$HOME/.cfg/$repo_file" - if [[ "$direction" == "to-repo" ]]; then - if [[ -e "$sys_file" && -n "$(diff "$full_repo_path" "$sys_file" 2>/dev/null || echo "diff")" ]]; then - cp -a "$sys_file" "$full_repo_path" - echo "Synced to repo: $sys_file" - fi - elif [[ "$direction" == "from-repo" ]]; then - if [[ -e "$full_repo_path" && -n "$(diff "$full_repo_path" "$sys_file" 2>/dev/null || echo "diff")" ]]; then - local dest_dir="$(dirname "$sys_file")" - if [[ "$sys_file" == /* && "$sys_file" != "$HOME/"* ]]; then - _sudo_prompt mkdir -p "$dest_dir" - _sudo_prompt cp -a "$full_repo_path" "$sys_file" - else - mkdir -p "$dest_dir" - cp -a "$full_repo_path" "$sys_file" - fi - echo "Synced from repo: $sys_file" - fi - fi - done - ;; - status) - local auto_synced=() - while read -r repo_file; do - local sys_file="$(_sys_path "$repo_file")" - local full_repo_path="$HOME/.cfg/$repo_file" - if [[ -e "$sys_file" && -e "$full_repo_path" ]]; then - if ! diff -q "$full_repo_path" "$sys_file" >/dev/null 2>&1; then - cp -fa "$sys_file" "$full_repo_path" - auto_synced+=("$repo_file") - fi - fi - done < <(_config ls-files) - if [[ ${#auto_synced[@]} -gt 0 ]]; then - echo "=== Auto-synced Files ===" - for repo_file in "${auto_synced[@]}"; do - echo "synced: $(_sys_path "$repo_file") -> $repo_file" - done - echo - fi - _config status - echo - ;; - deploy) - _config ls-files | while read -r repo_file; do - local full_repo_path="$HOME/.cfg/$repo_file" - local sys_file="$(_sys_path "$repo_file")" # destination only - - # Only continue if the source exists - if [[ -e "$full_repo_path" && -n "$sys_file" ]]; then - local dest_dir - dest_dir="$(dirname "$sys_file")" - - # Create destination if needed - if [[ "$sys_file" == /* && "$sys_file" != "$HOME/"* ]]; then - _sudo_prompt mkdir -p "$dest_dir" - _sudo_prompt cp -a "$full_repo_path" "$sys_file" - else - mkdir -p "$dest_dir" - cp -a "$full_repo_path" "$sys_file" - fi - - echo "Deployed: $repo_file -> $sys_file" - fi - done - ;; - checkout) - echo "Checking out dotfiles from .cfg..." - _config ls-files | while read -r repo_file; do - local full_repo_path="$HOME/.cfg/$repo_file" - local sys_file="$(_sys_path "$repo_file")" - - if [[ -e "$full_repo_path" && -n "$sys_file" ]]; then - local dest_dir - dest_dir="$(dirname "$sys_file")" - - # Create destination if it doesn't exist - if [[ "$sys_file" == /* && "$sys_file" != "$HOME/"* ]]; then - _sudo_prompt mkdir -p "$dest_dir" - _sudo_prompt cp -a "$full_repo_path" "$sys_file" - else - mkdir -p "$dest_dir" - cp -a "$full_repo_path" "$sys_file" - fi - - echo "Checked out: $repo_file -> $sys_file" - fi - done - ;; - backup) - local timestamp=$(date +%Y%m%d%H%M%S) - local backup_dir="$HOME/.dotfiles_backup/$timestamp" - echo "Backing up existing dotfiles to $backup_dir..." - - _config ls-files | while read -r repo_file; do - local sys_file="$(_sys_path "$repo_file")" - if [[ -e "$sys_file" ]]; then - local dest_dir_full="$backup_dir/$(dirname "$repo_file")" - mkdir -p "$dest_dir_full" - cp -a "$sys_file" "$backup_dir/$repo_file" - fi - done - echo "Backup complete. To restore, copy files from $backup_dir to their original locations." - ;; - *) - _config "$cmd" "$@" - ;; - esac - } -fi -EOF - print_success "Added config function to $profile" - else - print_info "Config function already exists in $profile or file not writable" - fi - done - - return 0 -} - - -deploy_config() { - print_section "Deploying Configuration" - save_state "deploy_config" "started" - - # Install and setup the config command first - install_config_command - - # Deploy dotfiles from repository to system - if [[ -d "$DOTFILES_DIR" ]]; then - print_info "Deploying dotfiles from repository to system locations..." - - # Source shell configuration to make config function available - reload_shell_config - - # Check if config function is available - if declare -f config >/dev/null 2>&1 || type config >/dev/null 2>&1; then - print_info "Config function available, deploying files..." - - if [[ "$DRY_RUN" == true ]]; then - print_dry_run "config restore ." - print_dry_run "config reset" - print_dry_run "config deploy" - else - # Use the config function to deploy files - if config deploy; then - print_success "Dotfiles deployed successfully" - else - print_warning "Some files may have failed to deploy" - fi - fi - else - print_info "Config function not available, using manual deployment..." - manual_deploy_dotfiles - fi - - # Set appropriate permissions - set_dotfile_permissions - - else - print_warning "Dotfiles directory not found, skipping deployment" - fi - - mark_step_completed "deploy_config" -} - -reload_shell_config() { - print_info "Reloading shell configuration..." - - # Source common shell files if they exist - local shell_files=() - - case "$(basename "$SHELL")" in - bash) - shell_files+=("$HOME/.bashrc" "$HOME/.profile") - ;; - zsh) - shell_files+=("$HOME/.zshrc" "$HOME/.config/zsh/.zshrc" "$HOME/.profile") - ;; - *) - shell_files+=("$HOME/.profile") - ;; - esac - - for shell_file in "${shell_files[@]}"; do - if [[ -f "$shell_file" ]]; then - print_info "Sourcing: $shell_file" - # shellcheck disable=SC1090 - source "$shell_file" 2>/dev/null || print_warning "Failed to source $shell_file" - fi - done -} - -#====================================== -# Installation Step Functions -#====================================== - -setup_environment() { - print_section "Setting Up Environment" - save_state "setup_environment" "started" - - detect_os - detect_privilege_tools - detect_package_manager || { - print_error "Cannot proceed without a supported package manager" - mark_step_failed "setup_environment" - return 1 - } - - if [[ -n "$PRIVILEGE_TOOL" ]]; then - test_privilege_access || { - print_error "Cannot obtain necessary privileges" - mark_step_failed "setup_environment" - return 1 - } - fi - - mark_step_completed "setup_environment" -} - -check_connectivity() { - print_section "Checking Connectivity" - save_state "check_connectivity" "started" - - if check_internet_connectivity; then - mark_step_completed "check_connectivity" - return 0 - else - print_warning "Limited internet connectivity - some features may be unavailable" - mark_step_completed "check_connectivity" - return 0 # Don't fail completely - fi -} - -install_dependencies() { - print_section "Installing Dependencies" - save_state "install_dependencies" "started" - - if install_dependencies_if_missing; then - mark_step_completed "install_dependencies" - return 0 - else - mark_step_failed "install_dependencies" - return 1 - fi -} - -install_dotfiles() { - print_section "Installing Dotfiles" - save_state "install_dotfiles" "started" - - local update=false - - # Check internet connectivity for git operations - if [[ "$INTERNET_AVAILABLE" != true ]]; then - print_warning "No internet connectivity - skipping dotfiles installation" - mark_step_completed "install_dotfiles" - return 0 - fi - - if [[ -d "$DOTFILES_DIR" ]]; then - if [[ "$UPDATE_MODE" == true ]] || prompt_user "Dotfiles repository already exists. Update it?"; then - print_info "Updating existing dotfiles..." - if execute_command "git --git-dir='$DOTFILES_DIR' --work-tree='$HOME/.cfg' pull origin main"; then - update=true - print_success "Dotfiles updated successfully" - else - print_error "Failed to pull updates" - mark_step_failed "install_dotfiles" - return 1 - fi - else - print_skip "Skipping dotfiles update" - mark_step_completed "install_dotfiles" - return 0 - fi - else - print_info "Cloning dotfiles repository..." - if execute_command "git clone --bare '$DOTFILES_URL' '$DOTFILES_DIR'"; then - print_success "Dotfiles repository cloned" - else - print_error "Failed to clone dotfiles repository" - mark_step_failed "install_dotfiles" - return 1 - fi - fi - - # Configure the repository - execute_command "git --git-dir='$DOTFILES_DIR' --work-tree='$HOME/.cfg' config status.showUntrackedFiles no" - - mark_step_completed "install_dotfiles" - print_success "Dotfiles installed successfully" -} - -setup_user_dirs() { - print_section "Setting Up User Directories" - save_state "setup_user_dirs" "started" - - local directories=('.cache' '.config' '.local/bin' '.local/share' '.scripts') - - for dir in "${directories[@]}"; do - create_dir "$HOME/$dir" - done - - # Set up XDG directories - if command_exists xdg-user-dirs-update; then - execute_command "xdg-user-dirs-update" - print_success "XDG user directories configured" - fi - - mark_step_completed "setup_user_dirs" -} - -install_essentials() { - print_section "Installing Essential Tools" - save_state "install_essentials" "started" - - # Install package processing tools first - for tool in "${PACKAGE_TOOLS[@]}"; do - if ! command_exists "$tool"; then - case "$tool" in - yq) - if install_yq; then - print_success "Installed package tool: $tool" - else - print_error "Failed to install package tool: $tool" - mark_step_failed "install_essentials" - return 1 - fi - ;; - jq) - if install_single_package "jq" "essential"; then - print_success "Installed package tool: $tool" - else - print_error "Failed to install package tool: $tool" - mark_step_failed "install_essentials" - return 1 - fi - ;; - esac - else - print_info "Package tool already available: $tool" - fi - done - - mark_step_completed "install_essentials" -} - -install_packages() { - print_section "Installing Packages" - save_state "install_packages" "started" - - # Skip if essentials-only mode - if [[ "$INSTALL_MODE" == "essentials" ]]; then - print_skip "Package installation (essentials-only mode)" - mark_step_completed "install_packages" - return 0 - fi - - # Skip if no internet and packages require download - if [[ "$INTERNET_AVAILABLE" != true ]]; then - print_warning "No internet connectivity - skipping package installation" - mark_step_completed "install_packages" - return 0 - fi - - # Determine profile to install - local profile="$INSTALL_MODE" - if [[ "$INSTALL_MODE" == "ask" ]]; then - profile="dev" # Default - fi - - # Change to home directory to find packages.yml - local original_dir="$PWD" - cd "$HOME" 2>/dev/null || true - - # Look for packages.yml in common locations - local packages_files=("$PACKAGES_FILE" "common/$PACKAGES_FILE" ".cfg/common/$PACKAGES_FILE") - local found_packages_file="" - - for pf in "${packages_files[@]}"; do - if [[ -f "$pf" ]]; then - found_packages_file="$pf" - break - fi - done - - if [[ -n "$found_packages_file" ]]; then - if install_packages_from_yaml "$found_packages_file" "$profile"; then - mark_step_completed "install_packages" - else - print_warning "Some packages failed to install, but continuing..." - mark_step_completed "install_packages" # Don't fail the whole installation - fi - else - print_warning "packages.yml not found, skipping package installation" - mark_step_completed "install_packages" - fi - - cd "$original_dir" 2>/dev/null || true -} - -setup_shell() { - print_section "Setting Up Shell Environment" - save_state "setup_shell" "started" - - if command_exists zsh; then - if [[ "$FORCE_MODE" == true ]] || prompt_user "Change default shell to Zsh?"; then - local zsh_path - zsh_path="$(command -v zsh)" - if execute_with_privilege "chsh -s '$zsh_path' '$USER'"; then - print_success "Default shell changed to Zsh" - print_warning "Please log out and log back in to apply changes" - else - print_error "Failed to change default shell" - fi - fi - else - print_warning "Zsh not installed, skipping shell setup" - fi - - # Install Zsh plugins if in dotfiles directory - if [[ -f "$HOME/.zshrc" || -f "$HOME/.config/zsh/.zshrc" ]]; then - install_zsh_plugins - fi - - mark_step_completed "setup_shell" -} - -install_zsh_plugins() { - if [[ "$INTERNET_AVAILABLE" != true ]]; then - print_warning "No internet connectivity - skipping Zsh plugins installation" - return 0 - fi - - local zsh_plugins_dir="$HOME/.config/zsh/plugins" - - print_info "Installing Zsh plugins..." - create_dir "$HOME/.config/zsh" - create_dir "$zsh_plugins_dir" - - local plugins=( - "zsh-you-should-use:https://github.com/MichaelAquilina/zsh-you-should-use.git" - "zsh-syntax-highlighting:https://github.com/zsh-users/zsh-syntax-highlighting.git" - "zsh-autosuggestions:https://github.com/zsh-users/zsh-autosuggestions.git" - ) - - for plugin_info in "${plugins[@]}"; do - local plugin_name="${plugin_info%:*}" - local plugin_url="${plugin_info#*:}" - local plugin_dir="$zsh_plugins_dir/$plugin_name" - - if [[ ! -d "$plugin_dir" ]]; then - print_info "Installing $plugin_name..." - if execute_command "git clone '$plugin_url' '$plugin_dir'"; then - print_success "Installed $plugin_name" - else - print_error "Failed to install $plugin_name" - fi - else - print_info "$plugin_name already installed" - fi - done -} - -setup_ssh() { - print_section "Setting Up SSH" - save_state "setup_ssh" "started" - - local ssh_dir="$HOME/.ssh" - - if [[ ! -f "$ssh_dir/id_rsa" && ! -f "$ssh_dir/id_ed25519" ]]; then - if [[ "$FORCE_MODE" == true ]] || prompt_user "Generate SSH key pair?"; then - create_dir "$ssh_dir" 700 - - local email="${USER}@${HOSTNAME:-$(hostname)}" - local key_file="$ssh_dir/id_ed25519" - - if execute_command "ssh-keygen -t ed25519 -f '$key_file' -N '' -C '$email'"; then - print_success "SSH key pair generated (Ed25519)" - execute_command "chmod 600 '$key_file'" - execute_command "chmod 644 '$key_file.pub'" - - if [[ "$DRY_RUN" != true ]] && [[ -f "$key_file.pub" ]]; then - print_info "Your public key:" - print_color "$GREEN" "$(cat "$key_file.pub")" - print_info "Copy this key to your Git hosting service" - fi - else - print_error "Failed to generate SSH key" - mark_step_failed "setup_ssh" - return 1 - fi - fi - else - print_info "SSH key already exists" - fi - - mark_step_completed "setup_ssh" -} - -# Helper function to detect the init system -detect_init_system() { - if [ -d /run/systemd/system ]; then - echo "systemd" - elif command -v rc-service &>/dev/null; then - echo "openrc" - elif [ -d /etc/sv ]; then - echo "runit" - elif command -v service &>/dev/null; then - echo "sysvinit" - else - echo "unknown" - fi -} - -# Helper function to manage a service (enable/start) -manage_service() { - local action="$1" - local service="$2" - local init_system="$3" - local success=false - - case "$init_system" in - systemd) - if [ "$action" == "enable" ]; then - execute_command "$PRIVILEGE_TOOL systemctl enable '$service'" - success=$? - elif [ "$action" == "start" ]; then - execute_command "$PRIVILEGE_TOOL systemctl start '$service'" - success=$? - fi - ;; - openrc) - if [ "$action" == "enable" ]; then - execute_command "$PRIVILEGE_TOOL rc-update add '$service' default" - success=$? - elif [ "$action" == "start" ]; then - execute_command "$PRIVILEGE_TOOL rc-service '$service' start" - success=$? - fi - ;; - runit) - if [ "$action" == "enable" ]; then - # Runit services are enabled by creating a symlink in the run level directory - execute_command "$PRIVILEGE_TOOL ln -sf /etc/sv/'$service' /var/service/" - success=$? - elif [ "$action" == "start" ]; then - # The 'start' action is usually implied by the symlink, but you can - # manually start it if needed - execute_command "$PRIVILEGE_TOOL sv start '$service'" - success=$? - fi - ;; - sysvinit|unknown) - # Use the generic 'service' command - if [ "$action" == "start" ]; then - execute_command "$PRIVILEGE_TOOL service '$service' start" - success=$? - fi - # Enabling is system-dependent for sysvinit/unknown; we'll check for chkconfig - if [ "$action" == "enable" ]; then - if command -v chkconfig &>/dev/null; then - execute_command "$PRIVILEGE_TOOL chkconfig '$service' on" - success=$? - else - success=0 - fi - fi - ;; - *) - print_error "Unknown init system: $init_system. Cannot $action service '$service'." - return 1 - ;; - esac - - return $((1 - success)) -} - -# Configure system services -configure_services() { - print_section "Configuring System Services" - save_state "configure_services" "started" - - if [[ "$CFG_OS" != "linux" ]]; then - print_skip "Service configuration (not supported on $CFG_OS)" - mark_step_completed "configure_services" - return 0 - fi - - # Detect the init system once - local INIT_SYSTEM=$(detect_init_system) - print_info "Detected Init System: $INIT_SYSTEM" - - # Enable TLP for laptop power management - if command_exists tlp; then - print_info "TLP is installed" - if [[ "$FORCE_MODE" == true ]] || prompt_user "Enable TLP power management service?"; then - if manage_service "enable" "tlp" "$INIT_SYSTEM"; then - manage_service "start" "tlp" "$INIT_SYSTEM" - print_success "TLP enabled and started" - else - print_error "Failed to enable TLP" - fi - fi - elif [[ "$FORCE_MODE" == true ]] || prompt_user "Install and enable TLP for better battery life?"; then - case "$DISTRO" in - PACMAN) execute_command "$PRIVILEGE_TOOL pacman -S --noconfirm tlp tlp-rdw" ;; - APT) execute_command "$PRIVILEGE_TOOL apt install -y tlp tlp-rdw" ;; - DNF) execute_command "$PRIVILEGE_TOOL dnf install -y tlp tlp-rdw" ;; - esac - - if command_exists tlp; then - manage_service "enable" "tlp" "$INIT_SYSTEM" - manage_service "start" "tlp" "$INIT_SYSTEM" - print_success "TLP installed, enabled and started" - fi - fi - - # Configure other useful services - local services_to_enable=() - - # Check for and configure common services - # NOTE: The 'is-enabled' check is non-portable and removed for simplicity - if command_exists docker; then - if [[ "$FORCE_MODE" == true ]] || prompt_user "Enable Docker service?"; then - services_to_enable+=("docker") - fi - fi - - if command_exists bluetooth; then - if [[ "$FORCE_MODE" == true ]] || prompt_user "Enable Bluetooth service?"; then - services_to_enable+=("bluetooth") - fi - fi - - # Enable selected services - for service in "${services_to_enable[@]}"; do - if manage_service "enable" "$service" "$INIT_SYSTEM"; then - manage_service "start" "$service" "$INIT_SYSTEM" - print_success "Enabled and started $service" - else - print_error "Failed to enable $service" - fi - done - - mark_step_completed "configure_services" -} - -setup_tmux_plugins() { - if [[ "$INTERNET_AVAILABLE" != true ]]; then - print_warning "No internet connectivity - skipping Tmux plugins installation" - return 0 - fi - - local tpm_dir="$HOME/.config/tmux/plugins/tpm" - local plugins_dir="$HOME/.config/tmux/plugins" - - if [[ ! -f "$HOME/.tmux.conf" && ! -f "$HOME/.config/tmux/tmux.conf" ]]; then - print_info "Tmux config not found, skipping plugin setup" - return 0 - fi - - print_info "Setting up Tmux plugins..." - create_dir "$plugins_dir" - - if [[ ! -d "$tpm_dir" || ! "$(ls -A "$tpm_dir" 2>/dev/null)" ]]; then - print_info "Installing Tmux Plugin Manager (TPM)..." - if execute_command "git clone https://github.com/tmux-plugins/tpm '$tpm_dir'"; then - print_success "TPM installed successfully" - print_info "Run 'tmux' and press 'prefix + I' to install plugins" - else - print_error "Failed to install TPM" - fi - else - print_info "TPM already installed" - fi -} - -setup_development() { - print_section "Setting Up Development Environment" - save_state "setup_development" "started" - - # Git configuration - if command_exists git; then - if [[ "$FORCE_MODE" == true ]] || prompt_user "Configure Git global settings?"; then - configure_git - fi - fi - - # Development tools based on install mode - case "$INSTALL_MODE" in - dev|full) - install_development_tools - ;; - *) - print_info "Skipping development tools installation for mode: $INSTALL_MODE" - ;; - esac - - mark_step_completed "setup_development" -} - -configure_git() { - local git_name="${USER}" - local git_email="${USER}@${HOSTNAME:-$(hostname)}" - - if [[ "$FORCE_MODE" != true ]]; then - print_color "$YELLOW" "Enter your Git username [$git_name]: " - read -r input_name - [[ -n "$input_name" ]] && git_name="$input_name" - - print_color "$YELLOW" "Enter your Git email [$git_email]: " - read -r input_email - [[ -n "$input_email" ]] && git_email="$input_email" - fi - - execute_command "git config --global user.name '$git_name'" - execute_command "git config --global user.email '$git_email'" - execute_command "git config --global init.defaultBranch main" - execute_command "git config --global pull.rebase false" - print_success "Git configured with name: $git_name, email: $git_email" -} - -install_development_tools() { - if [[ "$INTERNET_AVAILABLE" != true ]]; then - print_warning "No internet connectivity - skipping development tools installation" - return 0 - fi - - print_info "Installing development tools..." - - # Install Rust if not present - if ! command_exists rustc; then - install_rust - fi - - # Install Node.js via NVM if not present - if ! command_exists node; then - install_nvm - install_node - fi - - # Install Yarn if Node.js is available - if command_exists npm && ! command_exists yarn; then - install_yarn - fi -} - -install_rust() { - print_info "Installing Rust via rustup..." - - if command_exists rustup; then - print_info "Rust already installed" - return 0 - fi - - local cargo_home="${XDG_DATA_HOME:-$HOME/.local/share}/cargo" - local rustup_home="${XDG_DATA_HOME:-$HOME/.local/share}/rustup" - - create_dir "$(dirname "$cargo_home")" - - if execute_command "CARGO_HOME='$cargo_home' RUSTUP_HOME='$rustup_home' curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y"; then - print_success "Rust installed successfully" - - # Add to PATH for current session - if [[ -f "$cargo_home/env" ]]; then - source "$cargo_home/env" - fi - - return 0 - else - print_error "Failed to install Rust" - return 1 - fi -} - -install_nvm() { - local nvm_dir="$HOME/.config/nvm" - - if [[ -d "$nvm_dir" && -f "$nvm_dir/nvm.sh" ]]; then - print_info "NVM already installed" - return 0 - fi - - print_info "Installing Node Version Manager (NVM)..." - create_dir "$nvm_dir" - - if execute_command "curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/master/install.sh | NVM_DIR='$nvm_dir' bash"; then - export NVM_DIR="$nvm_dir" - if [[ -s "$NVM_DIR/nvm.sh" ]]; then - source "$NVM_DIR/nvm.sh" - print_success "NVM installed successfully" - return 0 - else - print_error "NVM installation failed - script not found" - return 1 - fi - else - print_error "Failed to install NVM" - return 1 - fi -} - -install_node() { - if command_exists node; then - print_info "Node.js already installed" - return 0 - fi - - print_info "Installing Node.js..." - - # Source NVM if available - local nvm_dir="$HOME/.config/nvm" - if [[ -s "$nvm_dir/nvm.sh" ]]; then - export NVM_DIR="$nvm_dir" - source "$NVM_DIR/nvm.sh" - fi - - if command_exists nvm; then - if execute_command "nvm install --lts" && execute_command "nvm use --lts" && execute_command "nvm alias default lts/*"; then - print_success "Node.js installed successfully" - return 0 - else - print_error "Failed to install Node.js via NVM" - return 1 - fi - else - print_error "NVM not available for Node.js installation" - return 1 - fi -} - -install_yarn() { - print_info "Installing Yarn..." - - if execute_command "curl -o- -L https://yarnpkg.com/install.sh | bash"; then - print_success "Yarn installed successfully" - - # Add to PATH for current session - local yarn_bin="$HOME/.yarn/bin" - if [[ -d "$yarn_bin" && ":$PATH:" != *":$yarn_bin:"* ]]; then - export PATH="$yarn_bin:$PATH" - fi - - return 0 - else - print_error "Failed to install Yarn" - return 1 - fi -} - -apply_tweaks() { - print_section "Applying System Tweaks" - save_state "apply_tweaks" "started" - - case "$CFG_OS" in - linux) - apply_linux_tweaks - ;; - macos) - apply_macos_tweaks - ;; - *) - print_info "No system tweaks defined for $CFG_OS" - ;; - esac - - mark_step_completed "apply_tweaks" -} - -apply_linux_tweaks() { - # --- Locale tweak --- - if command -v localectl >/dev/null 2>&1; then - local current_locale - current_locale=$(localectl status | grep "System Locale" | cut -d= -f2 | cut -d, -f1) - if [[ "$current_locale" != "en_US.UTF-8" ]]; then - if prompt_user "Set system locale to en_US.UTF-8?"; then - if execute_with_privilege "localectl set-locale LANG=en_US.UTF-8"; then - print_success "Locale set to en_US.UTF-8" - else - print_error "Failed to set locale" - fi - fi - fi - fi - - # --- Power / Display timeout tweaks --- - if command -v gsettings >/dev/null 2>&1; then - print_info "Setting GNOME power/display timeouts to 'never'" - - # Turn off blank screen - gsettings set org.gnome.desktop.session idle-delay 0 - - # Turn off automatic suspend - gsettings set org.gnome.settings-daemon.plugins.power sleep-inactive-ac-type 'nothing' - gsettings set org.gnome.settings-daemon.plugins.power sleep-inactive-battery-type 'nothing' - - print_success "GNOME power/display settings applied" - else - print_info "gsettings not found; skipping GNOME power/display tweaks" - fi - - print_info "Linux system tweaks applied" -} - -apply_macos_tweaks() { - print_info "macOS system tweaks applied (placeholder)" -} - -#====================================== -# Installation Mode Selection -#====================================== - -select_installation_mode() { - if [[ "$INSTALL_MODE" != "ask" ]]; then - return 0 # Mode already set via command line - fi - - print_header "Installation Mode Selection" - - print_color "$CYAN" "Available installation modes:" - echo - - local mode_number=1 - local mode_options=() - - for mode in essentials minimal dev server full; do - local description="${INSTALLATION_PROFILES[$mode]}" - print_color "$YELLOW" "$mode_number. $mode - $description" - mode_options+=("$mode") - ((mode_number++)) - done - - echo - print_color "$CYAN" "You can also specify a custom profile from the profiles/ directory" - echo - - while true; do - print_color "$YELLOW" "Select installation mode [1-5] or enter profile name [dev]: " - read -r response - - if [[ -z "$response" ]]; then - INSTALL_MODE="dev" - break - elif [[ "$response" =~ ^[1-5]$ ]]; then - INSTALL_MODE="${mode_options[$((response-1))]}" - break - elif [[ "$response" =~ ^[a-zA-Z][a-zA-Z0-9_-]*$ ]]; then - # Check if it's a valid profile - if [[ -f "profiles/$response.yml" ]] || [[ "${INSTALLATION_PROFILES[$response]:-}" ]]; then - INSTALL_MODE="$response" - break - else - print_warning "Profile '$response' not found" - fi - else - print_warning "Invalid selection. Please enter 1-5 or a profile name" - fi - done - - print_success "Selected installation mode: $INSTALL_MODE" - print_info "Description: ${INSTALLATION_PROFILES[$INSTALL_MODE]:-Custom profile}" -} - -#====================================== -# Command Line Argument Parsing -#====================================== - -show_help() { - cat << EOF -Dotfiles Installation Script - -USAGE: - $0 [OPTIONS] - -OPTIONS: - -h, --help Show this help message - -r, --resume Resume from last failed step - -u, --update Update existing dotfiles and packages - -v, --verbose Enable verbose output - -n, --dry-run Show what would be done without executing - -f, --force Force reinstallation and skip prompts - -m, --mode MODE Installation mode (essentials|minimal|dev|server|full|PROFILE) - -INSTALLATION MODES: - essentials Install only essential packages (git, curl, etc.) - minimal Minimal setup for basic development - dev Full development environment (default) - server Server configuration - full Complete installation with all packages - PROFILE Custom profile from profiles/ directory - -EXAMPLES: - $0 # Interactive installation (asks for mode) - $0 --mode essentials # Install essentials only - $0 --mode dev # Development environment - $0 --resume # Resume from last failed step - $0 --update --mode full # Update and install all packages - $0 --dry-run --mode dev # Preview development installation - -EOF -} - -parse_arguments() { - while [[ $# -gt 0 ]]; do - case $1 in - -h|--help) - show_help - exit 0 - ;; - -r|--resume) - RESUME_MODE=true - shift - ;; - -u|--update) - UPDATE_MODE=true - shift - ;; - -v|--verbose) - VERBOSE_MODE=true - shift - ;; - -n|--dry-run) - DRY_RUN=true - shift - ;; - -f|--force) - FORCE_MODE=true - shift - ;; - -m|--mode) - INSTALL_MODE="$2" - shift 2 - ;; - *) - print_error "Unknown option: $1" - show_help - exit 1 - ;; - esac - done - - # Validate install mode - if [[ "$INSTALL_MODE" != "ask" ]]; then - if [[ ! "${INSTALLATION_PROFILES[$INSTALL_MODE]:-}" ]] && [[ ! -f "profiles/$INSTALL_MODE.yml" ]]; then - print_error "Invalid installation mode: $INSTALL_MODE" - print_info "Available modes: ${!INSTALLATION_PROFILES[*]}" - exit 1 - fi - fi -} - -#====================================== -# Summary Functions -#====================================== - -print_installation_summary() { - print_header "Installation Summary" - - local total_steps=${#STEP_ORDER[@]} - local completed_count=${#COMPLETED_STEPS[@]} - local failed_count=${#FAILED_ITEMS[@]} - - print_section "Progress Overview" - print_color "$CYAN" "Installation Mode: $INSTALL_MODE" - print_color "$CYAN" "Total Steps: $total_steps" - print_color "$GREEN" "Completed: $completed_count" - print_color "$RED" "Failed: $failed_count" - - if [[ ${#INSTALL_SUMMARY[@]} -gt 0 ]]; then - print_section "Successful Operations" - printf '%s\n' "${INSTALL_SUMMARY[@]}" - fi - - if [[ ${#FAILED_ITEMS[@]} -gt 0 ]]; then - print_section "Failed Operations" - printf '%s\n' "${FAILED_ITEMS[@]}" - print_warning "Check the log file: $LOG_FILE" - fi - - if [[ ${#SKIPPED_ITEMS[@]} -gt 0 ]]; then - print_section "Skipped Operations" - printf '%s\n' "${SKIPPED_ITEMS[@]}" - fi - - echo - print_color "$GREEN$BOLD" "Installation completed!" - print_info "Log file: $LOG_FILE" "always" -} - -#====================================== -# Main Installation Flow -#====================================== - -execute_step() { - local step_name="$1" - local step_desc="${INSTALLATION_STEPS[$step_name]}" - - if is_step_completed "$step_name" && [[ "$FORCE_MODE" != true ]]; then - print_success "$step_desc (already completed)" - return 0 - fi - - if "$step_name"; then - print_success "$step_desc completed" - mark_step_completed "$step_name" - return 0 - else - print_error "$step_desc failed" - mark_step_failed "$step_name" - return 1 - fi -} - -main() { - parse_arguments "$@" - setup_logging - - print_header "Dotfiles Installation" - - if [[ "$DRY_RUN" == true ]]; then - print_warning "DRY RUN MODE - No changes will be made" - echo - fi - - print_info "Starting installation for user: $USER" "always" - print_info "Log file: $LOG_FILE" "always" - - # Handle resume mode - if [[ "$RESUME_MODE" == true ]]; then - if load_state; then - print_info "Resuming from previous installation..." "always" - print_info "Last step: ${LAST_STEP:-unknown}" "always" - if [[ -n "${COMPLETED_STEPS:-}" ]]; then - eval "COMPLETED_STEPS=(${COMPLETED_STEPS:-})" - fi - else - print_warning "No previous installation state found" - print_info "Starting fresh installation..." - RESUME_MODE=false - fi - fi - - # Select installation mode if not specified - select_installation_mode - - # Show installation plan - echo - print_color "$YELLOW$BOLD" "Installation Plan (Mode: $INSTALL_MODE):" - local step_number=1 - for step in "${STEP_ORDER[@]}"; do - local step_desc="${INSTALLATION_STEPS[$step]}" - if is_step_completed "$step" && [[ "$FORCE_MODE" != true ]]; then - print_color "$GREEN" "$step_number. $step_desc (✓ completed)" - else - print_color "$CYAN" "$step_number. $step_desc" - fi - step_number=$((step_number + 1)) - done - - echo - if [[ "$FORCE_MODE" != true ]] && [[ "$DRY_RUN" != true ]] && ! prompt_user "Continue with installation?"; then - print_info "Installation cancelled by user" - exit 0 - fi - - # Execute installation steps - local failed_steps=() - local step_number=1 - local total_steps=${#STEP_ORDER[@]} - - for step in "${STEP_ORDER[@]}"; do - echo - print_color "$MAGENTA$BOLD" "[$step_number/$total_steps] ${INSTALLATION_STEPS[$step]}" - - if execute_step "$step"; then - print_info "Step completed successfully: $step" - else - failed_steps+=("$step") - print_error "Step failed: $step" - - if [[ "$FORCE_MODE" != true ]] && [[ "$DRY_RUN" != true ]]; then - echo - if ! prompt_user "Step '$step' failed. Continue with remaining steps?" "Y"; then - print_info "Installation stopped by user" - break - fi - fi - fi - - step_number=$((step_number + 1)) - done - - # Post-installation - if [[ ${#failed_steps[@]} -eq 0 ]]; then - print_success "All installation steps completed successfully!" - clear_state - else - print_warning "${#failed_steps[@]} steps failed: ${failed_steps[*]}" - if [[ "${failed_steps[-1]:-}" != "" ]]; then - save_state "${failed_steps[-1]}" "failed" - fi - fi - - print_installation_summary - - # Final recommendations - if [[ "$DRY_RUN" != true ]]; then - echo - print_section "Post-Installation Recommendations" - print_color "$CYAN" "• Restart your terminal or run: source ~/.bashrc (or ~/.zshrc)" - print_color "$CYAN" "• Review your dotfiles configuration in: $DOTFILES_DIR" - print_color "$CYAN" "• Use the 'config' command to manage your dotfiles" - - if [[ ${#failed_steps[@]} -gt 0 ]]; then - print_color "$YELLOW" "• Run '$0 --resume' to retry failed steps" - print_color "$YELLOW" "• Check the log file for detailed error information: $LOG_FILE" - fi - - echo - print_color "$GREEN$BOLD" "Thank you for using the Dotfiles Installation Script!" - fi - - [[ ${#failed_steps[@]} -eq 0 ]] && exit 0 || exit 1 -} - -#====================================== -# Script Entry Point -#====================================== - -cleanup_on_exit() { - local exit_code=$? - - if [[ $exit_code -ne 0 ]] && [[ "$DRY_RUN" != true ]]; then - print_error "Installation interrupted (exit code: $exit_code)" - - if [[ -n "${current_step:-}" ]]; then - save_state "$current_step" "interrupted" - print_info "State saved. Run with --resume to continue" - fi - fi -} - -handle_interrupt() { - print_warning "Installation interrupted by user" - exit 130 -} - -trap cleanup_on_exit EXIT -trap handle_interrupt INT - -# Execute main if script is run directly -if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then - # Check basic requirements - for req in git curl; do - if ! command_exists "$req"; then - print_error "$req is required but not installed" - exit 1 - fi - done - - main "$@" -fi |
