| @@ -1,20 +1,29 @@ |
| 1 | 1 | | #!/usr/bin/env bash |
| 2 | + | # |
| 3 | + | # krolyxon's dotfiles installer |
| 4 | + | # |
| 5 | + | # READ THIS FIRST: |
| 6 | + | # This sets up MY personal desktop (Arch + Hyprland + aurora), built around my own |
| 7 | + | # hardware (Lenovo Legion, Intel + NVIDIA hybrid, linux-zen) and my habits. |
| 8 | + | # It is NOT a general purpose installer and I'm not trying to make it one. |
| 9 | + | # |
| 10 | + | # Only run it on a FRESHLY INSTALLED, UNCONFIGURED Arch Linux system. |
| 11 | + | # It installs a lot of packages, moves your existing configs out of the way, |
| 12 | + | # changes your login shell and can enable system services. |
| 13 | + | # |
| 14 | + | # If you just want a piece of the setup, copy the files you like instead. |
| 15 | + | # |
| 16 | + | # Usage: |
| 17 | + | # ./install.sh interactive |
| 18 | + | # ./install.sh --yes pre-select everything (still asks the fresh-system question) |
| 19 | + | # ./install.sh --check compare installed packages with packages/*.txt, changes nothing |
| 2 | 20 | | |
| 3 | 21 | | trap 'log WARN "Interrupted by user"; exit 130' INT |
| 4 | 22 | | |
| 5 | 23 | | INSTALL_STATUS="none" # none | partial | complete | failed |
| 6 | 24 | | AUTO_YES=0 |
| 7 | | − | |
| 8 | | − | ####################### |
| 9 | | − | ## Arguement Parsing ## |
| 10 | | − | ####################### |
| 11 | | − | for arg in "$@"; do |
| 12 | | − | case "$arg" in |
| 13 | | − | --yes|--ci|--non-interactive) |
| 14 | | − | AUTO_YES=1 |
| 15 | | − | ;; |
| 16 | | − | esac |
| 17 | | − | done |
| 25 | + | CHECK_ONLY=0 |
| 26 | + | BACKUP_DIR="" |
| 18 | 27 | | |
| 19 | 28 | | log() { |
| 20 | 29 | | local level="$1" |
| @@ -33,6 +42,79 @@ log() { |
| 33 | 42 | | "$color" "$(date '+%H:%M:%S')" "$level" "$reset" "$*" >&2 |
| 34 | 43 | | } |
| 35 | 44 | | |
| 45 | + | usage() { |
| 46 | + | sed -n '3,19s/^# \{0,1\}//p' "$0" |
| 47 | + | } |
| 48 | + | |
| 49 | + | ####################### |
| 50 | + | ## Arguement Parsing ## |
| 51 | + | ####################### |
| 52 | + | for arg in "$@"; do |
| 53 | + | case "$arg" in |
| 54 | + | --yes|--ci|--non-interactive) |
| 55 | + | AUTO_YES=1 |
| 56 | + | ;; |
| 57 | + | --check) |
| 58 | + | CHECK_ONLY=1 |
| 59 | + | ;; |
| 60 | + | -h|--help) |
| 61 | + | usage |
| 62 | + | exit 0 |
| 63 | + | ;; |
| 64 | + | *) |
| 65 | + | log ERROR "Unknown option: $arg" |
| 66 | + | usage |
| 67 | + | exit 1 |
| 68 | + | ;; |
| 69 | + | esac |
| 70 | + | done |
| 71 | + | |
| 72 | + | currentDir="$(dirname "$(readlink -f "$0")")" |
| 73 | + | cd "$currentDir" || exit 1 |
| 74 | + | PKG_DIR="$currentDir/packages" |
| 75 | + | |
| 76 | + | |
| 77 | + | ################### |
| 78 | + | ## Package lists ## |
| 79 | + | ################### |
| 80 | + | # packages/*.txt: one package per line, '#' comments. |
| 81 | + | # A trailing '!' (e.g. "blender !") means: listed, but not pre-selected in the picker. |
| 82 | + | |
| 83 | + | # All packages in a list |
| 84 | + | read_list() { |
| 85 | + | sed 's/#.*//' "$PKG_DIR/$1.txt" | awk 'NF { print $1 }' |
| 86 | + | } |
| 87 | + | |
| 88 | + | # Packages of a list that are pre-selected in the picker |
| 89 | + | preselected() { |
| 90 | + | sed 's/#.*//' "$PKG_DIR/$1.txt" | awk 'NF && $2 != "!" { print $1 }' |
| 91 | + | } |
| 92 | + | |
| 93 | + | check_packages() { |
| 94 | + | local listed installed aur |
| 95 | + | listed=$(for f in "$PKG_DIR"/*.txt; do read_list "$(basename "$f" .txt)"; done | sort -u) |
| 96 | + | installed=$(pacman -Qqe | sort -u) |
| 97 | + | aur=$(pacman -Qqm | sort -u) |
| 98 | + | |
| 99 | + | echo "Installed explicitly, but not in any packages/*.txt:" |
| 100 | + | comm -23 <(echo "$installed") <(echo "$listed") | while read -r p; do |
| 101 | + | if grep -qx "$p" <<< "$aur"; then echo " $p (aur)"; else echo " $p"; fi |
| 102 | + | done |
| 103 | + | |
| 104 | + | echo |
| 105 | + | echo "Listed, but not installed:" |
| 106 | + | comm -13 <(pacman -Qq | sort -u) <(echo "$listed") | sed 's/^/ /' |
| 107 | + | } |
| 108 | + | |
| 109 | + | if ((CHECK_ONLY)); then |
| 110 | + | check_packages |
| 111 | + | exit 0 |
| 112 | + | fi |
| 113 | + | |
| 114 | + | |
| 115 | + | ################# |
| 116 | + | ## GATEKEEPING ## |
| 117 | + | ################# |
| 36 | 118 | | |
| 37 | 119 | | ## Prevent user from running this script as root |
| 38 | 120 | | if [[ "$EUID" -eq 0 ]]; then |
| @@ -41,11 +123,51 @@ if [[ "$EUID" -eq 0 ]]; then |
| 41 | 123 | | exit 1 |
| 42 | 124 | | fi |
| 43 | 125 | | |
| 126 | + | if [[ ! -f /etc/arch-release ]]; then |
| 127 | + | log ERROR "This is not Arch Linux. These dotfiles and this script are for Arch only." |
| 128 | + | exit 1 |
| 129 | + | fi |
| 130 | + | |
| 131 | + | ## Does this look like a system that is already set up? |
| 132 | + | ## (configs this repo doesn't provide, or another desktop's config) |
| 133 | + | check_fresh_system() { |
| 134 | + | local foreign=() desktops=() entry |
| 135 | + | for entry in "$HOME"/.config/*; do |
| 136 | + | [[ -e "$entry" ]] || continue |
| 137 | + | entry="$(basename "$entry")" |
| 138 | + | [[ -e "$currentDir/.config/$entry" ]] || foreign+=("$entry") |
| 139 | + | done |
| 140 | + | for entry in i3 sway niri river awesome bspwm openbox qtile xfce4 \ |
| 141 | + | kdeglobals plasma-org.kde.plasma.desktop-appletsrc gnome-session; do |
| 142 | + | [[ -e "$HOME/.config/$entry" ]] && desktops+=("$entry") |
| 143 | + | done |
| 144 | + | |
| 145 | + | ((${#foreign[@]} <= 10 && ${#desktops[@]} == 0)) && return 0 |
| 146 | + | |
| 147 | + | printf '\033[1;31m' |
| 148 | + | echo "==================================================================" |
| 149 | + | echo " This does NOT look like a fresh Arch install." |
| 150 | + | echo |
| 151 | + | ((${#desktops[@]})) && echo " Found another desktop's config: ${desktops[*]}" |
| 152 | + | ((${#foreign[@]} > 10)) && echo " Found ${#foreign[@]} entries in ~/.config that this repo doesn't provide." |
| 153 | + | echo |
| 154 | + | echo " This installer is meant for a freshly installed, unconfigured" |
| 155 | + | echo " system. On a system you already use it WILL change things you" |
| 156 | + | echo " care about: your configs get moved aside, your shell changes, and" |
| 157 | + | echo " lots of packages get installed." |
| 158 | + | echo "==================================================================" |
| 159 | + | printf '\033[0m' |
| 160 | + | local answer |
| 161 | + | read -rp "Type 'I understand' to continue anyway: " answer |
| 162 | + | [[ "$answer" == "I understand" ]] || { log WARN "Aborted."; exit 1; } |
| 163 | + | } |
| 164 | + | check_fresh_system |
| 165 | + | |
| 44 | 166 | | choose_packages() { |
| 45 | | − | local -n arr=$1 |
| 167 | + | local list="$1" |
| 46 | 168 | | gum choose --no-limit \ |
| 47 | | − | --selected "$(IFS=,; echo "${arr[*]}")" \ |
| 48 | | − | "${arr[@]}" |
| 169 | + | --selected "$(preselected "$list" | paste -sd,)" \ |
| 170 | + | $(read_list "$list") |
| 49 | 171 | | } |
| 50 | 172 | | |
| 51 | 173 | | prompt_style() { |
| @@ -82,18 +204,10 @@ install_prerequisites() { |
| 82 | 204 | | sudo pacman -S --needed --noconfirm stow gum git zsh |
| 83 | 205 | | } |
| 84 | 206 | | |
| 85 | | − | |
| 86 | | − | ################# |
| 87 | | − | ## GATEKEEPING ## |
| 88 | | − | ################# |
| 89 | | − | |
| 90 | 207 | | ## Installs prerequisites |
| 91 | 208 | | log INFO "Installing prerequisites: " |
| 92 | 209 | | install_prerequisites |
| 93 | 210 | | |
| 94 | | − | currentDir="$(dirname "$(readlink -f "$0")")" |
| 95 | | − | cd "$currentDir" |
| 96 | | − | |
| 97 | 211 | | |
| 98 | 212 | | DISCLAIMER=$(gum style \ |
| 99 | 213 | | --border double \ |
| @@ -102,18 +216,18 @@ DISCLAIMER=$(gum style \ |
| 102 | 216 | | --bold \ |
| 103 | 217 | | "⚠️ DISCLAIMER |
| 104 | 218 | | |
| 105 | | − | This script is provided AS IS, WITHOUT ANY WARRANTY. |
| 106 | | − | There is NO guarantee that it will work on your system. |
| 219 | + | These are MY personal dotfiles, built for MY machine |
| 220 | + | (Lenovo Legion, Intel + NVIDIA hybrid, linux-zen) and the way I work. |
| 221 | + | They are NOT general purpose. They're public as a reference, |
| 222 | + | or for people who want exactly the same setup. |
| 107 | 223 | | |
| 108 | | − | It may: |
| 109 | | − | • Modify system files |
| 110 | | − | • Overwrite or DELETE existing configuration |
| 111 | | − | • Potentially break your setup |
| 224 | + | Only run this on a FRESHLY INSTALLED, UNCONFIGURED Arch system. |
| 112 | 225 | | |
| 113 | | − | You are STRONGLY ADVISED to back up: |
| 114 | | − | • ~/.config |
| 115 | | − | • ~/.local |
| 116 | | − | • /etc (if modified) |
| 226 | + | This script is provided AS IS, WITHOUT ANY WARRANTY. It will: |
| 227 | + | • Install a lot of packages (including NVIDIA drivers if selected) |
| 228 | + | • Move conflicting files in your \$HOME to ~/.dotfiles-backup/ |
| 229 | + | • Change your login shell to zsh |
| 230 | + | • Optionally write /etc/keyd and enable keyd |
| 117 | 231 | | |
| 118 | 232 | | Proceed at your OWN RISK." |
| 119 | 233 | | ) |
| @@ -123,68 +237,26 @@ confirm "$DISCLAIMER |
| 123 | 237 | | Do you understand the risks and want to continue?" || exit 1 |
| 124 | 238 | | |
| 125 | 239 | | |
| 126 | | − | ########### |
| 127 | | − | ## UTILS ## |
| 128 | | − | ########### |
| 129 | | − | source "$currentDir/packages/pkg_utils.sh" |
| 130 | | − | prompt_style "Important utilities (Most likely go with defaults)" |
| 131 | | − | if ((AUTO_YES)); then |
| 132 | | − | UTILITY_PKGS=("${pkg_utils[@]}") |
| 133 | | − | else |
| 134 | | − | gum_to_array UTILITY_PKGS < <(choose_packages pkg_utils) |
| 135 | | − | fi |
| 136 | | − | |
| 137 | | − | |
| 138 | | − | ############### |
| 139 | | − | ## DEV TOOLS ## |
| 140 | | − | ############### |
| 141 | | − | source "$currentDir/packages/pkg_dev_tools.sh" |
| 142 | | − | prompt_style "Select Development Tools" |
| 143 | | − | if ((AUTO_YES)); then |
| 144 | | − | DEV_PKGS=("${pkg_dev_tools[@]}") |
| 145 | | − | else |
| 146 | | − | gum_to_array DEV_PKGS < <(choose_packages pkg_dev_tools) |
| 147 | | − | fi |
| 148 | | − | |
| 149 | | − | |
| 150 | | − | |
| 151 | 240 | | ####################### |
| 152 | | − | ## OPTIONAL PACKAGES ## |
| 241 | + | ## Package selection ## |
| 153 | 242 | | ####################### |
| 154 | | − | source "$currentDir/packages/pkg_optional.sh" |
| 155 | | − | prompt_style "Select Optional Desktop Packages" |
| 156 | | − | if ((AUTO_YES)); then |
| 157 | | − | OPTIONAL_PKGS=("${pkg_optional[@]}") |
| 158 | | − | else |
| 159 | | − | gum_to_array OPTIONAL_PKGS < <(choose_packages pkg_optional) |
| 160 | | − | fi |
| 161 | | − | |
| 162 | | − | |
| 163 | | − | |
| 164 | | − | ################# |
| 165 | | − | ## GPU DRIVERS ## |
| 166 | | − | ################# |
| 167 | | − | source "$currentDir/packages/pkg_gpu.sh" |
| 168 | | − | prompt_style "Select GPU drivers" |
| 169 | | − | if ((AUTO_YES)); then |
| 170 | | − | GPU_PKGS=("${pkg_gpu[@]}") |
| 171 | | − | else |
| 172 | | − | gum_to_array GPU_PKGS < <(choose_packages pkg_gpu) |
| 173 | | − | fi |
| 174 | | − | |
| 175 | | − | |
| 176 | | − | ################## |
| 177 | | − | ## AUR PACKAGES ## |
| 178 | | − | ################## |
| 179 | | − | source "$currentDir/packages/pkg_aur.sh" |
| 180 | | − | prompt_style "Select AUR packages" |
| 181 | | − | |
| 182 | | − | if ((AUTO_YES)); then |
| 183 | | − | AUR_PKGS=("${pkg_aur[@]}") |
| 184 | | − | else |
| 185 | | − | gum_to_array AUR_PKGS < <(choose_packages pkg_aur) |
| 186 | | − | fi |
| 243 | + | SELECTED_PKGS=() |
| 244 | + | for list in utils dev gpu optional aur; do |
| 245 | + | case "$list" in |
| 246 | + | utils) prompt_style "Important utilities (Most likely go with defaults)" ;; |
| 247 | + | dev) prompt_style "Select Development Tools" ;; |
| 248 | + | gpu) prompt_style "Select GPU drivers" ;; |
| 249 | + | optional) prompt_style "Select Optional Desktop Packages" ;; |
| 250 | + | aur) prompt_style "Select AUR packages" ;; |
| 251 | + | esac |
| 187 | 252 | | |
| 253 | + | if ((AUTO_YES)); then |
| 254 | + | mapfile -t picked < <(preselected "$list") |
| 255 | + | else |
| 256 | + | gum_to_array picked < <(choose_packages "$list") |
| 257 | + | fi |
| 258 | + | SELECTED_PKGS+=("${picked[@]}") |
| 259 | + | done |
| 188 | 260 | | |
| 189 | 261 | | |
| 190 | 262 | | ########################## |
| @@ -195,8 +267,8 @@ WARNING=$(gum style \ |
| 195 | 267 | | --border-foreground 196 \ |
| 196 | 268 | | --padding "1 2" \ |
| 197 | 269 | | --bold \ |
| 198 | | − | "⚠️ This will DELETE any conflicting files and replace them with symlinks from this repo. |
| 199 | | − | Make sure you have already backed up all your existing config files (~/.config)") |
| 270 | + | "⚠️ Files in your \$HOME that conflict with this repo will be MOVED to |
| 271 | + | ~/.dotfiles-backup/<date>/ and replaced with symlinks from this repo.") |
| 200 | 272 | | |
| 201 | 273 | | if confirm "$WARNING |
| 202 | 274 | | |
| @@ -205,40 +277,39 @@ Proceed with system configuration (stow, shell)?"; then |
| 205 | 277 | | ## Stow dotfiles ## |
| 206 | 278 | | ################### |
| 207 | 279 | | log INFO "Detecting conflicts..." |
| 208 | | − | conflicts=$(stow . --no-folding -nv 2>&1 | \ |
| 280 | + | mapfile -t conflicts < <(stow . --no-folding -nv 2>&1 | \ |
| 209 | 281 | | sed -n 's/.*existing target \(.*\) since neither.*/\1/p') |
| 210 | 282 | | |
| 211 | | − | if [[ -z "$conflicts" ]]; then |
| 283 | + | if ((${#conflicts[@]} == 0)); then |
| 212 | 284 | | log INFO "No conflicts. Running stow normally..." |
| 213 | | − | stow . --no-folding \ |
| 214 | | − | && log SUCCESS "Dotfiles stowed successfully" \ |
| 215 | | − | || log ERROR "Stow failed" |
| 216 | 285 | | else |
| 217 | | − | log WARN "These paths conflict and will be removed:" |
| 218 | | − | printf ' %s\n' $conflicts |
| 219 | | − | read -rp "Proceed with deleting these files? (y/N): " ok |
| 220 | | − | if [[ ! "$ok" =~ ^[Yy]$ ]]; then |
| 221 | | − | log WARN "Aborted." |
| 286 | + | log WARN "These paths conflict and will be moved to a backup:" |
| 287 | + | printf ' %s\n' "${conflicts[@]}" |
| 288 | + | if ! confirm "Move these files to ~/.dotfiles-backup/ ?"; then |
| 289 | + | log WARN "Aborted." |
| 222 | 290 | | exit 1 |
| 223 | 291 | | fi |
| 224 | 292 | | |
| 225 | | − | # Remove conflicts relative to $HOME |
| 226 | | − | for path in $conflicts; do |
| 227 | | − | [[ -n "$path" && "$path" != "/" ]] && rm -rf "$HOME/$path" && log INFO "Deleted $path" |
| 228 | | − | done |
| 293 | + | # Move conflicts (relative to $HOME) into the backup dir, keeping their paths |
| 294 | + | BACKUP_DIR="$HOME/.dotfiles-backup/$(date '+%Y%m%d-%H%M%S')" |
| 295 | + | for path in "${conflicts[@]}"; do |
| 296 | + | [[ -n "$path" && "$path" != "/" ]] || continue |
| 297 | + | mkdir -p "$BACKUP_DIR/$(dirname "$path")" |
| 298 | + | mv "$HOME/$path" "$BACKUP_DIR/$path" && log INFO "Backed up $path" |
| 299 | + | done |
| 300 | + | log INFO "Backups are in $BACKUP_DIR" |
| 301 | + | fi |
| 229 | 302 | | |
| 230 | | − | log INFO "Running stow..." |
| 231 | 303 | | stow . --no-folding \ |
| 232 | | − | && log INFO "Dotfiles stowed with overwrite." \ |
| 304 | + | && log SUCCESS "Dotfiles stowed successfully" \ |
| 233 | 305 | | || log ERROR "Stow failed" |
| 234 | | − | fi |
| 235 | 306 | | |
| 236 | 307 | | ################################# |
| 237 | 308 | | ## Change default shell to zsh ## |
| 238 | 309 | | ################################# |
| 239 | 310 | | log INFO "Changing the default shell to ZSH" |
| 240 | 311 | | if [[ "$SHELL" != "$(command -v zsh)" ]]; then |
| 241 | | − | chsh -s $(which zsh) \ |
| 312 | + | chsh -s "$(command -v zsh)" \ |
| 242 | 313 | | && log SUCCESS "Default shell successfully set to zsh" \ |
| 243 | 314 | | || log ERROR "Default shell could not be set to zsh" |
| 244 | 315 | | else |
| @@ -250,15 +321,10 @@ else |
| 250 | 321 | | fi |
| 251 | 322 | | |
| 252 | 323 | | |
| 253 | | − | |
| 254 | | − | source "$currentDir/packages/pkg_desktop.sh" |
| 324 | + | mapfile -t DESKTOP_PKGS < <(read_list desktop) |
| 255 | 325 | | ALL_PKGS=( |
| 256 | | − | "${DEV_PKGS[@]}" |
| 257 | | − | "${OPTIONAL_PKGS[@]}" |
| 258 | | − | "${GPU_PKGS[@]}" |
| 259 | | − | "${UTILITY_PKGS[@]}" |
| 260 | | − | "${AUR_PKGS[@]}" |
| 261 | | − | "${pkg_desktop[@]}" |
| 326 | + | "${DESKTOP_PKGS[@]}" |
| 327 | + | "${SELECTED_PKGS[@]}" |
| 262 | 328 | | ) |
| 263 | 329 | | |
| 264 | 330 | | ######################## |
| @@ -271,6 +337,7 @@ if ((${#ALL_PKGS[@]})); then |
| 271 | 337 | | ## Installing paru if not installed already |
| 272 | 338 | | if ! command -v paru >/dev/null 2>&1; then |
| 273 | 339 | | log INFO "Installing Paru (AUR package manager)" |
| 340 | + | sudo pacman -S --needed --noconfirm base-devel |
| 274 | 341 | | git clone https://aur.archlinux.org/paru.git |
| 275 | 342 | | (cd paru && makepkg -sri) |
| 276 | 343 | | rm -rf paru |
| @@ -309,6 +376,12 @@ fi |
| 309 | 376 | | #################### |
| 310 | 377 | | ## Final dialogue ## |
| 311 | 378 | | #################### |
| 379 | + | BACKUP_NOTE="" |
| 380 | + | [[ -n "$BACKUP_DIR" ]] && BACKUP_NOTE=" |
| 381 | + | |
| 382 | + | Your old conflicting files are in: |
| 383 | + | $BACKUP_DIR" |
| 384 | + | |
| 312 | 385 | | case "$INSTALL_STATUS" in |
| 313 | 386 | | complete) |
| 314 | 387 | | gum style \ |
| @@ -319,7 +392,7 @@ case "$INSTALL_STATUS" in |
| 319 | 392 | | "✔ Installation Complete! |
| 320 | 393 | | |
| 321 | 394 | | All selected packages were installed successfully. |
| 322 | | − | You may now reboot the system for all the changes to apply." |
| 395 | + | You may now reboot the system for all the changes to apply.$BACKUP_NOTE" |
| 323 | 396 | | ;; |
| 324 | 397 | | partial) |
| 325 | 398 | | gum style \ |
| @@ -330,7 +403,7 @@ You may now reboot the system for all the changes to apply." |
| 330 | 403 | | "⚠ Setup Finished (Partial) |
| 331 | 404 | | |
| 332 | 405 | | Some steps were skipped by user choice. |
| 333 | | − | Your system was NOT fully configured." |
| 406 | + | Your system was NOT fully configured.$BACKUP_NOTE" |
| 334 | 407 | | ;; |
| 335 | 408 | | failed) |
| 336 | 409 | | gum style \ |
| @@ -341,9 +414,9 @@ Your system was NOT fully configured." |
| 341 | 414 | | "❌ Installation Failed |
| 342 | 415 | | |
| 343 | 416 | | One or more steps did not complete successfully. |
| 344 | | − | Check the logs above." |
| 417 | + | Check the logs above.$BACKUP_NOTE" |
| 345 | 418 | | ;; |
| 346 | 419 | | *) |
| 347 | | − | gum style --bold "Finished." |
| 420 | + | gum style --bold "Finished.$BACKUP_NOTE" |
| 348 | 421 | | ;; |
| 349 | 422 | | esac |