From e1091a6b21b04d053fd635ef9519982a30ba9419 Mon Sep 17 00:00:00 2001 From: srdusr Date: Sun, 21 Sep 2025 01:39:35 +0200 Subject: Testing --- common/install.sh | 1755 +++++++++++++++++++++++++++++++++++++++++---------- common/packages.yml | 1161 ++++++++++++++++++---------------- 2 files changed, 2012 insertions(+), 904 deletions(-) (limited to 'common') 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 "") ;; - gentoo|funtoo) - distro_package=$(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 "") + ;; + 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 - # Install packages from each section - for section in "${sections[@]}"; do - print_info "Installing packages from section: $section" + print_info "Installing package groups for $profile: ${groups[*]}" + + # 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 + + # 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 - # 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' + 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,21 +1968,58 @@ install_essentials() { print_section "Installing Essential Tools" save_state "install_essentials" "started" - # Install package processing tools first + # 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 - case "$tool" in - yq) - if install_yq; then - print_success "Installed package tool: $tool" - else - print_error "Failed to install package tool: $tool" - mark_step_failed "install_essentials" - return 1 - fi + 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) + if install_yq; then + print_success "Installed package tool: $tool" + else + print_error "Failed to install package tool: $tool" + mark_step_failed "install_essentials" + return 1 + fi ;; jq) - if install_single_package "jq" "essential"; then + 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//.git + # git@github.com:/.git + # https://github.com// + 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 - mark_step_completed "setup_development" + # 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 + + 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." +} + +apply_macos_tweaks() { + print_info "macOS system tweaks applied (placeholder)" +} - # Turn off blank screen - gsettings set org.gnome.desktop.session idle-delay 0 +#====================================== +# Custom Installation Functions +#====================================== - # 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' +handle_custom_installs() { + local packages_file="$1" - print_success "GNOME power/display settings applied" - else - print_info "gsettings not found; skipping GNOME power/display tweaks" + 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:" @@ -2161,6 +3150,55 @@ select_installation_mode() { print_info "Description: ${INSTALLATION_PROFILES[$INSTALL_MODE]:-Custom profile}" } +#====================================== +# 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" -- cgit v1.2.3