From 2ffedd076e45198f31dbe35827d12b51fc9581b0 Mon Sep 17 00:00:00 2001 From: The_miro Date: Fri, 3 Jul 2026 14:22:03 +0200 Subject: [PATCH] fix(installer): flush stray tty input, split shell rc's from shell setup The first checklist (Select Components) could silently auto-confirm its defaults, swallowing the user's toggle: tui-install.sh runs right after the long, mostly-unattended base install, and keystrokes buffered during that wait were consumed by the script's very first read. Drain the tty before the first prompt. Also split the "shell" component into "shell" (tools/plugins/programs) and a new "shellrc" (personal .bashrc/.zshrc/.vimrc), so the shell environment can be installed without overwriting the user's rc files. shell-setup.sh reads MARCHY_DEPLOY_SHELL_RC to gate the rc symlinks and now also deploys .vimrc, which /etc/skel sync already expected but nothing ever created. Removes the separate, easily-desynced "Shell Config for New Users" prompt in favor of deriving /etc/skel behavior from the same shellrc choice, and mirrors the removal in generate-answerfile.sh. Co-Authored-By: Claude Sonnet 5 --- setup/modules/shell-setup.sh | 18 ++++++-- setup/tools/generate-answerfile.sh | 13 +----- setup/tui-install.sh | 73 +++++++++++++++++++----------- 3 files changed, 62 insertions(+), 42 deletions(-) diff --git a/setup/modules/shell-setup.sh b/setup/modules/shell-setup.sh index 7f56634..0bbcaf5 100755 --- a/setup/modules/shell-setup.sh +++ b/setup/modules/shell-setup.sh @@ -12,7 +12,8 @@ # ║ user — must NOT run as root. ║ # ║ ║ # ║ WHAT GETS DEPLOYED: ║ -# ║ ~/.bashrc, ~/.zshrc — symlinks to Dotfiles repo ║ +# ║ ~/.bashrc, ~/.zshrc, ~/.vimrc — symlinks to Dotfiles repo, unless ║ +# ║ MARCHY_DEPLOY_SHELL_RC=0 (defaults to 1 outside the TUI installer) ║ # ║ ~/.config/starship.toml — symlink to repo ║ # ║ ~/.config/nvim/ — symlink to repo/nvim/ ║ # ║ ~/.config/micro/ — copy from repo/micro/ (plugin state needs cp)║ @@ -160,9 +161,18 @@ if [[ -e "$HOME/.config" && ! -w "$HOME/.config" ]]; then sudo chown -R "$(id -un):$(id -gn)" "$HOME/.config" 2>/dev/null || true fi -# Shell init files — symlink so edits in the repo apply immediately -ln -sf ~/Dotfiles/.bashrc ~/.bashrc -ln -sf ~/Dotfiles/.zshrc ~/.zshrc +# Shell init files — symlink so edits in the repo apply immediately. +# MARCHY_DEPLOY_SHELL_RC lets the caller install the shell tools/plugins below +# (zsh, oh-my-zsh, neovim, yazi, micro, starship, …) without overwriting the +# user's existing .bashrc/.zshrc/.vimrc with the_miro's personal versions. +# Defaults to deploying them when run standalone (outside the TUI installer). +if [[ "${MARCHY_DEPLOY_SHELL_RC:-1}" == "1" ]]; then + ln -sf ~/Dotfiles/.bashrc ~/.bashrc + ln -sf ~/Dotfiles/.zshrc ~/.zshrc + ln -sf ~/Dotfiles/.vimrc ~/.vimrc +else + skip "Personal shell rc's not selected — leaving .bashrc/.zshrc/.vimrc as-is." +fi # Starship prompt config — symlink into ~/.config/ ln -sf ~/Dotfiles/starship.toml ~/.config/starship.toml diff --git a/setup/tools/generate-answerfile.sh b/setup/tools/generate-answerfile.sh index d952c62..6acdc70 100755 --- a/setup/tools/generate-answerfile.sh +++ b/setup/tools/generate-answerfile.sh @@ -201,7 +201,6 @@ dialog --backtitle "$BACKTITLE" \ AF_COMPONENTS="" AF_DE="none" AF_APPS="" -AF_SHELL_RC="dotfiles" AF_COLOR_TEXT="" AF_COLOR_BG="" AF_COLOR_HIGHLIGHT="" @@ -324,14 +323,6 @@ if [[ "$AF_RUN_TUI" == "true" ]]; then 3>&1 1>&2 2>&3) || AF_APPS="" # END GENERATED MODULES: module-checklist - # ── Shell RC preference ─────────────────────────────────────────────────── - AF_SHELL_RC=$(dialog --backtitle "$BACKTITLE" \ - --title " Shell Config for New Users " \ - --menu "\n Should new users on this machine inherit the dotfiles' rc files?\n (Controls what gets copied to /etc/skel)\n" 13 68 2 \ - "dotfiles" "Use the_miro's .zshrc / .bashrc / .vimrc from dotfiles" \ - "defaults" "Skip — use system defaults for new users" \ - 3>&1 1>&2 2>&3) || AF_SHELL_RC="defaults" - # ── Colorway ────────────────────────────────────────────────────────────── # Read defaults from repo colors.conf declare -A _cdef @@ -393,7 +384,6 @@ SUMMARY+=" Encrypt: $AF_ENCRYPT\n" SUMMARY+=" FIDO2 root: $AF_FIDO2_ROOT / FIDO2 user: $AF_FIDO2_USER\n" SUMMARY+=" Run TUI: $AF_RUN_TUI\n" [[ -n "$AF_DE" && "$AF_DE" != "none" ]] && SUMMARY+=" DE: $AF_DE\n" -SUMMARY+=" Shell rc: $AF_SHELL_RC\n" [[ -n "$AF_COLOR_TEXT" ]] && SUMMARY+=" Colors: custom\n" dialog --backtitle "$BACKTITLE" \ @@ -435,8 +425,7 @@ mkdir -p "$(dirname "$OUTPUT")" printf ' "run_tui": %s,\n' "$AF_RUN_TUI" printf ' "components": %s,\n' "$(_words_to_json_array "$AF_COMPONENTS")" printf ' "desktop_environment": %s,\n' "$(json_str "$AF_DE")" - printf ' "apps": %s,\n' "$(_words_to_json_array "$AF_APPS")" - printf ' "shell_rc": %s' "$(json_str "$AF_SHELL_RC")" + printf ' "apps": %s' "$(_words_to_json_array "$AF_APPS")" if [[ -n "$AF_COLOR_TEXT" ]]; then printf ',\n "colors": {\n' diff --git a/setup/tui-install.sh b/setup/tui-install.sh index bb03e78..5c141eb 100755 --- a/setup/tui-install.sh +++ b/setup/tui-install.sh @@ -257,6 +257,18 @@ log_sep() { printf "\n══════════════════════════════════\n %s\n %s\n" "$1" "$(date)" >> "$LOG" } +flush_tty_input() { + # Discard any keystrokes queued on the terminal before the first prompt. + # This script normally runs right after the long, mostly-unattended base + # install (partitioning, pacstrap, …); impatient Enter presses or other + # stray input typed during that wait sit in the tty's input buffer and + # would otherwise be consumed by the very first `read` here — making the + # first checklist appear to auto-confirm its defaults before the user can + # toggle anything. A short non-blocking drain loop clears the queue. + local junk + while read -r -t 0.05 junk /dev/null; do :; done +} + run_module() { local label="$1" script="$2" STEP=$(( STEP + 1 )) @@ -307,13 +319,14 @@ count_steps() { # run_module() can display accurate [N/TOTAL] progress from the first step. # Each glob test mirrors the corresponding run_module() call below; they # must stay in sync whenever a new module is added or removed. - local c="$1" de="$2" a="${3:-}" + local c=" $1 " de="$2" a="${3:-}" TOTAL=0 # Base components: each keyword maps to exactly one module script. - [[ "$c" == *"pkg"* ]] && TOTAL=$(( TOTAL + 1 )) - [[ "$c" == *"core"* ]] && TOTAL=$(( TOTAL + 1 )) - [[ "$c" == *"svc"* ]] && TOTAL=$(( TOTAL + 1 )) - [[ "$c" == *"shell"* ]] && TOTAL=$(( TOTAL + 1 )) + # Padded with spaces so "shell" doesn't false-match the "shellrc" tag. + [[ "$c" == *" pkg "* ]] && TOTAL=$(( TOTAL + 1 )) + [[ "$c" == *" core "* ]] && TOTAL=$(( TOTAL + 1 )) + [[ "$c" == *" svc "* ]] && TOTAL=$(( TOTAL + 1 )) + [[ "$c" == *" shell "* ]] && TOTAL=$(( TOTAL + 1 )) # A non-"none" DE selection always installs exactly one DE module. [[ "$de" != "none" ]] && TOTAL=$(( TOTAL + 1 )) # Optional app modules: one word-boundary check per app, one increment per match. @@ -410,7 +423,6 @@ count_steps() { AF_COMPONENTS="" AF_DE="none" AF_APPS="" -AF_SHELL_RC="dotfiles" AF_COLOR_TEXT="" AF_COLOR_BG="" AF_COLOR_HIGHLIGHT="" @@ -427,7 +439,6 @@ load_answerfile() { AF_COMPONENTS=$(jq -r '(.components // []) | join(" ")' "$ANSWERFILE") AF_DE=$(jq -r '.desktop_environment // "none"' "$ANSWERFILE") AF_APPS=$(jq -r '(.apps // []) | join(" ")' "$ANSWERFILE") - AF_SHELL_RC=$(jq -r '.shell_rc // "dotfiles"' "$ANSWERFILE") # Color values are optional; an empty string means "keep the repo default". AF_COLOR_TEXT=$(jq -r '.colors.COLOR_TEXT // ""' "$ANSWERFILE") AF_COLOR_BG=$(jq -r '.colors.COLOR_BG // ""' "$ANSWERFILE") @@ -484,6 +495,7 @@ printf "Dotfiles install: %s\nDotfiles dir: %s\n" "$(date)" "$DOTFILES_DIR" > # ── Welcome ─────────────────────────────────────────────────────────────────── if ! $ANSWERFILE_MODE; then + flush_tty_input ui_msgbox " Welcome " "\ the_miro's Arch dotfiles installer\n\ Cyberqueer · Wayland · Hyprland\n\ @@ -503,13 +515,17 @@ if $ANSWERFILE_MODE; then COMPONENTS="$AF_COMPONENTS" else # ui_checklist takes tag/desc/state triplets and echoes the selected tags. - # All four components are pre-selected ("on") because they form the expected base. + # All five components are pre-selected ("on") because they form the expected base. + # "shellrc" is split out from "shell" so the shell tools/plugins/programs can be + # installed without symlinking the personal .bashrc/.zshrc/.vimrc over them — + # it only has an effect when "shell" is also selected. # Entering 'q' returns exit code 1; the '|| { ...; exit 0; }' treats that as a clean abort. COMPONENTS=$(ui_checklist " Select Components " "Optional base components — all enabled by default:" \ "pkg" "pkg Package managers yay · nvm · rust" on \ "core" "core Core packages 100+ base system packages" on \ "svc" "svc Core services NetworkManager · cronie · fail2ban" on \ - "shell" "shell Shell setup zsh · nvim · yazi · micro · starship" on) \ + "shell" "shell Shell setup zsh · nvim · yazi · micro · starship" on \ + "shellrc" "shellrc Personal shell rc's .bashrc/.zshrc/.vimrc (needs shell)" on) \ || { clear; echo "Aborted."; exit 0; } fi @@ -624,13 +640,13 @@ else fi # ── Shell RC preference ─────────────────────────────────────────────────────── -if $ANSWERFILE_MODE; then - SHELL_RC="$AF_SHELL_RC" +# Derived from the "shellrc" component rather than asked separately: it only takes +# effect when "shell" also runs, and whether new users (via /etc/skel) inherit the +# rc files should follow the same choice as the current user's deployment. +if [[ " $COMPONENTS " == *" shell "* && " $COMPONENTS " == *" shellrc "* ]]; then + SHELL_RC="dotfiles" else - SHELL_RC=$(ui_menu " Shell Config for New Users " \ - "Should new users on this machine inherit the dotfiles' rc files?\n (Controls what gets copied to /etc/skel)" \ - "dotfiles" "Use the_miro's .zshrc / .bashrc / .vimrc from dotfiles" \ - "defaults" "Skip — use system defaults for new users") || SHELL_RC="defaults" + SHELL_RC="defaults" fi # ── Confirmation (interactive mode only) ────────────────────────────────────── @@ -638,13 +654,14 @@ if ! $ANSWERFILE_MODE; then # Build a human-readable summary of everything that will be installed so the # user can review the full list before any changes are made to the system. SUMMARY="" - [[ "$COMPONENTS" == *"pkg"* ]] && SUMMARY+=" ✦ Package managers (yay, nvm, rust)\n" - [[ "$COMPONENTS" == *"core"* ]] && SUMMARY+=" ✦ Core packages\n" - [[ "$COMPONENTS" == *"svc"* ]] && SUMMARY+=" ✦ Core services\n" - [[ "$COMPONENTS" == *"shell"* ]] && SUMMARY+=" ✦ Shell setup\n" + [[ " $COMPONENTS " == *" pkg "* ]] && SUMMARY+=" ✦ Package managers (yay, nvm, rust)\n" + [[ " $COMPONENTS " == *" core "* ]] && SUMMARY+=" ✦ Core packages\n" + [[ " $COMPONENTS " == *" svc "* ]] && SUMMARY+=" ✦ Core services\n" + [[ " $COMPONENTS " == *" shell "* ]] && SUMMARY+=" ✦ Shell setup (plugins & programs)\n" + [[ "$SHELL_RC" == "dotfiles" ]] && SUMMARY+=" ✦ Personal shell rc's (.bashrc/.zshrc/.vimrc)\n" [[ "$DE" != "none" && "$DE" != "" ]] && SUMMARY+=" ✦ Desktop environment: $DE\n" - [[ "$SHELL_RC" == "dotfiles" ]] && SUMMARY+=" ✦ Shell rc files → /etc/skel (dotfiles)\n" \ - || SUMMARY+=" ✦ Shell rc files → /etc/skel (system defaults)\n" + [[ "$SHELL_RC" == "dotfiles" ]] && SUMMARY+=" ✦ Shell rc's → /etc/skel for new users (dotfiles)\n" \ + || SUMMARY+=" ✦ Shell rc's → /etc/skel for new users (system defaults)\n" if [[ -n "$SELECTED_APPS" ]]; then SUMMARY+="\n Applications:\n" @@ -741,12 +758,16 @@ fi count_steps "$COMPONENTS" "$DE" "$SELECTED_APPS" # ── Installation: base components ───────────────────────────────────────────── -# Each guard uses glob matching against the space-separated COMPONENTS string. +# Each guard uses padded-space glob matching against the COMPONENTS string so +# "shell" doesn't false-match the "shellrc" tag. # Order matters: package managers must be installed before packages that need yay/rust. -[[ "$COMPONENTS" == *"pkg"* ]] && run_module "Package Managers" "$MODULES/package-managers.sh" -[[ "$COMPONENTS" == *"core"* ]] && run_module "Core Packages" "$MODULES/core-packages.sh" -[[ "$COMPONENTS" == *"svc"* ]] && run_module "Core Services" "$MODULES/core.sh" -[[ "$COMPONENTS" == *"shell"* ]] && run_module "Shell Setup" "$MODULES/shell-setup.sh" +[[ " $COMPONENTS " == *" pkg "* ]] && run_module "Package Managers" "$MODULES/package-managers.sh" +[[ " $COMPONENTS " == *" core "* ]] && run_module "Core Packages" "$MODULES/core-packages.sh" +[[ " $COMPONENTS " == *" svc "* ]] && run_module "Core Services" "$MODULES/core.sh" +# MARCHY_DEPLOY_SHELL_RC tells shell-setup.sh whether to symlink the personal +# .bashrc/.zshrc/.vimrc, independently of installing the shell tools/plugins themselves. +[[ " $COMPONENTS " == *" shellrc "* ]] && export MARCHY_DEPLOY_SHELL_RC=1 || export MARCHY_DEPLOY_SHELL_RC=0 +[[ " $COMPONENTS " == *" shell "* ]] && run_module "Shell Setup" "$MODULES/shell-setup.sh" # Route the single selected DE value to its corresponding install script. # "none" is the skip sentinel — no case branch matches it intentionally.