aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsrdusr <trevorgray@srdusr.com>2025-09-21 01:39:35 +0200
committersrdusr <trevorgray@srdusr.com>2025-09-21 01:39:35 +0200
commite1091a6b21b04d053fd635ef9519982a30ba9419 (patch)
tree5c678fee08acdd14bdc81487ce0b2a6ffea0a391
parentdfafb13ec3ea243a4ce307867389ebe99c25daba (diff)
downloaddotfiles-e1091a6b21b04d053fd635ef9519982a30ba9419.tar.gz
dotfiles-e1091a6b21b04d053fd635ef9519982a30ba9419.zip
Testing
-rwxr-xr-xcommon/install.sh1735
-rw-r--r--common/packages.yml1161
2 files changed, 2002 insertions, 894 deletions
diff --git a/common/install.sh b/common/install.sh
index 60abd0b..b316d54 100755
--- a/common/install.sh
+++ b/common/install.sh
@@ -12,13 +12,12 @@ set -euo pipefail # Exit on error, undefined vars, pipe failures
# Variables & Configuration
#======================================
-# Color definitions for pretty UI
+# Color definitions
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'
@@ -47,12 +46,15 @@ UPDATE_MODE=false
VERBOSE_MODE=false
DRY_RUN=false
FORCE_MODE=false
+ASK_MODE=false # New: ask for each step
INSTALL_MODE="ask" # ask, essentials, full, profile
# Global variables for system detection
CFG_OS=""
DISTRO=""
PACKAGE_MANAGER=""
+PACKAGE_UPDATE_CMD=""
+PACKAGE_INSTALL_CMD=""
PRIVILEGE_TOOL=""
PRIVILEGE_CACHED=false
@@ -60,6 +62,64 @@ PRIVILEGE_CACHED=false
ESSENTIAL_TOOLS=("git" "curl" "wget")
PACKAGE_TOOLS=("yq" "jq")
+# Config command tracking
+CONFIG_COMMAND_AVAILABLE=false
+CONFIG_COMMAND_FILE=""
+
+# Steps can be skipped by providing a comma-separated list in SKIP_STEPS
+SKIP_STEPS="${SKIP_STEPS:-}"
+
+# Run control: run only a specific step, or start from a specific step
+RUN_ONLY_STEP="${RUN_ONLY_STEP:-}"
+RUN_FROM_STEP="${RUN_FROM_STEP:-}"
+__RUN_FROM_STARTED=false
+
+# Interactive per-step prompt even without --ask (opt-in)
+# Set INTERACTIVE_SKIP=true to be prompted for non-essential steps.
+INTERACTIVE_SKIP="${INTERACTIVE_SKIP:-false}"
+
+# Steps considered essential (should rarely be skipped)
+ESSENTIAL_STEPS=(
+ setup_environment
+ check_connectivity
+ detect_package_manager
+ install_dependencies
+)
+
+is_step_skipped() {
+ local step="$1"
+ [[ ",${SKIP_STEPS}," == *",${step},"* ]]
+}
+
+skip_step_if_requested() {
+ local step="$1"
+ if is_step_skipped "$step"; then
+ print_skip "Skipping step by request: $step"
+ mark_step_completed "$step"
+ return 1
+ fi
+ return 0
+}
+
+should_run_step() {
+ local step="$1"
+ # If RUN_ONLY_STEP is set, only run that exact step
+ if [[ -n "$RUN_ONLY_STEP" && "$step" != "$RUN_ONLY_STEP" ]]; then
+ print_skip "Skipping step (RUN_ONLY_STEP=$RUN_ONLY_STEP): $step"
+ return 1
+ fi
+ # If RUN_FROM_STEP is set, skip until we reach it, then run subsequent steps
+ if [[ -n "$RUN_FROM_STEP" && "$__RUN_FROM_STARTED" != true ]]; then
+ if [[ "$step" == "$RUN_FROM_STEP" ]]; then
+ __RUN_FROM_STARTED=true
+ else
+ print_skip "Skipping step until RUN_FROM_STEP=$RUN_FROM_STEP: $step"
+ return 1
+ fi
+ fi
+ return 0
+}
+
# Installation profiles
declare -A INSTALLATION_PROFILES=(
["essentials"]="Essential packages only (git, curl, wget, vim, zsh)"
@@ -73,6 +133,7 @@ declare -A INSTALLATION_PROFILES=(
declare -A INSTALLATION_STEPS=(
["setup_environment"]="Setup installation environment"
["check_connectivity"]="Check internet connectivity"
+ ["detect_package_manager"]="Detect or configure package manager"
["install_dependencies"]="Install dependencies"
["install_dotfiles"]="Install dotfiles repository"
["setup_user_dirs"]="Setup user directories"
@@ -81,26 +142,27 @@ declare -A INSTALLATION_STEPS=(
["setup_shell"]="Setup shell environment"
["setup_ssh"]="Setup SSH configuration"
["configure_services"]="Configure system services"
- ["setup_development"]="Setup development environment"
+ ["setup_development_environment"]="Setup development environment"
["apply_tweaks"]="Apply system tweaks"
["deploy_config"]="Deploy config command and dotfiles"
)
-# Step order (important for dependencies)
+# Step order
STEP_ORDER=(
"setup_environment"
"check_connectivity"
+ "detect_package_manager"
"install_dependencies"
"install_dotfiles"
+ "deploy_config"
"setup_user_dirs"
"install_essentials"
"install_packages"
"setup_shell"
"setup_ssh"
"configure_services"
- "setup_development"
+ "setup_development_environment"
"apply_tweaks"
- "deploy_config"
)
#======================================
@@ -226,7 +288,7 @@ print_skip() {
print_dry_run() {
local message="$1"
- print_color "$MAGENTA" "[DRY RUN] $message"
+ print_color "$CYAN" "[DRY RUN] $message"
}
#======================================
@@ -354,68 +416,99 @@ test_privilege_access() {
}
detect_package_manager() {
+ print_section "Detecting Package Manager"
+ save_state "detect_package_manager" "started"
+
# 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" ;;
+ PACKAGE_MANAGER="pacman"
+ PACKAGE_UPDATE_CMD="pacman -Sy"
+ PACKAGE_INSTALL_CMD="pacman -S --noconfirm"
+ ;;
debian|ubuntu|mint|pop|elementary|zorin)
DISTRO="$ID"
- PACKAGE_MANAGER="apt" ;;
+ PACKAGE_MANAGER="apt"
+ PACKAGE_UPDATE_CMD="apt-get update"
+ PACKAGE_INSTALL_CMD="apt-get install -y"
+ ;;
fedora|rhel|centos|rocky|almalinux)
DISTRO="$ID"
- PACKAGE_MANAGER="dnf" ;;
+ PACKAGE_MANAGER="dnf"
+ PACKAGE_UPDATE_CMD="dnf check-update"
+ PACKAGE_INSTALL_CMD="dnf install -y"
+ ;;
opensuse*|sles)
DISTRO="$ID"
- PACKAGE_MANAGER="zypper" ;;
- gentoo|funtoo)
+ PACKAGE_MANAGER="zypper"
+ PACKAGE_UPDATE_CMD="zypper refresh"
+ PACKAGE_INSTALL_CMD="zypper install -y"
+ ;;
+ gentoo)
DISTRO="$ID"
- PACKAGE_MANAGER="portage" ;;
+ PACKAGE_MANAGER="portage"
+ PACKAGE_UPDATE_CMD="emerge --sync"
+ PACKAGE_INSTALL_CMD="emerge"
+ ;;
alpine)
DISTRO="$ID"
- PACKAGE_MANAGER="apk" ;;
+ PACKAGE_MANAGER="apk"
+ PACKAGE_UPDATE_CMD="apk update"
+ PACKAGE_INSTALL_CMD="apk add"
+ ;;
void)
DISTRO="$ID"
- PACKAGE_MANAGER="xbps" ;;
+ PACKAGE_MANAGER="xbps"
+ PACKAGE_UPDATE_CMD="xbps-install -S"
+ PACKAGE_INSTALL_CMD="xbps-install -y"
+ ;;
nixos)
DISTRO="$ID"
- PACKAGE_MANAGER="nix" ;;
- *)
- print_warning "Unknown distribution: $ID, trying to detect package manager directly"
+ PACKAGE_MANAGER="nix"
+ PACKAGE_UPDATE_CMD="nix-channel --update"
+ PACKAGE_INSTALL_CMD="nix-env -iA nixpkgs."
;;
esac
elif [[ "$CFG_OS" == "macos" ]]; then
DISTRO="macos"
if command -v brew &>/dev/null; then
PACKAGE_MANAGER="brew"
+ PACKAGE_UPDATE_CMD="brew update"
+ PACKAGE_INSTALL_CMD="brew install"
else
- PACKAGE_MANAGER="brew-install" # Will install homebrew
+ PACKAGE_MANAGER="brew-install"
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"
+ "pacman:pacman:pacman -Sy:pacman -S --noconfirm"
+ "apt:apt:apt-get update:apt-get install -y"
+ "dnf:dnf:dnf check-update:dnf install -y"
+ "yum:yum:yum check-update:yum install -y"
+ "zypper:zypper:zypper refresh:zypper install -y"
+ "emerge:portage:emerge --sync:emerge"
+ "apk:apk:apk update:apk add"
+ "xbps-install:xbps:xbps-install -S:xbps-install -y"
+ "nix-env:nix:nix-channel --update:nix-env -iA nixpkgs."
+ "pkg:pkg:pkg update:pkg install -y"
+ "brew:brew:brew update:brew install"
)
for manager in "${managers[@]}"; do
- local cmd="${manager%:*}"
- local name="${manager#*:}"
+ local cmd="${manager%%:*}"
+ local name="${manager#*:}"; name="${name%%:*}"
+ local update_cmd="${manager#*:*:}"; update_cmd="${update_cmd%%:*}"
+ local install_cmd="${manager##*:}"
+
if command -v "$cmd" &>/dev/null; then
PACKAGE_MANAGER="$name"
+ PACKAGE_UPDATE_CMD="$update_cmd"
+ PACKAGE_INSTALL_CMD="$install_cmd"
break
fi
done
@@ -424,17 +517,80 @@ detect_package_manager() {
if [[ -n "$PACKAGE_MANAGER" ]]; then
print_success "Detected package manager: $PACKAGE_MANAGER"
[[ -n "$DISTRO" ]] && print_info "Distribution: $DISTRO"
+
+ # Try to override commands from packages.yml -> package_managers
+ # Find packages.yml in standard locations
+ local original_dir="$PWD"
+ cd "$HOME" 2>/dev/null || true
+ 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
+ cd "$original_dir" 2>/dev/null || true
+
+ if command_exists yq && [[ -n "$found_packages_file" ]]; then
+ # Prefer distro block, fallback to manager block
+ local pm_update pm_install
+ if [[ -n "$DISTRO" ]]; then
+ pm_update=$(yq eval ".package_managers.${DISTRO}.update" "$found_packages_file" 2>/dev/null | grep -v "^null$" || echo "")
+ pm_install=$(yq eval ".package_managers.${DISTRO}.install" "$found_packages_file" 2>/dev/null | grep -v "^null$" || echo "")
+ fi
+ if [[ -z "$pm_update" || -z "$pm_install" ]]; then
+ pm_update=$(yq eval ".package_managers.${PACKAGE_MANAGER}.update" "$found_packages_file" 2>/dev/null | grep -v "^null$" || echo "")
+ pm_install=$(yq eval ".package_managers.${PACKAGE_MANAGER}.install" "$found_packages_file" 2>/dev/null | grep -v "^null$" || echo "")
+ fi
+ if [[ -n "$pm_update" && -n "$pm_install" ]]; then
+ PACKAGE_UPDATE_CMD="$pm_update"
+ PACKAGE_INSTALL_CMD="$pm_install"
+ print_info "Using package manager commands from packages.yml"
+ fi
+ fi
+
+ mark_step_completed "detect_package_manager"
return 0
else
print_error "Could not detect package manager"
- return 1
+ manual_package_manager_setup
+ return $?
fi
}
+manual_package_manager_setup() {
+ print_warning "No supported package manager detected automatically"
+ print_info "Please provide package manager commands manually:"
+
+ while true; do
+ print_color "$YELLOW" "Enter package update command (e.g., 'apt-get update'): "
+ read -r PACKAGE_UPDATE_CMD
+ [[ -n "$PACKAGE_UPDATE_CMD" ]] && break
+ print_warning "Update command cannot be empty"
+ done
+
+ while true; do
+ print_color "$YELLOW" "Enter package install command (e.g., 'apt-get install -y'): "
+ read -r PACKAGE_INSTALL_CMD
+ [[ -n "$PACKAGE_INSTALL_CMD" ]] && break
+ print_warning "Install command cannot be empty"
+ done
+
+ PACKAGE_MANAGER="manual"
+ print_success "Manual package manager configuration set"
+ print_info "Update command: $PACKAGE_UPDATE_CMD"
+ print_info "Install command: $PACKAGE_INSTALL_CMD"
+
+ mark_step_completed "detect_package_manager"
+ return 0
+}
+
#======================================
# Utility Functions
#======================================
+
command_exists() {
command -v "$1" &>/dev/null
}
@@ -488,9 +644,9 @@ prompt_user() {
while true; do
if [[ "$default" == "Y" ]]; then
- print_color "$YELLOW" "$question [Y/n]: "
+ printf "%b%s%b" "$YELLOW" "$question [Y/n]: " "$NOCOLOR"
else
- print_color "$YELLOW" "$question [y/N]: "
+ printf "%b%s%b" "$YELLOW" "$question [y/N]: " "$NOCOLOR"
fi
read -r response
@@ -500,9 +656,9 @@ prompt_user() {
fi
case "${response^^}" in
- Y|YES) return 0 ;;
- N|NO) return 1 ;;
- *) print_warning "Please answer Y/yes or N/no" ;;
+ Y|YES) echo; return 0 ;;
+ N|NO) echo; return 1 ;;
+ *) echo; print_warning "Please answer Y/yes or N/no" ;;
esac
done
}
@@ -554,39 +710,48 @@ setup_logging() {
print_info "Log file initialized: $LOG_FILE" "always"
}
-get_package_name() {
+get_package_names() {
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=""
+ local distro_packages=""
- # Try to get package name for current distribution
+ # Try to get package name(s) 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 "")
+ distro_packages=$(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 "")
+ distro_packages=$(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 "")
+ distro_packages=$(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 "")
+ distro_packages=$(yq eval ".opensuse.$package" "$packages_file" 2>/dev/null | grep -v "^null$" || echo "")
+ ;;
+ gentoo)
+ distro_packages=$(yq eval ".gentoo.$package" "$packages_file" 2>/dev/null | grep -v "^null$" || echo "")
+ ;;
+ alpine)
+ distro_packages=$(yq eval ".alpine.$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 "")
+ void)
+ distro_packages=$(yq eval ".void.$package" "$packages_file" 2>/dev/null | grep -v "^null$" || echo "")
;;
macos)
- distro_package=$(yq eval ".macos[]" "$packages_file" 2>/dev/null | grep "^$package$" || echo "")
+ # macOS uses array format, check if package exists in the list
+ if yq eval ".macos[]" "$packages_file" 2>/dev/null | grep -q "^$package$"; then
+ distro_packages="$package"
+ fi
;;
esac
- # Return the distribution-specific package name if found
- if [[ -n "$distro_package" ]]; then
- echo "$distro_package"
+ # Return the distribution-specific package name(s) if found
+ if [[ -n "$distro_packages" ]]; then
+ echo "$distro_packages"
return 0
fi
fi
@@ -632,6 +797,13 @@ install_dependencies_if_missing() {
fi
done
+ # If everything is already present, skip with a clear message
+ if [[ ${#missing_deps[@]} -eq 0 ]]; then
+ print_skip "All required dependencies are already installed"
+ mark_step_completed "install_dependencies"
+ return 0
+ fi
+
# 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"
@@ -664,6 +836,7 @@ install_dependencies_if_missing() {
mark_step_failed "install_dependencies"
return 1
else
+ print_success "Dependencies satisfied: ${missing_deps[*]}"
mark_step_completed "install_dependencies"
return 0
fi
@@ -694,54 +867,75 @@ install_package_offline() {
# 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 the correct package name(s) for this distro - can be multiple packages
+ local pkg_names
+ pkg_names=$(get_package_names "$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"
+ print_info "Installing $package_type package: $package -> $pkg_names"
- 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
+ # Handle multiple packages
+ local install_success=true
+ for pkg_name in $pkg_names; do
+ print_info "Installing: $pkg_name"
+
+ case "$PACKAGE_MANAGER" in
+ pacman)
+ execute_with_privilege "$PACKAGE_INSTALL_CMD '$pkg_name'" || install_success=false
+ ;;
+ apt)
+ execute_with_privilege "$PACKAGE_INSTALL_CMD '$pkg_name'" || install_success=false
+ ;;
+ dnf|yum)
+ execute_with_privilege "$PACKAGE_INSTALL_CMD '$pkg_name'" || install_success=false
+ ;;
+ zypper)
+ execute_with_privilege "$PACKAGE_INSTALL_CMD '$pkg_name'" || install_success=false
+ ;;
+ portage)
+ local emerge_cmd="$PACKAGE_INSTALL_CMD"
+ if [[ -n "$use_flags" ]]; then
+ emerge_cmd="USE='$use_flags' $PACKAGE_INSTALL_CMD"
+ print_info "Using USE flags for $pkg_name: $use_flags"
+ fi
+ execute_with_privilege "$emerge_cmd '$pkg_name'" || install_success=false
+ ;;
+ apk)
+ execute_with_privilege "$PACKAGE_INSTALL_CMD '$pkg_name'" || install_success=false
+ ;;
+ xbps)
+ execute_with_privilege "$PACKAGE_INSTALL_CMD '$pkg_name'" || install_success=false
+ ;;
+ nix)
+ execute_command "$PACKAGE_INSTALL_CMD$pkg_name" || install_success=false
+ ;;
+ brew)
+ execute_command "$PACKAGE_INSTALL_CMD '$pkg_name'" || install_success=false
+ ;;
+ brew-install)
+ print_error "Homebrew not installed. Please install it first."
+ return 1
+ ;;
+ manual)
+ execute_with_privilege "$PACKAGE_INSTALL_CMD '$pkg_name'" || install_success=false
+ ;;
+ *)
+ print_error "Package manager '$PACKAGE_MANAGER' not supported"
+ return 1
+ ;;
+ esac
+ done
+
+ return $([[ "$install_success" == true ]] && echo 0 || echo 1)
}
update_package_database() {
@@ -749,23 +943,23 @@ update_package_database() {
case "$PACKAGE_MANAGER" in
pacman)
- execute_with_privilege "pacman -Sy" ;;
+ execute_with_privilege "$PACKAGE_UPDATE_CMD" ;;
apt)
- execute_with_privilege "apt-get update" ;;
- dnf)
- execute_with_privilege "dnf check-update" || true ;;
- yum)
- execute_with_privilege "yum check-update" || true ;;
+ execute_with_privilege "$PACKAGE_UPDATE_CMD" ;;
+ dnf|yum)
+ execute_with_privilege "$PACKAGE_UPDATE_CMD" || true ;;
zypper)
- execute_with_privilege "zypper refresh" ;;
+ execute_with_privilege "$PACKAGE_UPDATE_CMD" ;;
portage)
- execute_with_privilege "emerge --sync" ;;
+ execute_with_privilege "$PACKAGE_UPDATE_CMD" ;;
apk)
- execute_with_privilege "apk update" ;;
+ execute_with_privilege "$PACKAGE_UPDATE_CMD" ;;
xbps)
- execute_with_privilege "xbps-install -S" ;;
+ execute_with_privilege "$PACKAGE_UPDATE_CMD" ;;
brew)
- execute_command "brew update" ;;
+ execute_command "$PACKAGE_UPDATE_CMD" ;;
+ manual)
+ execute_with_privilege "$PACKAGE_UPDATE_CMD" ;;
*)
print_info "Package database update not needed for $PACKAGE_MANAGER" ;;
esac
@@ -857,10 +1051,48 @@ parse_packages_from_yaml() {
mapfile -t packages < <(yq eval ".$section[]" "$packages_file" 2>/dev/null | grep -v "^null$" || true)
fi
- # Output packages
+ # Output packages (one per line)
printf '%s\n' "${packages[@]}"
}
+get_profile_package_groups() {
+ local packages_file="$1"
+ local profile="$2"
+ local groups=()
+
+ if [[ ! -f "$packages_file" ]]; then
+ print_warning "Package file not found: $packages_file"
+ return 1
+ fi
+
+ # Get package groups for the profile from the profiles section
+ if yq eval ".profiles.$profile.packages" "$packages_file" &>/dev/null; then
+ mapfile -t groups < <(yq eval ".profiles.$profile.packages[]" "$packages_file" 2>/dev/null | grep -v "^null$" || true)
+ fi
+
+ # Fallback to old method if profiles section doesn't exist
+ if [[ ${#groups[@]} -eq 0 ]]; then
+ case "$profile" in
+ essentials)
+ groups=("common" "essentials") ;;
+ minimal)
+ groups=("common" "essentials" "minimal") ;;
+ dev)
+ groups=("common" "essentials" "minimal" "dev") ;;
+ server)
+ groups=("common" "essentials" "minimal" "server") ;;
+ full)
+ groups=("common" "essentials" "minimal" "dev" "server" "desktop" "wm" "media" "fonts") ;;
+ *)
+ print_error "Unknown profile: $profile"
+ return 1
+ ;;
+ esac
+ fi
+
+ printf '%s\n' "${groups[@]}"
+}
+
install_packages_from_yaml() {
local packages_file="$1"
local profile="${2:-essentials}"
@@ -874,48 +1106,35 @@ install_packages_from_yaml() {
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
+ # Get package groups to install based on profile
+ local groups
+ mapfile -t groups < <(get_profile_package_groups "$packages_file" "$profile")
+
+ if [[ ${#groups[@]} -eq 0 ]]; then
+ print_error "No package groups found for profile: $profile"
+ return 1
+ fi
+
+ print_info "Installing package groups for $profile: ${groups[*]}"
- # Install packages from each section
- for section in "${sections[@]}"; do
- print_info "Installing packages from section: $section"
+ # Install packages from each group
+ for group in "${groups[@]}"; do
+ print_info "Installing packages from group: $group"
local packages
- mapfile -t packages < <(parse_packages_from_yaml "$packages_file" "$section")
+ mapfile -t packages < <(parse_packages_from_yaml "$packages_file" "$group")
if [[ ${#packages[@]} -eq 0 ]]; then
- print_info "No packages found in section: $section"
+ print_info "No packages found in group: $group"
continue
fi
- print_info "Found ${#packages[@]} packages in section $section"
+ print_info "Found ${#packages[@]} packages in group $group: ${packages[*]}"
for package in "${packages[@]}"; do
[[ -z "$package" ]] && continue
- if install_single_package "$package" "$section" "$packages_file"; then
+ if install_single_package "$package" "$group" "$packages_file"; then
print_success "Installed: $package"
((installed_count++))
else
@@ -925,6 +1144,11 @@ install_packages_from_yaml() {
done
done
+ # Handle development environment setup
+ if yq eval ".profiles.$profile.enable_development" "$packages_file" 2>/dev/null | grep -q "true"; then
+ setup_development_environment "$packages_file"
+ fi
+
print_info "Package installation summary:"
print_color "$GREEN" " Installed: $installed_count"
print_color "$RED" " Failed: ${#failed_packages[@]}"
@@ -943,67 +1167,108 @@ install_packages_from_yaml() {
# Dotfiles Management System (Config Command)
#======================================
-install_config_command() {
- print_info "Installing config command for dotfiles management"
+check_existing_config_command() {
+ print_info "Checking for existing config command..."
- # Known function files where cfg might already be defined
+ # Known function files where config might already be defined
local function_files=(
"$HOME/.config/zsh/user/functions.zsh"
+ "$HOME/.config/zsh/.zshrc"
+ "$HOME/.zshrc"
"$HOME/.bashrc"
+ "$HOME/.profile"
)
- # Check if cfg is already defined
- local cfg_defined=false
+ # Check if config command is already available in current shell
+ if type config >/dev/null 2>&1; then
+ CONFIG_COMMAND_AVAILABLE=true
+ print_success "Config command already available in current shell"
+ return 0
+ fi
+
+ # Check files for existing config function definition
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"
+ if [[ -f "$f" ]]; then
+ if grep -q '^\s*config\s*()' "$f" || grep -q '# Dotfiles Management System' "$f"; then
+ CONFIG_COMMAND_AVAILABLE=true
+ CONFIG_COMMAND_FILE="$f"
+ print_success "Config command found in: $f"
+ # Do NOT source user shell files here to avoid early exits or side-effects.
+ # We'll rely on fallbacks (git/manual deploy) if the function is not in the current shell.
+ return 0
fi
- break
fi
done
- if [[ "$cfg_defined" == true ]]; then
- print_info "cfg function already defined, no need to append"
- return
+ CONFIG_COMMAND_AVAILABLE=false
+ print_info "No existing config command found"
+ return 1
+}
+
+install_config_command() {
+ print_section "Installing Config Command"
+
+ if check_existing_config_command; then
+ if [[ "$FORCE_MODE" == true ]]; then
+ print_info "Force mode: reinstalling config command"
+ else
+ return 0
+ fi
fi
- # Determine current shell
+ # Determine current shell and profile file
local current_shell
current_shell=$(basename "$SHELL")
- local profile_files=()
-
+ local profile_file=""
case "$current_shell" in
bash)
- profile_files+=("$HOME/.bashrc")
- [[ -f "$HOME/.profile" ]] && profile_files+=("$HOME/.profile")
+ if [[ -f "$HOME/.bashrc" ]]; then
+ profile_file="$HOME/.bashrc"
+ else
+ profile_file="$HOME/.bashrc"
+ touch "$profile_file"
+ fi
;;
zsh)
- profile_files+=("$HOME/.zshrc")
- [[ -f "$HOME/.config/zsh/.zshrc" ]] && profile_files+=("$HOME/.config/zsh/.zshrc")
- [[ -f "$HOME/.profile" ]] && profile_files+=("$HOME/.profile")
+ if [[ -f "$HOME/.config/zsh/user/functions.zsh" ]]; then
+ profile_file="$HOME/.config/zsh/user/functions.zsh"
+ elif [[ -f "$HOME/.config/zsh/.zshrc" ]]; then
+ profile_file="$HOME/.config/zsh/.zshrc"
+ elif [[ -f "$HOME/.zshrc" ]]; then
+ profile_file="$HOME/.zshrc"
+ else
+ profile_file="$HOME/.zshrc"
+ touch "$profile_file"
+ fi
;;
*)
- [[ -f "$HOME/.profile" ]] && profile_files+=("$HOME/.profile")
+ if [[ -f "$HOME/.profile" ]]; then
+ profile_file="$HOME/.profile"
+ else
+ profile_file="$HOME/.profile"
+ touch "$profile_file"
+ fi
;;
esac
- # If no profile files exist, create .bashrc
- if [[ ${#profile_files[@]} -eq 0 ]]; then
- profile_files+=("$HOME/.bashrc")
- touch "$HOME/.bashrc"
+ if [[ ! -w "$profile_file" ]]; then
+ print_error "Cannot write to profile file: $profile_file"
+ return 1
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'
+ # Check if config function already exists in the target file
+ if grep -q "# Dotfiles Management System" "$profile_file" 2>/dev/null; then
+ print_info "Config function already exists in $profile_file"
+ CONFIG_COMMAND_AVAILABLE=true
+ CONFIG_COMMAND_FILE="$profile_file"
+ return 0
+ fi
+
+ print_info "Adding config function to: $profile_file"
+
+ # Add the config function
+ cat >> "$profile_file" << 'EOF'
# Dotfiles Management System
if [[ -d "$HOME/.cfg" && -d "$HOME/.cfg/refs" ]]; then
@@ -1125,6 +1390,7 @@ if [[ -d "$HOME/.cfg" && -d "$HOME/.cfg/refs" ]]; then
config() {
local cmd="$1"; shift
local target_dir=""
+
# Parse optional --target flag for add
if [[ "$cmd" == "add" ]]; then
while [[ "$1" == --* ]]; do
@@ -1242,9 +1508,8 @@ if [[ -d "$HOME/.cfg" && -d "$HOME/.cfg/refs" ]]; then
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
+ local sys_file="$(_sys_path "$repo_file")"
- # Only continue if the source exists
if [[ -e "$full_repo_path" && -n "$sys_file" ]]; then
local dest_dir
dest_dir="$(dirname "$sys_file")"
@@ -1307,37 +1572,93 @@ if [[ -d "$HOME/.cfg" && -d "$HOME/.cfg/refs" ]]; then
}
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
-}
+ if [[ $? -eq 0 ]]; then
+ print_success "Config command added to: $profile_file"
+ CONFIG_COMMAND_AVAILABLE=true
+ CONFIG_COMMAND_FILE="$profile_file"
+ # Source the file to make config command available immediately
+ # shellcheck disable=SC1090
+ source "$profile_file" 2>/dev/null || print_warning "Failed to source $profile_file"
+
+ return 0
+ else
+ print_error "Failed to add config command to $profile_file"
+ return 1
+ fi
+}
deploy_config() {
print_section "Deploying Configuration"
save_state "deploy_config" "started"
- # Install and setup the config command first
- install_config_command
+ # Ensure config command is available
+ if [[ "$CONFIG_COMMAND_AVAILABLE" != true ]]; then
+ install_config_command || {
+ print_error "Failed to install config command"
+ mark_step_failed "deploy_config"
+ return 1
+ }
+ fi
# Deploy dotfiles from repository to system
if [[ -d "$DOTFILES_DIR" ]]; then
- print_info "Deploying dotfiles from repository to system locations..."
+ print_info "Checking out dotfiles from repository..."
- # Source shell configuration to make config function available
+ # Reload 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..."
+ # First, checkout files from the bare repository to restore directory structure
+ if [[ "$DRY_RUN" == true ]]; then
+ print_dry_run "config checkout"
+ else
+ # Source the config function if available
+ if type config >/dev/null 2>&1; then
+ print_info "Using config command to checkout files..."
+ if config checkout; then
+ print_success "Files checked out from repository"
+ else
+ print_warning "Some files may have failed to checkout, trying force checkout..."
+ config checkout -f || print_warning "Force checkout also had issues"
+ fi
+ else
+ # Fallback: use git directly
+ print_info "Using git directly to checkout files..."
+ if git --git-dir="$DOTFILES_DIR" --work-tree="$DOTFILES_DIR" checkout HEAD -- . 2>/dev/null; then
+ print_success "Files checked out using git directly"
+ else
+ print_warning "Git checkout had issues, continuing anyway..."
+ fi
+ fi
+ fi
+
+ # Backup existing files prior to deployment (prompt, allow skip)
+ if [[ "$DRY_RUN" == true ]]; then
+ print_dry_run "Backup existing dotfiles prior to deployment"
+ else
+ if [[ "$FORCE_MODE" == true ]]; then
+ # In force mode, perform backup without prompting
+ backup_existing_dotfiles || print_warning "Backup encountered issues (continuing)"
+ else
+ if prompt_user "Backup existing dotfiles before deployment?"; then
+ backup_existing_dotfiles || print_warning "Backup encountered issues (continuing)"
+ else
+ print_skip "User chose to skip backup before deployment"
+ fi
+ fi
+ fi
+
+ print_info "Deploying dotfiles from repository to system locations..."
+
+ # Verify config command is working
+ if ! verify_config_command; then
+ print_warning "Config command not working properly, using manual deployment"
+ manual_deploy_dotfiles
+ else
+ print_info "Config command 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
@@ -1347,9 +1668,6 @@ deploy_config() {
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
@@ -1362,6 +1680,16 @@ deploy_config() {
mark_step_completed "deploy_config"
}
+verify_config_command() {
+ if type config >/dev/null 2>&1; then
+ print_success "Config command is available and working"
+ return 0
+ else
+ print_warning "Config command not available"
+ return 1
+ fi
+}
+
reload_shell_config() {
print_info "Reloading shell configuration..."
@@ -1389,6 +1717,132 @@ reload_shell_config() {
done
}
+# Manual deployment function (fallback when config command not available)
+manual_deploy_dotfiles() {
+ print_info "Using manual deployment method..."
+
+ if [[ ! -d "$DOTFILES_DIR" ]]; then
+ print_error "Dotfiles directory not found: $DOTFILES_DIR"
+ return 1
+ fi
+
+ local os_dir="$DOTFILES_DIR/$CFG_OS"
+ local common_dir="$DOTFILES_DIR/common"
+
+ deploy_file() {
+ local repo_file="$1"
+ local rel_path sys_file sys_dir base
+
+ # Determine destination based on repo path
+ rel_path="${repo_file#$DOTFILES_DIR/}"
+
+ # OS-specific files outside home
+ if [[ "$rel_path" == "$CFG_OS/"* && "$rel_path" != */home/* ]]; then
+ sys_file="/${rel_path#$CFG_OS/}"
+ else
+ case "$rel_path" in
+ common/config/*)
+ case "$CFG_OS" in
+ linux)
+ base="${XDG_CONFIG_HOME:-$HOME/.config}"
+ sys_file="$base/${rel_path#common/config/}"
+ ;;
+ macos)
+ sys_file="$HOME/Library/Application Support/${rel_path#common/config/}"
+ ;;
+ windows)
+ sys_file="$LOCALAPPDATA\\${rel_path#common/config/}"
+ ;;
+ *)
+ sys_file="$HOME/.config/${rel_path#common/config/}"
+ ;;
+ esac
+ ;;
+ common/assets/*)
+ sys_file="$HOME/.cfg/$rel_path"
+ ;;
+ common/*)
+ sys_file="$HOME/${rel_path#common/}"
+ ;;
+ */home/*)
+ sys_file="$HOME/${rel_path#*/home/}"
+ ;;
+ profile/*|README.md)
+ sys_file="$HOME/.cfg/$rel_path"
+ ;;
+ *)
+ sys_file="$HOME/.cfg/$rel_path"
+ ;;
+ esac
+ fi
+
+ sys_dir="$(dirname "$sys_file")"
+ mkdir -p "$sys_dir"
+
+ # Copy with privilege if path is system (/etc, /usr, etc.)
+ if [[ "$sys_file" == /* ]]; then
+ # If we lack a privilege tool and are not root, skip with clear message
+ if [[ -z "$PRIVILEGE_TOOL" && "$EUID" -ne 0 ]]; then
+ print_skip "Skipping privileged deploy (no sudo/doas): $rel_path -> $sys_file"
+ else
+ execute_with_privilege "cp -a '$repo_file' '$sys_file'" \
+ && print_info "Deployed (privileged): $rel_path" \
+ || print_error "Failed to deploy (privileged): $rel_path"
+ fi
+ else
+ cp -a "$repo_file" "$sys_file" \
+ && print_info "Deployed: $rel_path" \
+ || print_error "Failed to deploy: $rel_path"
+ fi
+ }
+
+ # Deploy all files in OS dir
+ if [[ -d "$os_dir" ]]; then
+ find "$os_dir" -type f | while read -r f; do
+ deploy_file "$f"
+ done
+ fi
+
+ # Deploy all files in common dir
+ if [[ -d "$common_dir" ]]; then
+ find "$common_dir" -type f | while read -r f; do
+ deploy_file "$f"
+ done
+ fi
+}
+
+# Set appropriate file permissions
+set_dotfile_permissions() {
+ print_info "Setting appropriate file permissions..."
+
+ # SSH directory permissions
+ if [[ -d "$HOME/.ssh" ]]; then
+ chmod 700 "$HOME/.ssh"
+ find "$HOME/.ssh" -name "id_*" -not -name "*.pub" -exec chmod 600 {} \; 2>/dev/null || true
+ find "$HOME/.ssh" -name "*.pub" -exec chmod 644 {} \; 2>/dev/null || true
+ find "$HOME/.ssh" -name "config" -exec chmod 600 {} \; 2>/dev/null || true
+ print_info "SSH permissions set"
+ fi
+
+ # GPG directory permissions
+ if [[ -d "$HOME/.gnupg" ]]; then
+ chmod 700 "$HOME/.gnupg"
+ find "$HOME/.gnupg" -type f -exec chmod 600 {} \; 2>/dev/null || true
+ print_info "GPG permissions set"
+ fi
+
+ # Make scripts executable
+ if [[ -d "$HOME/.local/bin" ]]; then
+ find "$HOME/.local/bin" -type f -exec chmod +x {} \; 2>/dev/null || true
+ print_info "Script permissions set"
+ fi
+
+ if [[ -d "$HOME/.scripts" ]]; then
+ find "$HOME/.scripts" -type f -name "*.sh" -exec chmod +x {} \; 2>/dev/null || true
+ print_info "Shell script permissions set"
+ fi
+}
+
#======================================
# Installation Step Functions
#======================================
@@ -1500,10 +1954,11 @@ setup_user_dirs() {
create_dir "$HOME/$dir"
done
- # Set up XDG directories
+ # Set up XDG directories (ensure existence; no deletions)
if command_exists xdg-user-dirs-update; then
- execute_command "xdg-user-dirs-update"
- print_success "XDG user directories configured"
+ # Suppress tool output to avoid misleading terms like "removed"; we only ensure presence.
+ execute_command "xdg-user-dirs-update >/dev/null 2>&1 || true"
+ print_success "Ensured XDG user directories exist (standardized names may be renamed, never removed)"
fi
mark_step_completed "setup_user_dirs"
@@ -1513,8 +1968,43 @@ install_essentials() {
print_section "Installing Essential Tools"
save_state "install_essentials" "started"
+ # Fast-path: determine if any package tools are actually missing
+ local missing_tools=()
+ for tool in "${PACKAGE_TOOLS[@]}"; do
+ if [[ "$tool" == "yq" ]]; then
+ if command_exists yq || [[ -x "$HOME/.local/bin/yq" ]]; then
+ continue
+ fi
+ elif [[ "$tool" == "jq" ]]; then
+ if command_exists jq || is_package_installed jq; then
+ continue
+ fi
+ fi
+ if ! command_exists "$tool"; then
+ missing_tools+=("$tool")
+ fi
+ done
+
+ if [[ ${#missing_tools[@]} -eq 0 ]]; then
+ print_skip "All essential tools are already installed"
+ mark_step_completed "install_essentials"
+ return 0
+ fi
+
# Install package processing tools first
for tool in "${PACKAGE_TOOLS[@]}"; do
+ if [[ "$tool" == "yq" ]]; then
+ if command_exists yq || [[ -x "$HOME/.local/bin/yq" ]]; then
+ print_info "Package tool already available: yq"
+ continue
+ fi
+ elif [[ "$tool" == "jq" ]]; then
+ if command_exists jq || is_package_installed jq; then
+ print_info "Package tool already available: jq"
+ continue
+ fi
+ fi
+
if ! command_exists "$tool"; then
case "$tool" in
yq)
@@ -1527,7 +2017,9 @@ install_essentials() {
fi
;;
jq)
- if install_single_package "jq" "essential"; then
+ if command_exists jq || is_package_installed jq; then
+ print_info "Package tool already available: jq"
+ elif install_single_package "jq" "essential"; then
print_success "Installed package tool: $tool"
else
print_error "Failed to install package tool: $tool"
@@ -1562,12 +2054,6 @@ 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
@@ -1584,15 +2070,86 @@ install_packages() {
done
if [[ -n "$found_packages_file" ]]; then
- if install_packages_from_yaml "$found_packages_file" "$profile"; then
+ # Handle custom installs first
+ handle_custom_installs "$found_packages_file"
+
+ # Install packages
+ if install_packages_from_yaml "$found_packages_file" "$INSTALL_MODE"; 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
+ mark_step_completed "install_packages"
fi
else
- print_warning "packages.yml not found, skipping package installation"
- mark_step_completed "install_packages"
+ print_warning "packages.yml not found, attempting to download from GitHub..."
+
+ # Derive raw URL from DOTFILES_URL
+ # Supports formats like:
+ # https://github.com/<owner>/<repo>.git
+ # git@github.com:<owner>/<repo>.git
+ # https://github.com/<owner>/<repo>
+ local owner repo branch
+ branch="main"
+ case "$DOTFILES_URL" in
+ git@github.com:*)
+ owner="${DOTFILES_URL#git@github.com:}"
+ owner="${owner%.git}"
+ repo="${owner#*/}"
+ owner="${owner%%/*}"
+ ;;
+ https://github.com/*)
+ owner="${DOTFILES_URL#https://github.com/}"
+ owner="${owner%.git}"
+ repo="${owner#*/}"
+ owner="${owner%%/*}"
+ ;;
+ *)
+ owner=""
+ repo=""
+ ;;
+ esac
+
+ local packages_url=""
+ if [[ -n "$owner" && -n "$repo" ]]; then
+ packages_url="https://raw.githubusercontent.com/$owner/$repo/$branch/common/packages.yml"
+ fi
+ local temp_packages="/tmp/packages.yml"
+
+ if command_exists curl && [[ -n "$packages_url" ]]; then
+ if curl -fsSL "$packages_url" -o "$temp_packages" 2>/dev/null; then
+ # Create common directory if it doesn't exist
+ mkdir -p "$HOME/.cfg/common" 2>/dev/null || mkdir -p "$HOME/common" 2>/dev/null
+
+ # Move to appropriate location
+ if [[ -d "$HOME/.cfg/common" ]]; then
+ mv "$temp_packages" "$HOME/.cfg/common/packages.yml"
+ found_packages_file="$HOME/.cfg/common/packages.yml"
+ elif [[ -d "$HOME/common" ]]; then
+ mv "$temp_packages" "$HOME/common/packages.yml"
+ found_packages_file="$HOME/common/packages.yml"
+ else
+ mv "$temp_packages" "$HOME/packages.yml"
+ found_packages_file="$HOME/packages.yml"
+ fi
+
+ print_success "Downloaded packages.yml from GitHub"
+
+ # Now install packages with the downloaded file
+ handle_custom_installs "$found_packages_file"
+ if install_packages_from_yaml "$found_packages_file" "$INSTALL_MODE"; then
+ mark_step_completed "install_packages"
+ else
+ print_warning "Some packages failed to install, but continuing..."
+ mark_step_completed "install_packages"
+ fi
+ else
+ print_warning "Failed to download packages.yml, skipping package installation"
+ mark_step_completed "install_packages"
+ fi
+ else
+ print_warning "curl not available and packages.yml not found, skipping package installation"
+ mark_step_completed "install_packages"
+ fi
fi
cd "$original_dir" 2>/dev/null || true
@@ -1602,64 +2159,53 @@ setup_shell() {
print_section "Setting Up Shell Environment"
save_state "setup_shell" "started"
+ # Ensure config command is available before changing shells
+ if [[ "$CONFIG_COMMAND_AVAILABLE" != true ]]; then
+ print_warning "Config command not available, installing it first..."
+ install_config_command || {
+ print_error "Failed to install config command before shell setup"
+ mark_step_failed "setup_shell"
+ return 1
+ }
+ fi
+
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)"
+ local zsh_path
+ zsh_path="$(command -v zsh)"
+
+ if [[ "$FORCE_MODE" == true ]]; then
+ print_info "FORCE mode: changing default shell to Zsh without prompting"
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
+ elif [[ "$ASK_MODE" == true ]]; then
+ if prompt_user "Change default shell to Zsh?" "N"; then
+ 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
+ else
+ print_skip "Default shell change (user chose No)"
+ fi
+ else
+ print_info "Skipping shell change (non-interactive mode). Use --ask to be prompted or --force to auto-change."
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
+ # Zsh plugins are managed via packages.yml custom_installs (zsh_plugins)
+ # No direct plugin installation here to avoid duplication.
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
-}
+## install_zsh_plugins deprecated; handled via packages.yml
setup_ssh() {
print_section "Setting Up SSH"
@@ -1775,8 +2321,14 @@ manage_service() {
return $((1 - success))
}
-# Configure system services
-configure_services() {
+#======================================
+# Service Management Functions
+#======================================
+
+configure_services_from_yaml() {
+ local packages_file="$1"
+ local profile="$2"
+
print_section "Configuring System Services"
save_state "configure_services" "started"
@@ -1786,63 +2338,90 @@ configure_services() {
return 0
fi
- # Detect the init system once
+ if [[ ! -f "$packages_file" ]]; then
+ print_warning "Package file not found, skipping service configuration"
+ mark_step_completed "configure_services"
+ return 0
+ fi
+
+ # Detect the init system
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"
+ # Get services to enable for all profiles
+ local services_all
+ mapfile -t services_all < <(yq eval ".services.enable.all[]" "$packages_file" 2>/dev/null | grep -v "^null$" || true)
+
+ # Get services to enable for specific profile
+ local services_profile
+ mapfile -t services_profile < <(yq eval ".services.enable.$profile[]" "$packages_file" 2>/dev/null | grep -v "^null$" || true)
+
+ # Get services to disable for specific profile
+ local services_disable
+ mapfile -t services_disable < <(yq eval ".services.disable.$profile[]" "$packages_file" 2>/dev/null | grep -v "^null$" || true)
+
+ # Enable services
+ for service in "${services_all[@]}" "${services_profile[@]}"; do
+ [[ -z "$service" ]] && continue
+ if [[ "$FORCE_MODE" == true ]] || prompt_user "Enable $service service?"; then
+ 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 TLP"
+ print_error "Failed to enable $service"
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
+ done
- if command_exists tlp; then
- manage_service "enable" "tlp" "$INIT_SYSTEM"
- manage_service "start" "tlp" "$INIT_SYSTEM"
- print_success "TLP installed, enabled and started"
+ # Disable services
+ for service in "${services_disable[@]}"; do
+ [[ -z "$service" ]] && continue
+ if [[ "$FORCE_MODE" == true ]] || prompt_user "Disable $service service?"; then
+ if manage_service "stop" "$service" "$INIT_SYSTEM"; then
+ manage_service "disable" "$service" "$INIT_SYSTEM"
+ print_success "Stopped and disabled $service"
+ else
+ print_error "Failed to disable $service"
+ fi
fi
- fi
+ done
- # Configure other useful services
- local services_to_enable=()
+ mark_step_completed "configure_services"
+}
- # 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
+configure_services() {
+ # Change to home directory to find packages.yml
+ local original_dir="$PWD"
+ cd "$HOME" 2>/dev/null || true
- if command_exists bluetooth; then
- if [[ "$FORCE_MODE" == true ]] || prompt_user "Enable Bluetooth service?"; then
- services_to_enable+=("bluetooth")
- fi
- fi
+ local packages_files=("$PACKAGES_FILE" "common/$PACKAGES_FILE" ".cfg/common/$PACKAGES_FILE")
+ local found_packages_file=""
- # 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"
+ for pf in "${packages_files[@]}"; do
+ if [[ -f "$pf" ]]; then
+ found_packages_file="$pf"
+ break
fi
done
- mark_step_completed "configure_services"
+ if [[ -n "$found_packages_file" ]]; then
+ configure_services_from_yaml "$found_packages_file" "$INSTALL_MODE"
+ else
+ # Fallback to original configure_services logic
+ 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
+
+ # Original service configuration logic here...
+ mark_step_completed "configure_services"
+ fi
+
+ cd "$original_dir" 2>/dev/null || true
}
setup_tmux_plugins() {
@@ -1875,33 +2454,272 @@ setup_tmux_plugins() {
fi
}
-setup_development() {
- print_section "Setting Up Development Environment"
- save_state "setup_development" "started"
+#======================================
+# Development Environment Setup
+#======================================
+
+
+setup_development_environment() {
+ # Accept optional packages_file argument. If missing, try to locate a default.
+ local packages_file="${1:-}"
+ if [[ -z "$packages_file" ]]; then
+ local candidates=("$HOME/$PACKAGES_FILE" "$HOME/common/$PACKAGES_FILE" "$HOME/.cfg/common/$PACKAGES_FILE")
+ for pf in "${candidates[@]}"; do
+ if [[ -f "$pf" ]]; then
+ packages_file="$pf"
+ break
+ fi
+ done
+ fi
+
+ print_info "Setting up development environment"
+
+ if [[ -z "$packages_file" || ! -f "$packages_file" ]]; then
+ print_warning "Package file not found, skipping development setup"
+ return 0
+ fi
+
+ # Apply git configuration
+ local git_configs
+ if command_exists yq; then
+ mapfile -t git_configs < <(yq eval ".development.git_config[]" "$packages_file" 2>/dev/null | grep -v "^null$" || true)
+ else
+ git_configs=()
+ fi
+
+ if [[ ${#git_configs[@]} -gt 0 ]] && command_exists git; then
+ print_info "Applying git configuration"
+ for config in "${git_configs[@]}"; do
+ [[ -z "$config" ]] && continue
+ print_info "Running: $config"
+ execute_command "$config"
+ done
+ fi
+}
+
+# Backup existing files that will be affected by deployment
+backup_existing_dotfiles() {
+ local backup_root="$BACKUP_DIR/pre-deploy"
+ local os_dir="$DOTFILES_DIR/$CFG_OS"
+ local common_dir="$DOTFILES_DIR/common"
- # Git configuration
+ print_info "Creating backup at: $backup_root"
+ mkdir -p "$backup_root" 2>/dev/null || true
+
+ # Helper to compute destination path similar to manual_deploy_dotfiles
+ _compute_dest_path() {
+ local repo_file="$1"
+ local rel_path sys_file base
+ rel_path="${repo_file#$DOTFILES_DIR/}"
+
+ if [[ "$rel_path" == "$CFG_OS/"* && "$rel_path" != */home/* ]]; then
+ sys_file="/${rel_path#$CFG_OS/}"
+ else
+ case "$rel_path" in
+ common/config/*)
+ case "$CFG_OS" in
+ linux)
+ base="${XDG_CONFIG_HOME:-$HOME/.config}"
+ sys_file="$base/${rel_path#common/config/}"
+ ;;
+ macos)
+ sys_file="$HOME/Library/Application Support/${rel_path#common/config/}"
+ ;;
+ windows)
+ sys_file="$LOCALAPPDATA\\${rel_path#common/config/}"
+ ;;
+ *)
+ sys_file="$HOME/.config/${rel_path#common/config/}"
+ ;;
+ esac
+ ;;
+ common/assets/*)
+ sys_file="$HOME/.cfg/$rel_path"
+ ;;
+ common/*)
+ sys_file="$HOME/${rel_path#common/}"
+ ;;
+ */home/*)
+ sys_file="$HOME/${rel_path#*/home/}"
+ ;;
+ profile/*|README.md)
+ sys_file="$HOME/.cfg/$rel_path"
+ ;;
+ *)
+ sys_file="$HOME/.cfg/$rel_path"
+ ;;
+ esac
+ fi
+
+ echo "$sys_file"
+ }
+
+ _backup_one() {
+ local repo_file="$1"
+ local dest
+ dest=$(_compute_dest_path "$repo_file")
+ [[ -z "$dest" ]] && return 0
+
+ if [[ -e "$dest" ]]; then
+ local rel_path="${repo_file#$DOTFILES_DIR/}"
+ local backup_path="$backup_root/$rel_path"
+ local backup_dir
+ backup_dir="$(dirname "$backup_path")"
+ mkdir -p "$backup_dir" 2>/dev/null || true
+
+ if [[ "$dest" == /* ]]; then
+ execute_with_privilege "cp -a '$dest' '$backup_path'" \
+ && print_info "Backed up (privileged): $rel_path" \
+ || print_warning "Failed to backup (privileged): $rel_path"
+ else
+ cp -a "$dest" "$backup_path" \
+ && print_info "Backed up: $rel_path" \
+ || print_warning "Failed to backup: $rel_path"
+ fi
+ fi
+ }
+
+ # Backup files from OS dir
+ if [[ -d "$os_dir" ]]; then
+ find "$os_dir" -type f | while read -r f; do
+ _backup_one "$f"
+ done
+ fi
+
+ # Backup files from common dir
+ if [[ -d "$common_dir" ]]; then
+ find "$common_dir" -type f | while read -r f; do
+ _backup_one "$f"
+ done
+ fi
+
+ print_success "Backup completed at: $backup_root"
+}
+
+install_rust_development() {
+ local packages_file="$1"
+
+ if ! command_exists rustc; then
+ install_rust
+ fi
+
+ if command_exists cargo; then
+ print_info "Installing Rust components"
+ local components
+ mapfile -t components < <(yq eval ".development.rust.components[]" "$packages_file" 2>/dev/null | grep -v "^null$" || true)
+
+ for component in "${components[@]}"; do
+ [[ -z "$component" ]] && continue
+ execute_command "rustup component add $component"
+ done
+ fi
+}
+
+install_nodejs_development() {
+ local packages_file="$1"
+
+ if ! command_exists node; then
+ install_nvm
+ install_node
+ fi
+
+ if command_exists npm; then
+ print_info "Installing global Node.js packages"
+ local packages
+ mapfile -t packages < <(yq eval ".development.nodejs.global_packages[]" "$packages_file" 2>/dev/null | grep -v "^null$" || true)
+
+ for package in "${packages[@]}"; do
+ [[ -z "$package" ]] && continue
+ execute_command "npm install -g $package"
+ done
+ fi
+}
+
+install_python_development() {
+ local packages_file="$1"
+
+ if command_exists pip || command_exists pip3; then
+ print_info "Installing global Python packages"
+ local packages
+ mapfile -t packages < <(yq eval ".development.python.global_packages[]" "$packages_file" 2>/dev/null | grep -v "^null$" || true)
+
+ local pip_cmd="pip3"
+ command_exists pip3 || pip_cmd="pip"
+
+ for package in "${packages[@]}"; do
+ [[ -z "$package" ]] && continue
+ execute_command "$pip_cmd install --user $package"
+ done
+ fi
+}
+
+get_git_email_guess() {
+ local email_guess=""
+
+ # Try to get email from existing git config
if command_exists git; then
- if [[ "$FORCE_MODE" == true ]] || prompt_user "Configure Git global settings?"; then
- configure_git
+ email_guess=$(git config --global user.email 2>/dev/null || echo "")
+ if [[ -n "$email_guess" ]]; then
+ echo "$email_guess"
+ return 0
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
+ # Try to extract from common email-related environment variables
+ for var in EMAIL MAIL USER_EMAIL GIT_AUTHOR_EMAIL GIT_COMMITTER_EMAIL; do
+ if [[ -n "${!var:-}" ]]; then
+ echo "${!var}"
+ return 0
+ fi
+ done
+
+ # Check for email in /etc/passwd gecos field
+ if [[ -f /etc/passwd ]]; then
+ local gecos
+ gecos=$(getent passwd "$USER" 2>/dev/null | cut -d: -f5 | cut -d, -f1)
+ if [[ "$gecos" == *@* ]]; then
+ echo "$gecos"
+ return 0
+ fi
+ fi
+
+ # Try to guess based on common patterns
+ local domain=""
+
+ # Check if we can determine domain from hostname
+ if command_exists hostname; then
+ local fqdn
+ fqdn=$(hostname -f 2>/dev/null || hostname 2>/dev/null || echo "")
+ if [[ "$fqdn" == *.* ]]; then
+ domain="${fqdn#*.}"
+ fi
+ fi
+
+ # Fallback domain guessing
+ if [[ -z "$domain" ]]; then
+ if [[ -f /etc/mailname ]]; then
+ domain=$(cat /etc/mailname 2>/dev/null || echo "")
+ elif [[ -f /etc/hostname ]]; then
+ local hostname_file
+ hostname_file=$(cat /etc/hostname 2>/dev/null || echo "")
+ if [[ "$hostname_file" == *.* ]]; then
+ domain="${hostname_file#*.}"
+ fi
+ fi
+ fi
+
+ # Final fallback
+ if [[ -z "$domain" ]]; then
+ domain="localhost"
+ fi
- mark_step_completed "setup_development"
+ echo "${USER}@${domain}"
}
configure_git() {
local git_name="${USER}"
- local git_email="${USER}@${HOSTNAME:-$(hostname)}"
+ local git_email
+ git_email=$(get_git_email_guess)
if [[ "$FORCE_MODE" != true ]]; then
print_color "$YELLOW" "Enter your Git username [$git_name]: "
@@ -2048,22 +2866,85 @@ install_yarn() {
fi
}
+#======================================
+# System Tweaks Functions
+#======================================
+
+apply_system_tweaks() {
+ local packages_file="$1"
+
+ print_section "Applying System Tweaks"
+
+ if [[ ! -f "$packages_file" ]]; then
+ print_warning "Package file not found, skipping system tweaks"
+ return 0
+ fi
+
+ # Detect desktop environment and apply appropriate tweaks
+ local desktop_env=""
+ if [[ "$XDG_CURRENT_DESKTOP" == *"GNOME"* ]] || command_exists gnome-shell; then
+ desktop_env="gnome"
+ elif [[ "$XDG_CURRENT_DESKTOP" == *"KDE"* ]] || command_exists plasmashell; then
+ desktop_env="kde"
+ fi
+
+ if [[ -n "$desktop_env" ]]; then
+ print_info "Applying $desktop_env tweaks"
+
+ # Get tweak commands for the desktop environment
+ local tweaks
+ mapfile -t tweaks < <(yq eval ".system_tweaks.$desktop_env[]" "$packages_file" 2>/dev/null | grep -v "^null$" || true)
+
+ for tweak in "${tweaks[@]}"; do
+ [[ -z "$tweak" ]] && continue
+ print_info "Applying tweak: $tweak"
+ if execute_command "$tweak"; then
+ print_success "Applied: $tweak"
+ else
+ print_warning "Failed to apply: $tweak"
+ fi
+ done
+ else
+ print_info "No supported desktop environment detected for tweaks"
+ 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
+ # Change to home directory to find packages.yml
+ local original_dir="$PWD"
+ cd "$HOME" 2>/dev/null || true
+
+ 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
+ apply_system_tweaks "$found_packages_file"
+ else
+ case "$CFG_OS" in
+ linux)
+ apply_linux_tweaks
+ ;;
+ macos)
+ apply_macos_tweaks
+ ;;
+ *)
+ print_info "No system tweaks defined for $CFG_OS"
+ ;;
+ esac
+ fi
+ cd "$original_dir" 2>/dev/null || true
mark_step_completed "apply_tweaks"
}
@@ -2083,38 +2964,146 @@ apply_linux_tweaks() {
fi
fi
- # --- Power / Display timeout tweaks ---
- if command -v gsettings >/dev/null 2>&1; then
- print_info "Setting GNOME power/display timeouts to 'never'"
+ # Desktop environment tweaks should be declared in packages.yml under system_tweaks.
+ # This function keeps only essential, non-DE specific items. Use apply_system_tweaks
+ # to apply YAML-driven commands.
+ print_info "Linux system tweaks applied (core). Desktop tweaks come from packages.yml."
+}
- # Turn off blank screen
- gsettings set org.gnome.desktop.session idle-delay 0
+apply_macos_tweaks() {
+ print_info "macOS system tweaks applied (placeholder)"
+}
- # 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'
+#======================================
+# Custom Installation Functions
+#======================================
- print_success "GNOME power/display settings applied"
- else
- print_info "gsettings not found; skipping GNOME power/display tweaks"
+handle_custom_installs() {
+ local packages_file="$1"
+
+ if [[ ! -f "$packages_file" ]] || ! command_exists yq; then
+ return 0
fi
- print_info "Linux system tweaks applied"
-}
+ print_info "Processing custom installations..."
-apply_macos_tweaks() {
- print_info "macOS system tweaks applied (placeholder)"
+ # Get custom install commands
+ local custom_installs
+ mapfile -t custom_installs < <(yq eval ".custom_installs | keys | .[]" "$packages_file" 2>/dev/null | grep -v "^null$" || true)
+
+ for install_name in "${custom_installs[@]}"; do
+ [[ -z "$install_name" ]] && continue
+
+ # Check condition
+ local condition
+ condition=$(yq eval ".custom_installs.$install_name.condition" "$packages_file" 2>/dev/null | grep -v "^null$" || echo "")
+
+ if [[ -n "$condition" ]]; then
+ if ! eval "$condition" 2>/dev/null; then
+ print_info "Skipping $install_name (condition not met)"
+ continue
+ fi
+ fi
+
+ # Get OS-specific command
+ local install_cmd=""
+ case "$CFG_OS" in
+ linux)
+ install_cmd=$(yq eval ".custom_installs.$install_name.linux" "$packages_file" 2>/dev/null | grep -v "^null$" || echo "")
+ ;;
+ macos)
+ install_cmd=$(yq eval ".custom_installs.$install_name.macos" "$packages_file" 2>/dev/null | grep -v "^null$" || echo "")
+ ;;
+ windows)
+ install_cmd=$(yq eval ".custom_installs.$install_name.windows" "$packages_file" 2>/dev/null | grep -v "^null$" || echo "")
+ ;;
+ esac
+
+ # Fallback to generic command
+ if [[ -z "$install_cmd" ]]; then
+ install_cmd=$(yq eval ".custom_installs.$install_name.command" "$packages_file" 2>/dev/null | grep -v "^null$" || echo "")
+ fi
+
+ if [[ -n "$install_cmd" ]]; then
+ print_info "Running custom install: $install_name"
+ if execute_command "$install_cmd"; then
+ print_success "Custom install completed: $install_name"
+ else
+ print_error "Custom install failed: $install_name"
+ fi
+ else
+ print_warning "No install command found for $install_name on $CFG_OS"
+ fi
+ done
}
#======================================
# Installation Mode Selection
#======================================
-select_installation_mode() {
+
+detect_installation_mode() {
if [[ "$INSTALL_MODE" != "ask" ]]; then
return 0 # Mode already set via command line
fi
+ # Check if this is a re-run
+ if [[ -d "$DOTFILES_DIR" && ! "$UPDATE_MODE" == true ]]; then
+ print_section "Existing Installation Detected"
+ print_info "Dotfiles repository already exists at: $DOTFILES_DIR"
+
+ if [[ "$FORCE_MODE" == true ]]; then
+ print_info "Force mode: proceeding with update"
+ UPDATE_MODE=true
+ INSTALL_MODE="essentials" # Default to essentials for updates
+ else
+ while true; do
+ print_color "$YELLOW" "What would you like to do?"
+ print_color "$CYAN" "1. Update existing dotfiles and system"
+ print_color "$CYAN" "2. Full reinstallation"
+ print_color "$CYAN" "3. Exit"
+ print_color "$YELLOW" "Select option [1-3]: "
+ read -r response
+
+ case "$response" in
+ 1)
+ UPDATE_MODE=true
+ INSTALL_MODE="essentials"
+ print_success "Update mode selected"
+ break
+ ;;
+ 2)
+ print_warning "This will backup and reinstall everything"
+ if prompt_user "Continue with full reinstallation?"; then
+ # Backup existing installation
+ local backup_timestamp=$(date +%Y%m%d-%H%M%S)
+ local backup_location="$HOME/.dotfiles-backup-$backup_timestamp"
+ print_info "Backing up existing installation to: $backup_location"
+ cp -r "$DOTFILES_DIR" "$backup_location" 2>/dev/null || true
+ break
+ else
+ continue
+ fi
+ ;;
+ 3)
+ print_info "Installation cancelled by user"
+ exit 0
+ ;;
+ *)
+ print_warning "Invalid selection. Please enter 1-3"
+ ;;
+ esac
+ done
+ fi
+ fi
+
+ # If still asking, show installation mode selection
+ if [[ "$INSTALL_MODE" == "ask" ]]; then
+ select_installation_mode
+ fi
+}
+
+select_installation_mode() {
print_header "Installation Mode Selection"
print_color "$CYAN" "Available installation modes:"
@@ -2162,6 +3151,55 @@ select_installation_mode() {
}
#======================================
+# Ask Mode Implementation
+#======================================
+
+should_run_step() {
+ local step="$1"
+ local description="${INSTALLATION_STEPS[$step]}"
+
+ # Respect explicit skip list
+ if is_step_skipped "$step"; then
+ return 1
+ fi
+
+ # Run-only and run-from controls
+ if [[ -n "$RUN_ONLY_STEP" && "$step" != "$RUN_ONLY_STEP" ]]; then
+ return 1
+ fi
+ if [[ -n "$RUN_FROM_STEP" && "$__RUN_FROM_STARTED" != true ]]; then
+ if [[ "$step" == "$RUN_FROM_STEP" ]]; then
+ __RUN_FROM_STARTED=true
+ else
+ return 1
+ fi
+ fi
+
+ # Skip already completed steps unless forced
+ if is_step_completed "$step" && [[ "$FORCE_MODE" != true ]]; then
+ return 1
+ fi
+
+ # Ask mode prompt
+ if [[ "$ASK_MODE" == true ]]; then
+ prompt_user "Run step: $description?" && return 0 || return 1
+ fi
+
+ # Interactive skip even when not in ask mode (non-essential steps)
+ if [[ "$INTERACTIVE_SKIP" == true ]]; then
+ local is_essential=false
+ for es in "${ESSENTIAL_STEPS[@]}"; do [[ "$es" == "$step" ]] && is_essential=true && break; done
+ if [[ "$is_essential" != true ]]; then
+ if ! prompt_user "Proceed with: $description? (Choose No to skip)"; then
+ return 1
+ fi
+ fi
+ fi
+
+ return 0
+}
+
+#======================================
# Command Line Argument Parsing
#======================================
@@ -2179,6 +3217,7 @@ OPTIONS:
-v, --verbose Enable verbose output
-n, --dry-run Show what would be done without executing
-f, --force Force reinstallation and skip prompts
+ -a, --ask Ask before running each step
-m, --mode MODE Installation mode (essentials|minimal|dev|server|full|PROFILE)
INSTALLATION MODES:
@@ -2196,6 +3235,13 @@ EXAMPLES:
$0 --resume # Resume from last failed step
$0 --update --mode full # Update and install all packages
$0 --dry-run --mode dev # Preview development installation
+ $0 --ask --mode minimal # Ask before each step in minimal mode
+
+NOTES:
+ • Running without arguments on an existing installation will default to update mode
+ • Use --force to override existing installations
+ • Use --ask to have control over each installation step
+ • Configuration files are backed up before modification
EOF
}
@@ -2227,6 +3273,10 @@ parse_arguments() {
FORCE_MODE=true
shift
;;
+ -a|--ask)
+ ASK_MODE=true
+ shift
+ ;;
-m|--mode)
INSTALL_MODE="$2"
shift 2
@@ -2322,6 +3372,11 @@ main() {
echo
fi
+ if [[ "$ASK_MODE" == true ]]; then
+ print_warning "ASK MODE - You will be prompted for each step"
+ echo
+ fi
+
print_info "Starting installation for user: $USER" "always"
print_info "Log file: $LOG_FILE" "always"
@@ -2340,8 +3395,8 @@ main() {
fi
fi
- # Select installation mode if not specified
- select_installation_mode
+ # Detect installation mode (handles re-runs and updates)
+ detect_installation_mode
# Show installation plan
echo
@@ -2358,9 +3413,11 @@ main() {
done
echo
- if [[ "$FORCE_MODE" != true ]] && [[ "$DRY_RUN" != true ]] && ! prompt_user "Continue with installation?"; then
- print_info "Installation cancelled by user"
- exit 0
+ if [[ "$FORCE_MODE" != true ]] && [[ "$DRY_RUN" != true ]] && [[ "$ASK_MODE" != true ]]; then
+ if ! prompt_user "Continue with installation?"; then
+ print_info "Installation cancelled by user"
+ exit 0
+ fi
fi
# Execute installation steps
@@ -2370,7 +3427,14 @@ main() {
for step in "${STEP_ORDER[@]}"; do
echo
- print_color "$MAGENTA$BOLD" "[$step_number/$total_steps] ${INSTALLATION_STEPS[$step]}"
+ print_color "$CYAN$BOLD" "[$step_number/$total_steps] ${INSTALLATION_STEPS[$step]}"
+
+ # Check if we should run this step (ask mode)
+ if ! should_run_step "$step"; then
+ print_skip "${INSTALLATION_STEPS[$step]} (user choice)"
+ step_number=$((step_number + 1))
+ continue
+ fi
if execute_step "$step"; then
print_info "Step completed successfully: $step"
@@ -2410,6 +3474,7 @@ main() {
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"
+ print_color "$CYAN" "• Test the config command: config status"
if [[ ${#failed_steps[@]} -gt 0 ]]; then
print_color "$YELLOW" "• Run '$0 --resume' to retry failed steps"
diff --git a/common/packages.yml b/common/packages.yml
index baaf2b0..0e73655 100644
--- a/common/packages.yml
+++ b/common/packages.yml
@@ -1,56 +1,50 @@
+# Dotfiles Installation Packages Configuration
+# This file defines packages to install based on installation profiles and distribution-specific mappings
+
+#======================================
+# Installation Profiles
+#======================================
+
# Core packages needed by all installations
common:
- git
- curl
- wget
- - yq
# Essential packages for basic functionality
essentials:
- zsh
- - zsh-completions # git clone rather
- bash
- - bash-completion
- vim
- - neovim
- - tmux
- openssh
- sudo
- - man-pages
- - man-db
+ - man
- bc
- time
- rsync
- - tree-sitter
- - xdg-user-dirs
+ - tree
# Minimal development environment
minimal:
- gcc
- make
- - python3
- - python-pip
+ - python
- jq
- fzf
+ - neovim
+ - tmux
# Full development environment
dev:
- clang
- - gcc
- meson
- gdb
- - make
- cmake
- go
- ninja
- ripgrep
- fd
- - python3
- - python-pip
- - python-virtualenvwrapper
- nodejs
- - jq
- - fzf
- emacs
- vscode
@@ -58,47 +52,32 @@ dev:
server:
- ufw
- net-tools
- - iftop
- - iotop
- - atop
- - btop
- htop
+ - btop
- powertop
- - reflector
- clamav
- - ntpsec
+ - ntp
- networkmanager
- smartmontools
- hdparm
- acpi
- - dosfstools
- - ntfs-3g
- - nfs-utils
- parted
- - cups
- sysstat
- hwinfo
# Desktop environment packages
desktop:
- xorg
- - xorg-server
- wayland
- - xorg-xwayland
- xclip
- - xsel
- xterm
- gtk
- - dunst
- firefox
- mpv
- discord
- libinput
- - xf86-input-libinput
- - xf86-input-synaptics
- nnn
- ranger
- - qbittorrent
- obs-studio
- unrar
- unzip
@@ -106,359 +85,184 @@ desktop:
- imagemagick
- ffmpeg
- wezterm
- - wmctrl
- - xdo
- - xdotool
- - xbindkeys
- ncdu
- - fcitx
- picom
- rofi
- - wofi
- - pkgfile
- - jgmenu
- udiskie
- brightnessctl
- - slurp
- - swappy
- - swww
- - wayshot
- - wf-recorder
- wl-clipboard
- nemo
- blueman
- bluez
- - bluez-utils
- - bluez-tools
-# Window managers and desktop environments
+# Window managers
wm:
- hyprland
- bspwm
- sxhkd
- polybar
- - eww
# Audio/Media packages
media:
- mpd
- - wireplumber
- pipewire
- ncmpcpp
- - xdg-desktop-portal-wlr
-# Gaming and wine
+# Gaming
gaming:
- wine
- - winetricks
- steam
- - cabextract
# Virtualization
virtualization:
- libvirt
- - qemu-full
+ - qemu
# Fonts
fonts:
- - ttf-hack
- - ttf-nerd-fonts-symbols-mono
- - ttf-font-awesome
- - ttf-dejavu
-
-# Rust packages (installed via cargo)
-rust:
- - ripgrep
- - fd-find
- - bat
- - exa
- - starship
- - matugen
-
-# Language-specific packages
-languages:
- node:
- - typescript
- - eslint
- - prettier
- python:
- - black
- - flake8
- - mypy
- - requests
+ - hack-font
+ - nerd-fonts
+ - font-awesome
+ - dejavu-fonts
#======================================
# Distribution-specific package mappings
+# Format: generic_name -> distro_specific_name
#======================================
-# Arch Linux and derivatives (Manjaro, EndeavourOS, etc.)
arch:
+ # Core tools
+ python: python
nodejs: nodejs
- python3: python
- python-pip: python-pip
- python-virtualenvwrapper: python-virtualenvwrapper
- htop: htop
- qemu-full: qemu-full
- bluez-utils: bluez-utils
- bluez-tools: bluez-tools
- ntfs-3g: ntfs-3g
- ttf-hack: ttf-hack
- ttf-nerd-fonts-symbols-mono: ttf-nerd-fonts-symbols-mono
- ttf-font-awesome: ttf-font-awesome
- ttf-dejavu: ttf-dejavu
- man-pages: man-pages
- man-db: man-db
- xorg: xorg
- xorg-server: xorg-server
- xorg-xwayland: xorg-xwayland
+ man: man-pages man-db
+ tree: tree
+ ntp: ntpsec
+ hack-font: ttf-hack
+ nerd-fonts: ttf-nerd-fonts-symbols-mono
+ font-awesome: ttf-font-awesome
+ dejavu-fonts: ttf-dejavu
networkmanager: networkmanager
- reflector: reflector
- pkgfile: pkgfile
- tree-sitter: tree-sitter-cli
+ qemu: qemu-full
vscode: code
-# Debian and derivatives (Ubuntu, Mint, Pop!_OS, etc.)
+ # Desktop specific
+ xorg: xorg xorg-server
+ wayland: wayland xorg-xwayland
+ gtk: gtk3 gtk4
+ libinput: libinput xf86-input-libinput
+ bluez: bluez bluez-utils bluez-tools
+
+ # Media
+ pipewire: pipewire wireplumber
+
debian:
- nodejs: nodejs
- python3: python3
- python-pip: python3-pip
- python-virtualenvwrapper: virtualenvwrapper
- htop: htop
- qemu-full: qemu-system
- bluez-utils: bluez
- bluez-tools: bluez-tools
- ntfs-3g: ntfs-3g
- ttf-hack: fonts-hack
- ttf-nerd-fonts-symbols-mono: fonts-nerd-font-symbols
- ttf-font-awesome: fonts-font-awesome
- ttf-dejavu: fonts-dejavu
- man-pages: manpages-dev
- man-db: man-db
- xorg: xorg
- xorg-server: xserver-xorg
- xorg-xwayland: xwayland
+ # Core tools
+ python: python3 python3-pip
+ nodejs: nodejs npm
+ man: man-pages-dev man-db
+ tree: tree
+ ntp: ntp
+ hack-font: fonts-hack
+ nerd-fonts: fonts-nerd-font-symbols
+ font-awesome: fonts-font-awesome
+ dejavu-fonts: fonts-dejavu
networkmanager: network-manager
- reflector: apt-mirror
- pkgfile: apt-file
- tree-sitter: tree-sitter-cli
+ qemu: qemu-system
vscode: code
- zsh-completions: zsh-autosuggestions
- bash-completion: bash-completion
- openssh: openssh-client
- yq: yq
fd: fd-find
- ripgrep: ripgrep
- fzf: fzf
- neovim: neovim
- tmux: tmux
- git: git
- curl: curl
- wget: wget
- vim: vim
- gcc: gcc
- clang: clang
- make: make
- cmake: cmake
- meson: meson
- ninja: ninja-build
- gdb: gdb
- go: golang-go
- jq: jq
- emacs: emacs
- bc: bc
- time: time
- rsync: rsync
+
+ # Desktop specific
+ xorg: xorg xserver-xorg
+ wayland: libwayland-dev xwayland
+ gtk: libgtk-3-dev libgtk-4-dev
+ libinput: libinput10 xserver-xorg-input-libinput
+ bluez: bluez bluez-tools
+
+ # Media
+ pipewire: pipewire wireplumber
+
+ # System tools
ufw: ufw
net-tools: net-tools
- iftop: iftop
- iotop: iotop
- atop: atop
btop: btop
powertop: powertop
clamav: clamav
- ntpsec: ntp
smartmontools: smartmontools
hdparm: hdparm
acpi: acpi
- dosfstools: dosfstools
- nfs-utils: nfs-common
parted: parted
cups: cups
sysstat: sysstat
hwinfo: hwinfo
- wayland: libwayland-dev
- xclip: xclip
- xsel: xsel
- xterm: xterm
- gtk: libgtk-3-dev
- dunst: dunst
- firefox: firefox
- mpv: mpv
- discord: discord
- libinput: libinput10
- xf86-input-libinput: xserver-xorg-input-libinput
- xf86-input-synaptics: xserver-xorg-input-synaptics
- nnn: nnn
- ranger: ranger
- qbittorrent: qbittorrent
- obs-studio: obs-studio
- unrar: unrar
- unzip: unzip
- p7zip: p7zip-full
- imagemagick: imagemagick
- ffmpeg: ffmpeg
- wezterm: wezterm
- wmctrl: wmctrl
- xdo: xdo
- xdotool: xdotool
- xbindkeys: xbindkeys
- ncdu: ncdu
- fcitx: fcitx
- picom: picom
- rofi: rofi
- wofi: wofi
- jgmenu: jgmenu
- udiskie: udiskie
- brightnessctl: brightnessctl
- slurp: slurp
- swappy: swappy
- wf-recorder: wf-recorder
- wl-clipboard: wl-clipboard
- nemo: nemo
- mpd: mpd
- wireplumber: wireplumber
- pipewire: pipewire
- ncmpcpp: ncmpcpp
- xdg-desktop-portal-wlr: xdg-desktop-portal-wlr
- blueman: blueman
- wine: wine
- winetricks: winetricks
- steam: steam
- cabextract: cabextract
- libvirt: libvirt-daemon-system
- hyprland: hyprland
- bspwm: bspwm
- sxhkd: sxhkd
- polybar: polybar
- eww: eww
- xdg-user-dirs: xdg-user-dirs
-
-# Red Hat and derivatives (RHEL, CentOS, Fedora, Rocky, AlmaLinux)
+
rhel:
- nodejs: nodejs
- python3: python3
- python-pip: python3-pip
- python-virtualenvwrapper: python3-virtualenvwrapper
- htop: htop
- qemu-full: qemu-kvm
- bluez-utils: bluez
- bluez-tools: bluez-tools
- ntfs-3g: ntfs-3g
- ttf-hack: adobe-source-code-pro-fonts
- ttf-nerd-fonts-symbols-mono: powerline-fonts
- ttf-font-awesome: fontawesome-fonts
- ttf-dejavu: dejavu-fonts-common
- man-pages: man-pages
- man-db: man-db
- xorg: xorg-x11-server-Xorg
- xorg-server: xorg-x11-server-Xorg
- xorg-xwayland: xorg-x11-server-Xwayland
+ # Core tools
+ python: python3 python3-pip
+ nodejs: nodejs npm
+ man: man-pages man-db
+ tree: tree
+ ntp: chrony
+ hack-font: adobe-source-code-pro-fonts
+ nerd-fonts: powerline-fonts
+ font-awesome: fontawesome-fonts
+ dejavu-fonts: dejavu-fonts-common
networkmanager: NetworkManager
- reflector: yum-utils
- tree-sitter: tree-sitter-cli
+ qemu: qemu-kvm
vscode: code
- zsh-completions: zsh-completions
- bash-completion: bash-completion
- openssh: openssh-clients
- yq: yq
fd: fd-find
- ripgrep: ripgrep
- fzf: fzf
- neovim: neovim
- tmux: tmux
- git: git
- curl: curl
- wget: wget
- vim: vim-enhanced
- gcc: gcc
- clang: clang
- make: make
- cmake: cmake
- meson: meson
- ninja: ninja-build
- gdb: gdb
- go: golang
- jq: jq
- emacs: emacs
- bc: bc
- time: time
- rsync: rsync
+
+ # Desktop specific
+ xorg: xorg-x11-server-Xorg
+ wayland: wayland-devel xorg-x11-server-Xwayland
+ gtk: gtk3-devel gtk4-devel
+ libinput: libinput
+ bluez: bluez bluez-tools
+
+ # System tools
ufw: firewalld
net-tools: net-tools
- iftop: iftop
- iotop: iotop
- atop: atop
- btop: btop
+ btop: htop
powertop: powertop
clamav: clamav
- ntpsec: chrony
smartmontools: smartmontools
hdparm: hdparm
acpi: acpi
- dosfstools: dosfstools
- nfs-utils: nfs-utils
parted: parted
cups: cups
sysstat: sysstat
- hwinfo: hwinfo
-# openSUSE and SLES
opensuse:
- nodejs: nodejs16
- python3: python3
- python-pip: python3-pip
- python-virtualenvwrapper: python3-virtualenvwrapper
- htop: htop
- qemu-full: qemu
- bluez-utils: bluez
- bluez-tools: bluez-tools
- ntfs-3g: ntfs-3g
- ttf-hack: adobe-sourcecodepro-fonts
- ttf-nerd-fonts-symbols-mono: powerline-fonts
- ttf-font-awesome: fontawesome-fonts
- ttf-dejavu: dejavu-fonts
- man-pages: man-pages
- man-db: man
- xorg: xorg-x11-server
- xorg-server: xorg-x11-server
- xorg-xwayland: xwayland
+ # Core tools
+ python: python3 python3-pip
+ nodejs: nodejs16 npm16
+ man: man-pages man
+ tree: tree
+ ntp: chrony
+ hack-font: adobe-sourcecodepro-fonts
+ nerd-fonts: powerline-fonts
+ font-awesome: fontawesome-fonts
+ dejavu-fonts: dejavu-fonts
networkmanager: NetworkManager
- reflector: zypper
- tree-sitter: tree-sitter
+ qemu: qemu
vscode: code
-# Gentoo and Funtoo (with full package paths)
gentoo:
+ # Core tools with full package paths
git: dev-vcs/git
curl: net-misc/curl
wget: net-misc/wget
- yq: app-misc/yq
zsh: app-shells/zsh
- zsh-completions: app-shells/zsh-completions
bash: app-shells/bash
- bash-completion: app-shells/bash-completion
vim: app-editors/vim
neovim: app-editors/neovim
tmux: app-misc/tmux
openssh: net-misc/openssh
sudo: app-admin/sudo
- man-pages: sys-apps/man-pages
- man-db: sys-apps/man-db
+ man: sys-apps/man-pages sys-apps/man-db
bc: sys-devel/bc
time: sys-process/time
rsync: net-misc/rsync
- tree-sitter: dev-libs/tree-sitter
- xdg-user-dirs: x11-misc/xdg-user-dirs
+ tree: app-text/tree
gcc: sys-devel/gcc
clang: sys-devel/clang
make: sys-devel/make
@@ -468,9 +272,7 @@ gentoo:
ninja: dev-util/ninja
ripgrep: sys-apps/ripgrep
fd: sys-apps/fd
- python3: dev-lang/python
- python-pip: dev-python/pip
- python-virtualenvwrapper: dev-python/virtualenvwrapper
+ python: dev-lang/python
nodejs: net-libs/nodejs
jq: app-misc/jq
fzf: app-shells/fzf
@@ -480,43 +282,35 @@ gentoo:
htop: sys-process/htop
ufw: net-firewall/ufw
net-tools: sys-apps/net-tools
- iftop: net-analyzer/iftop
- iotop: sys-process/iotop
- atop: sys-process/atop
btop: sys-process/btop
powertop: sys-power/powertop
clamav: app-antivirus/clamav
- reflector: app-portage/mirrorselect
- ntpsec: net-misc/chrony
+ ntp: net-misc/chrony
networkmanager: net-misc/networkmanager
smartmontools: sys-apps/smartmontools
hdparm: sys-apps/hdparm
acpi: sys-power/acpi
- dosfstools: sys-fs/dosfstools
- ntfs-3g: sys-fs/ntfs3g
- nfs-utils: net-fs/nfs-utils
parted: sys-block/parted
cups: net-print/cups
sysstat: app-admin/sysstat
hwinfo: sys-apps/hwinfo
+ hack-font: media-fonts/hack
+ nerd-fonts: media-fonts/nerd-fonts
+ font-awesome: media-fonts/fontawesome
+ dejavu-fonts: media-fonts/dejavu
+
+ # Desktop
xorg: x11-base/xorg-server
- xorg-server: x11-base/xorg-server
- wayland: dev-libs/wayland
- xorg-xwayland: x11-base/xwayland
+ wayland: dev-libs/wayland x11-base/xwayland
xclip: x11-misc/xclip
- xsel: x11-misc/xsel
xterm: x11-terms/xterm
gtk: x11-libs/gtk+
- dunst: x11-misc/dunst
firefox: www-client/firefox
mpv: media-video/mpv
discord: net-im/discord-bin
- libinput: dev-libs/libinput
- xf86-input-libinput: x11-drivers/xf86-input-libinput
- xf86-input-synaptics: x11-drivers/xf86-input-synaptics
+ libinput: dev-libs/libinput x11-drivers/xf86-input-libinput
nnn: app-misc/nnn
ranger: app-misc/ranger
- qbittorrent: net-p2p/qbittorrent
obs-studio: media-video/obs-studio
unrar: app-arch/unrar
unzip: app-arch/unzip
@@ -524,97 +318,53 @@ gentoo:
imagemagick: media-gfx/imagemagick
ffmpeg: media-video/ffmpeg
wezterm: x11-terms/wezterm
- wmctrl: x11-misc/wmctrl
- xdo: x11-misc/xdo
- xdotool: x11-misc/xdotool
- xbindkeys: x11-misc/xbindkeys
ncdu: sys-fs/ncdu
- fcitx: app-i18n/fcitx
picom: x11-misc/picom
rofi: x11-misc/rofi
- wofi: gui-apps/wofi
- pkgfile: sys-apps/pkgcore
- jgmenu: x11-misc/jgmenu
udiskie: sys-fs/udiskie
brightnessctl: app-misc/brightnessctl
- slurp: gui-apps/slurp
- swappy: gui-apps/swappy
- swww: gui-apps/swww
- wayshot: gui-apps/wayshot
- wf-recorder: gui-apps/wf-recorder
wl-clipboard: gui-apps/wl-clipboard
nemo: gnome-extra/nemo
- mpd: media-sound/mpd
- wireplumber: media-video/wireplumber
- pipewire: media-video/pipewire
- ncmpcpp: media-sound/ncmpcpp
- xdg-desktop-portal-wlr: gui-libs/xdg-desktop-portal-wlr
blueman: net-wireless/blueman
bluez: net-wireless/bluez
- bluez-utils: net-wireless/bluez
- bluez-tools: net-wireless/bluez-tools
- wine: app-emulation/wine-vanilla
- winetricks: app-emulation/winetricks
- steam: games-util/steam-launcher
- cabextract: app-arch/cabextract
- libvirt: app-emulation/libvirt
- qemu-full: app-emulation/qemu
+
+ # Window managers
hyprland: gui-wm/hyprland
bspwm: x11-wm/bspwm
sxhkd: x11-misc/sxhkd
polybar: x11-misc/polybar
- eww: gui-apps/eww
- ttf-hack: media-fonts/hack
- ttf-nerd-fonts-symbols-mono: media-fonts/nerd-fonts
- ttf-font-awesome: media-fonts/fontawesome
- ttf-dejavu: media-fonts/dejavu
-# Alpine Linux
+ # Media
+ mpd: media-sound/mpd
+ pipewire: media-video/pipewire media-video/wireplumber
+ ncmpcpp: media-sound/ncmpcpp
+
+ # Gaming
+ wine: app-emulation/wine-vanilla
+ steam: games-util/steam-launcher
+
+ # Virtualization
+ libvirt: app-emulation/libvirt
+ qemu: app-emulation/qemu
+
alpine:
- nodejs: nodejs
- python3: python3
- python-pip: py3-pip
+ python: python3 py3-pip
+ nodejs: nodejs npm
+ man: man-pages man-db
+ ntp: chrony
htop: htop
- git: git
- curl: curl
- wget: wget
- vim: vim
- neovim: neovim
- tmux: tmux
- openssh: openssh-client
- sudo: sudo
- bash: bash
- zsh: zsh
- gcc: gcc
- make: make
- jq: jq
-
-# Void Linux
+
void:
+ python: python3 python3-pip
nodejs: nodejs
- python3: python3
- python-pip: python3-pip
- htop: htop
- git: git
- curl: curl
- wget: wget
- vim: vim
- neovim: neovim
- tmux: tmux
- openssh: openssh
- sudo: sudo
- bash: bash
- zsh: zsh
- gcc: gcc
- make: make
- jq: jq
-
-# macOS packages (via Homebrew)
+ man: man-pages
+ ntp: chrony
+
macos:
+ # Homebrew packages
- git
- curl
- wget
- - yq
- zsh
- bash
- vim
@@ -628,232 +378,525 @@ macos:
- ripgrep
- fd
- bat
- - exa
- htop
- rsync
- cmake
- ninja
- go
- - clang-format
- emacs
- visual-studio-code
-# Windows packages (via Chocolatey/Scoop/Winget)
windows:
+ # Chocolatey/Scoop/Winget packages
- git
- ripgrep
- fd
- - win32yank
- microsoft-windows-terminal
- - wsl
- - firefox
- - setdefaultbrowser
- nodejs
- - bat
- - 7zip
- python
- - javaruntime
- - autohotkey
- - bitwarden
- - notepadplusplus
- neovim
- vscode
+ - firefox
+ - 7zip
#======================================
# Gentoo USE flags configuration
#======================================
-
gentoo_use_flags:
- # Core system packages
git: "curl gpg perl python"
curl: "ssl http2 ipv6"
wget: "ssl ipv6 nls"
-
- # Shells and terminal tools
zsh: "unicode pcre gdbm"
bash: "net nls readline"
tmux: "vim-syntax"
-
- # Editors
vim: "python lua ruby perl cscope"
neovim: "lua python ruby"
- emacs: "gtk jpeg png svg tiff xpm cairo dbus gconf gsettings imagemagick json ssl xml xwidgets"
-
- # Development tools
+ emacs: "gtk jpeg png svg tiff xpm cairo dbus json ssl xml"
gcc: "cxx fortran graphite jit nptl openmp pch pie ssp"
clang: "static-analyzer"
python: "sqlite ssl readline ncurses xml"
nodejs: "ssl"
-
- # System utilities
htop: "unicode lm-sensors"
openssh: "ssl kerberos ldap pam"
-
- # Desktop environment
firefox: "dbus gtk3 pulseaudio startup-notification wifi"
mpv: "alsa pulseaudio lua drm wayland X"
gtk: "wayland X cups introspection"
-
- # Audio/Video
pipewire: "alsa bluetooth jack pulseaudio sound-server"
ffmpeg: "alsa encode mp3 opus pulseaudio theora vorbis webp x264 x265"
-
- # Networking
- networkmanager: "bluetooth dhclient introspection modemmanager ppp wifi"
+ networkmanager: "bluetooth dhclient introspection wifi"
bluez: "alsa cups obex readline"
-
- # Virtualization
- qemu-full: "aio alsa bluetooth curl fdt gtk jpeg ncurses nls opengl png pulseaudio sdl spice ssh static-user usb vhost-net virgl vnc"
- libvirt: "firewalld libssh lvm nfs nls numa parted pcap policykit qemu sasl udev vepa virt-network virtualbox xen"
+ qemu: "aio alsa bluetooth curl gtk jpeg ncurses opengl png pulseaudio sdl spice ssh usb vnc"
+ libvirt: "firewalld libssh nfs numa parted qemu sasl udev"
#======================================
-# Windows-specific configurations
+# System tweaks and configurations
#======================================
-
-# Windows bloatware removal lists
-windows_bloatware:
- - BioEnrollment
- - ContactSupport
- - Cortana
- - Feedback
- - Flash
- - Maps
- - OneDrive
- - Wallet
-
-# Default Windows 10/11 apps to remove
-windows_default_apps:
- - Microsoft.Appconnector
- - Microsoft.BingFinance
- - Microsoft.BingNews
- - Microsoft.BingSports
- - Microsoft.BingTranslator
- - Microsoft.BingWeather
- - Microsoft.MicrosoftOfficeHub
- - Microsoft.MicrosoftSolitaireCollection
- - Microsoft.MicrosoftPowerBIForWindows
- - Microsoft.MinecraftUWP
- - Microsoft.Office.OneNote
- - Microsoft.People
- - Microsoft.SkypeApp
- - Microsoft.Wallet
- - Microsoft.WindowsCamera
- - microsoft.windowscommunicationsapps
- - Microsoft.WindowsMaps
- - Microsoft.WindowsPhone
- - Microsoft.WindowsSoundRecorder
- - Microsoft.WindowsStore
- - Microsoft.ZuneMusic
- - Microsoft.ZuneVideo
- - Microsoft.CommsPhone
- - Microsoft.ConnectivityStore
- - Microsoft.GetHelp
- - Microsoft.Getstarted
- - Microsoft.Messaging
- - Microsoft.Office.Sway
- - Microsoft.OneConnect
- - Microsoft.WindowsFeedbackHub
- - Microsoft.BingFoodAndDrink
- - Microsoft.BingTravel
- - Microsoft.BingHealthAndFitness
- - Microsoft.WindowsReadingList
- - king.com.CandyCrushSaga
- - king.com.CandyCrushSodaSaga
- - king.com.*
- - Facebook.Facebook
+system_tweaks:
+ gnome:
+ # Power management settings
+ - gsettings set org.gnome.desktop.session idle-delay 0
+ - 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'
+ # Interface tweaks
+ - gsettings set org.gnome.desktop.interface clock-show-weekday true
+ - gsettings set org.gnome.desktop.interface show-battery-percentage true
+
+ kde:
+ # Power management
+ - kwriteconfig5 --file powermanagementprofilesrc --group AC --group DimDisplay --key idleTime 300000
+ - kwriteconfig5 --file powermanagementprofilesrc --group AC --group DPMSControl --key idleTime 600000
+
+ windows:
+ registry:
+ # Explorer settings
+ - path: "HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Advanced"
+ name: "Hidden"
+ value: 1
+ type: "DWORD"
+ description: "Show hidden files"
+ - path: "HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Advanced"
+ name: "HideFileExt"
+ value: 0
+ type: "DWORD"
+ description: "Show file extensions"
+ - path: "HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Advanced"
+ name: "TaskbarGlomLevel"
+ value: 2
+ type: "DWORD"
+ description: "Never combine taskbar buttons"
+ - path: "HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Advanced"
+ name: "TaskbarSmallIcons"
+ value: 1
+ type: "DWORD"
+ description: "Use small taskbar icons"
+
+ # Dark mode
+ - path: "HKCU:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"
+ name: "AppsUseLightTheme"
+ value: 0
+ type: "DWORD"
+ description: "Use dark theme for apps"
+ - path: "HKCU:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"
+ name: "SystemUsesLightTheme"
+ value: 0
+ type: "DWORD"
+ description: "Use dark theme for system"
+
+ # Search settings
+ - path: "HKCU:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Search"
+ name: "SearchBoxTaskbarMode"
+ value: 0
+ type: "DWORD"
+ description: "Hide search box from taskbar"
+
+ features:
+ - name: "Microsoft-Windows-Subsystem-Linux"
+ description: "Windows Subsystem for Linux"
+ requires_admin: true
+ - name: "VirtualMachinePlatform"
+ description: "Virtual Machine Platform"
+ requires_admin: true
+
+ hardening:
+ registry:
+ # Security hardening registry settings
+ - path: "HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System"
+ name: "EnableLUA"
+ value: 1
+ type: "DWORD"
+ description: "Enable User Account Control"
+ - path: "HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System"
+ name: "ConsentPromptBehaviorAdmin"
+ value: 2
+ type: "DWORD"
+ description: "UAC prompt for administrators"
+ - path: "HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings"
+ name: "DisablePasswordCaching"
+ value: 1
+ type: "DWORD"
+ description: "Disable password caching"
+ - path: "HKLM:\\SYSTEM\\CurrentControlSet\\Control\\Lsa"
+ name: "LimitBlankPasswordUse"
+ value: 1
+ type: "DWORD"
+ description: "Limit blank password use"
+ - path: "HKLM:\\SYSTEM\\CurrentControlSet\\Services\\lanmanserver\\parameters"
+ name: "AutoDisconnectTimeout"
+ value: 15
+ type: "DWORD"
+ description: "Auto disconnect timeout"
+ - path: "HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System"
+ name: "DontDisplayLastUserName"
+ value: 1
+ type: "DWORD"
+ description: "Don't display last username"
+
+ services:
+ disable:
+ - "Fax"
+ - "TelnetD"
+ - "RemoteRegistry"
+ - "Messenger"
+ - "NetMeeting Remote Desktop Sharing"
+ - "Remote Desktop Help Session Manager"
+ - "Routing and Remote Access"
+ - "Simple TCP/IP Services"
+ - "SNMP Service"
+
+ firewall:
+ - "netsh advfirewall set allprofiles state on"
+ - "netsh advfirewall firewall set rule group=\"File and Printer Sharing\" new enable=No"
+ - "netsh advfirewall firewall set rule group=\"Network Discovery\" new enable=No"
+
+ linux:
+ hardening:
+ sysctl:
+ # Network security
+ - net.ipv4.ip_forward = 0
+ - net.ipv4.conf.all.send_redirects = 0
+ - net.ipv4.conf.default.send_redirects = 0
+ - net.ipv4.conf.all.accept_source_route = 0
+ - net.ipv4.conf.default.accept_source_route = 0
+ - net.ipv4.conf.all.accept_redirects = 0
+ - net.ipv4.conf.default.accept_redirects = 0
+ - net.ipv4.conf.all.secure_redirects = 0
+ - net.ipv4.conf.default.secure_redirects = 0
+ - net.ipv4.conf.all.log_martians = 1
+ - net.ipv4.conf.default.log_martians = 1
+ - net.ipv4.icmp_echo_ignore_broadcasts = 1
+ - net.ipv4.icmp_ignore_bogus_error_responses = 1
+ - net.ipv4.conf.all.rp_filter = 1
+ - net.ipv4.conf.default.rp_filter = 1
+ - net.ipv4.tcp_syncookies = 1
+ - net.ipv6.conf.all.accept_ra = 0
+ - net.ipv6.conf.default.accept_ra = 0
+ - net.ipv6.conf.all.accept_redirects = 0
+ - net.ipv6.conf.default.accept_redirects = 0
+
+ # Kernel security
+ - kernel.dmesg_restrict = 1
+ - kernel.kptr_restrict = 2
+ - kernel.yama.ptrace_scope = 1
+ - kernel.kexec_load_disabled = 1
+ - kernel.unprivileged_bpf_disabled = 1
+ - net.core.bpf_jit_harden = 2
+
+ # Memory protection
+ - kernel.randomize_va_space = 2
+ - vm.mmap_min_addr = 65536
+
+ packages:
+ security:
+ - fail2ban
+ - ufw
+ - rkhunter
+ - chkrootkit
+ - lynis
+ - aide
+ - apparmor
+ - apparmor-utils
+
+ services:
+ disable:
+ - avahi-daemon
+ - cups
+ - bluetooth
+ - whoopsie
+ - apport
+ enable:
+ - ufw
+ - fail2ban
+ - apparmor
+
+ filesystem:
+ - "chmod 700 /root"
+ - "chmod 644 /etc/passwd"
+ - "chmod 600 /etc/shadow"
+ - "chmod 644 /etc/group"
+ - "chmod 600 /etc/gshadow"
+ - "find /home -name '.netrc' -delete"
+ - "find /home -name '.rhosts' -delete"
+
+ macos:
+ hardening:
+ defaults:
+ # Security settings
+ - domain: "com.apple.screensaver"
+ key: "askForPassword"
+ value: 1
+ type: "int"
+ description: "Require password after screensaver"
+ - domain: "com.apple.screensaver"
+ key: "askForPasswordDelay"
+ value: 0
+ type: "int"
+ description: "Require password immediately"
+ - domain: "com.apple.Safari"
+ key: "SendDoNotTrackHTTPHeader"
+ value: 1
+ type: "bool"
+ description: "Enable Do Not Track"
+ - domain: "com.apple.Safari"
+ key: "AutoFillPasswords"
+ value: 0
+ type: "bool"
+ description: "Disable password autofill"
+ - domain: "com.apple.loginwindow"
+ key: "GuestEnabled"
+ value: 0
+ type: "bool"
+ description: "Disable guest account"
+ - domain: "com.apple.loginwindow"
+ key: "SHOWFULLNAME"
+ value: 1
+ type: "bool"
+ description: "Show full name in login window"
+
+ system:
+ - "sudo spctl --master-enable" # Enable Gatekeeper
+ - "sudo defaults write /Library/Preferences/com.apple.alf globalstate -int 1" # Enable firewall
+ - "sudo launchctl load /System/Library/LaunchDaemons/com.apple.locate.plist" # Enable locate database
+
+ services:
+ disable:
+ - "com.apple.AirPlayXPCHelper"
+ - "com.apple.RemoteDesktop.agent"
+
+ packages:
+ security:
+ - gpg
+ - gnupg
+ - pinentry-mac
#======================================
-# Additional configurations
+# Service configurations
#======================================
-
-# Source installations (git repositories)
-source_packages:
- - name: "Aylur/astal.git"
- url: "https://github.com/Aylur/astal.git"
- build_deps: ["nodejs", "meson", "ninja"]
- - name: "Aylur/icon-theme-browser.git"
- url: "https://github.com/Aylur/icon-theme-browser.git"
- build_deps: ["nodejs", "meson", "ninja"]
-
-# Custom build instructions
-custom_builds:
- icon-theme-browser:
- instructions: |
- git clone https://github.com/Aylur/icon-theme-browser.git /tmp/icon-theme-browser
- cd /tmp/icon-theme-browser
- npm install
- meson setup --prefix /usr build
- meson install -C build
-
-# Kubernetes tools
-kubernetes:
- - kubectl
- - kubernetes-helm
- - kubeseal
- - kubeswitch
- - lazydocker
-
-# Distribution-specific additional packages
-linux_distro_specific:
- arch:
- - dkms
- - linux
- - linux-headers
- - linux-tools
- - base-devel
- - bind-tools
- - nvme-cli
- - vulkan-devel
- - lm_sensors
- - pacman-contrib
- - yay
- debian:
- - build-essential
- - linux-headers-generic
- - apt-transport-https
- - ca-certificates
- - gnupg
- - lsb-release
- - software-properties-common
- - dkms
- rhel:
- - kernel-devel
- - kernel-headers
- - gcc-c++
- - epel-release
- - dnf-plugins-core
- - development-tools
- opensuse:
- - kernel-devel
- - gcc-c++
- - patterns-devel-base-devel_basis
- - zypper-plugins
- gentoo:
- - sys-kernel/gentoo-sources
- - sys-apps/portage
- - app-portage/eix
- - app-portage/gentoolkit
- - sys-devel/gcc
- - sys-devel/make
-
-# Profile-specific configurations
-profile_configs:
- server:
- services_enable:
+services:
+ enable:
+ all:
- sshd
+ - networkmanager
+ server:
- firewalld
- chronyd
- services_disable:
+ desktop:
- bluetooth
- cups
- desktop:
- services_enable:
+ disable:
+ server:
- bluetooth
- cups
- - NetworkManager
- desktop_tweaks: true
+ - gdm
+ minimal:
+ - cups
+ - bluetooth
+
+#======================================
+# Development environment configurations
+#======================================
+development:
+ git_config:
+ - git config --global init.defaultBranch main
+ - git config --global pull.rebase false
+ - git config --global core.editor vim
+
+ rust:
+ components:
+ - rustc
+ - cargo
+ - clippy
+ - rustfmt
+
+ nodejs:
+ global_packages:
+ - typescript
+ - eslint
+ - prettier
+
+ python:
+ global_packages:
+ - black
+ - flake8
+ - mypy
+ - requests
+ - virtualenvwrapper
+
+#======================================
+# System update checks and maintenance
+#======================================
+system_updates:
+ linux:
+ kernel_check:
+ - "uname -r" # Current kernel
+ - "ls /boot/vmlinuz-* | tail -1 | sed 's/.*vmlinuz-//'" # Latest available
+
+ distro_updates:
+ arch:
+ check: "checkupdates"
+ update: "pacman -Syu"
+ kernel_update: "pacman -S linux linux-headers"
+ debian:
+ check: "apt list --upgradable"
+ update: "apt update && apt upgrade -y"
+ kernel_update: "apt install linux-image-generic linux-headers-generic"
+ rhel:
+ check: "dnf check-update"
+ update: "dnf update -y"
+ kernel_update: "dnf update kernel kernel-headers"
+ gentoo:
+ check: "emerge -pv --update --deep --newuse @world"
+ update: "emerge --update --deep --newuse @world"
+ kernel_update: "emerge gentoo-sources && genkernel all"
+
+ macos:
+ system_updates:
+ check: "softwareupdate -l"
+ update: "softwareupdate -ia"
+ major_check: "softwareupdate --list-full-installers"
+
+ windows:
+ system_updates:
+ check: "Get-WindowsUpdate -MicrosoftUpdate"
+ update: "Install-WindowsUpdate -MicrosoftUpdate -AcceptAll -AutoReboot"
+ feature_updates: "Get-WindowsUpdate -UpdateType Software"
+
+#======================================
+# Custom installation commands
+#======================================
+custom_installs:
+ yq:
+ condition: "! command -v yq"
+ linux: |
+ YQ_VERSION=$(curl -s https://api.github.com/repos/mikefarah/yq/releases/latest | grep 'tag_name' | cut -d'"' -f4)
+ YQ_BINARY="yq_linux_amd64"
+ curl -L "https://github.com/mikefarah/yq/releases/download/${YQ_VERSION}/${YQ_BINARY}" -o ~/.local/bin/yq
+ chmod +x ~/.local/bin/yq
+ macos: "brew install yq"
+ windows: "choco install yq"
+
+ homebrew:
+ condition: "test $(uname) = Darwin && ! command -v brew"
+ macos: '/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"'
+
+ oh-my-zsh:
+ condition: "test -d ~/.oh-my-zsh"
+ command: 'sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" "" --unattended'
+
+ system_updates:
+ condition: "true" # Always available
+ description: "Check and install system updates"
+ linux: |
+ case "$CFG_DISTRO" in
+ arch) checkupdates && sudo pacman -Syu ;;
+ debian|ubuntu) apt list --upgradable && sudo apt update && sudo apt upgrade -y ;;
+ rhel|fedora|centos) dnf check-update && sudo dnf update -y ;;
+ gentoo) emerge -pv --update --deep --newuse @world && sudo emerge --update --deep --newuse @world ;;
+ *) echo "Unsupported distribution for automatic updates" ;;
+ esac
+ macos: "softwareupdate -l && sudo softwareupdate -ia"
+ windows: |
+ if (Get-Module -ListAvailable -Name PSWindowsUpdate) {
+ Get-WindowsUpdate -MicrosoftUpdate
+ Install-WindowsUpdate -MicrosoftUpdate -AcceptAll -AutoReboot
+ } else {
+ Write-Host "PSWindowsUpdate module not installed. Install with: Install-Module PSWindowsUpdate"
+ }
+
+#======================================
+# Profile-specific package lists
+#======================================
+profiles:
+ essentials:
+ description: "Essential packages only (git, curl, wget, vim, zsh)"
+ packages:
+ - common
+ - essentials
+
+ minimal:
+ description: "Minimal setup for basic development"
+ packages:
+ - common
+ - essentials
+ - minimal
+
dev:
- install_development_tools: true
- configure_git: true
- install_rust: true
- install_nodejs: true
+ description: "Full development environment"
+ packages:
+ - common
+ - essentials
+ - minimal
+ - dev
+ enable_development: true
+
+ server:
+ description: "Server configuration"
+ packages:
+ - common
+ - essentials
+ - minimal
+ - server
+ enable_services: server
+
+ full:
+ description: "Complete installation with all packages"
+ packages:
+ - common
+ - essentials
+ - minimal
+ - dev
+ - server
+ - desktop
+ - wm
+ - media
+ - fonts
+ enable_development: true
+ enable_services: desktop
+
+#======================================
+# Package management helpers
+#======================================
+package_managers:
+ arch:
+ update: "pacman -Syu"
+ install: "pacman -S --noconfirm"
+ search: "pacman -Ss"
+
+ debian:
+ update: "apt update && apt upgrade -y"
+ install: "apt install -y"
+ search: "apt search"
+
+ rhel:
+ update: "dnf update -y"
+ install: "dnf install -y"
+ search: "dnf search"
+
+ opensuse:
+ update: "zypper update -y"
+ install: "zypper install -y"
+ search: "zypper search"
+
+ gentoo:
+ update: "emerge --sync && emerge -uDN @world"
+ install: "emerge"
+ search: "emerge --search"
+
+ alpine:
+ update: "apk update && apk upgrade"
+ install: "apk add"
+ search: "apk search"
+
+ void:
+ update: "xbps-install -Su"
+ install: "xbps-install -y"
+ search: "xbps-query -Rs"
+
+ macos:
+ update: "brew update && brew upgrade"
+ install: "brew install"
+ search: "brew search"
+
+ windows:
+ update: "choco upgrade all -y"
+ install: "choco install -y"
+ search: "choco search"