diff --git a/docs/superpowers/evidence/fresh-install-main-split-2026-08-17.json b/docs/superpowers/evidence/fresh-install-main-split-2026-08-17.json new file mode 100644 index 00000000..8f2d20de --- /dev/null +++ b/docs/superpowers/evidence/fresh-install-main-split-2026-08-17.json @@ -0,0 +1,58 @@ +{ + "intent": "Split fresh-install/main.sh (308 lines) into an entry script and a sourced package-helpers library, both under the 250-line cap, without changing the order of any top-level step.", + "scope": [ + "linux_configuration/scripts/single_use/fresh-install/main.sh 308 -> 200", + "New: .../fresh-install/lib/packages.sh 121 (the five installation functions)", + "Non-goal: any behaviour change; this script installs packages and was not executed" + ], + "changes": [ + "Extracted the five top-level functions - play_error_sound, install_from_aur, try_aur_install, process_packages, all_subpackages_installed - into lib/packages.sh, each with the comment directly above it.", + "main.sh sources the library immediately after `set -e`, using the SCRIPT_DIR idiom with a `# shellcheck source=` directive relative to the script's own directory, per the repo convention for new sourced libs.", + "The first attempt sliced by line range and was discarded: this file interleaves top-level commands (sudo -v, git config, cp of makepkg.conf and pacman.conf) between the function definitions, so a range slice swept them into the library where they would have run at source time, out of order. The rebuild extracts function blocks by brace counting and leaves every other line where it was." + ], + "verification": [ + { + "command": "bash meta/scripts/verify_shell_split.sh HEAD .../main.sh .../lib/packages.sh .../main.sh", + "result": "pass", + "evidence": "IDENTICAL: all 5 top-level functions moved verbatim, via the shfmt-normalised per-function hash." + }, + { + "command": "Structural comparison of the top-level flow before and after", + "result": "pass", + "evidence": "Exactly one line added to main.sh (the source line), zero lines missing, and the surviving order is preserved. Function blocks were removed structurally rather than by grep, so function bodies could not be mistaken for top-level code." + }, + { + "command": "bash -c '. lib/packages.sh; declare -F '", + "result": "pass", + "evidence": "All five functions defined, and the source completes with no side effects. This is the check that caught the bad first attempt: sourcing that version hit `sudo -v` and blocked on a password prompt." + }, + { + "command": "bash -n on both files", + "result": "pass", + "evidence": "Both parse. The script itself was deliberately NOT executed: it runs `sudo pacman -Sy`, copies over /etc/makepkg.conf and /etc/pacman.conf, and installs AUR packages." + }, + { + "command": "grep triage for a deployed copy of main.sh", + "result": "pass", + "evidence": "No other script copies or installs it, so the entry+lib shape is safe here - unlike the phone_focus_mode scripts, whose deploy.sh copies a hardcoded per-file list and never deploys lib/." + }, + { + "command": "pre-commit run --files ", + "result": "pass", + "evidence": "All hooks pass, including shfmt and shellcheck." + }, + { + "command": "bash ~/utils/scripts/check_file_length.sh --all", + "result": "pass", + "evidence": "31 files over cap, down from 32. The two files are 200 and 121 lines." + } + ], + "risks": [ + "main.sh at 200 lines has 50 lines of headroom. Its remaining bulk is the top-level package loop, which would need a different seam (a function extracted from inline code) to shrink further - that is a behaviour-preserving refactor rather than a move, so it was not attempted here.", + "This script is only ever run by hand on a fresh machine, so there is no test and no CI coverage for it. The verbatim proof and the source-time check are the whole of the evidence; a genuine end-to-end verification would mean reinstalling a machine." + ], + "rollback": [ + "git revert the commit; main.sh returns to its 308-line form and lib/packages.sh disappears.", + "After rollback, confirm bash -n main.sh still passes and that the file contains all five function definitions again." + ] +} diff --git a/linux_configuration/scripts/single_use/fresh-install/lib/packages.sh b/linux_configuration/scripts/single_use/fresh-install/lib/packages.sh new file mode 100755 index 00000000..76962d00 --- /dev/null +++ b/linux_configuration/scripts/single_use/fresh-install/lib/packages.sh @@ -0,0 +1,121 @@ +#!/usr/bin/env bash +# Package-installation helpers for the fresh-install run. +# +# Sourced by main.sh, which owns the ordering of the top-level steps; this +# file defines functions only, so sourcing it has no side effects. + +# Function to play a sound on error +play_error_sound() { + #pactl set-sink-volume @DEFAULT_SINK@ +50% + for _ in 1 2 3; do + paplay /usr/share/sounds/freedesktop/stereo/dialog-error.oga + done + #pactl set-sink-volume @DEFAULT_SINK@ -50% +} + +install_from_aur() { + local repo_url pkg_name repo_dir + repo_url="$1" + pkg_name="$2" + + mkdir -p "$HOME/aur" + cd "$HOME/aur" || return 1 + repo_dir="$(basename "$repo_url" .git)" + + if [ ! -d "$repo_dir" ]; then + git clone "$repo_url" + else + echo "Repository $repo_dir already cloned; updating" + (cd "$repo_dir" && git fetch --all -q && git reset --hard origin/HEAD -q || git pull --ff-only || true) + fi + cd "$repo_dir" || return 1 + + if pacman -Qi "$pkg_name" > /dev/null 2>&1; then + echo "$pkg_name is already installed" + return 0 + fi + + echo "Cleaning old package artifacts to avoid duplicate -U targets" + find . -maxdepth 1 -type f -name '*.pkg.tar.*' -delete 2> /dev/null || true + + echo "Building $pkg_name (clean build)" + # -c (clean up work dirs after) -C (clean build - remove src/ and pkg/ first) + if ! yes | makepkg -s -c -C --noconfirm --nocheck --skipchecksums --skipinteg --skippgpcheck --needed; then + echo "Build failed for $pkg_name" >&2 + return 1 + fi + + # Collect only the freshly built packages (should now be only current version) + mapfile -t built_pkgs < <(find . -maxdepth 1 -type f -name '*.pkg.tar.zst' -printf './%f\n') + if [ ${#built_pkgs[@]} -eq 0 ]; then + echo "No package files produced for $pkg_name" >&2 + return 1 + fi + + echo "Installing built package(s): ${built_pkgs[*]}" + if ! yes | sudo pacman -U --noconfirm "${built_pkgs[@]}"; then + echo "Installation failed for $pkg_name" >&2 + return 1 + fi +} + +# Helper: try to install from AUR and log result to done.txt/failed.txt +try_aur_install() { + local repo_url="$1" + local pkg_name="$2" + if install_from_aur "$repo_url" "$pkg_name"; then + echo "$pkg_name" >> done.txt + else + echo "$pkg_name" >> failed.txt + fi +} + +process_packages() { + local file_path + file_path="$1" + : > failed.txt + : > done.txt + + while IFS= read -r pkg_name; do + if [ -z "$pkg_name" ]; then + continue + fi + + local repo_url repo_dir + repo_url="https://aur.archlinux.org/${pkg_name}-git.git" + repo_dir="${pkg_name}-git" + + git clone "$repo_url" + if [ -d "$repo_dir" ] && [ -z "$(ls -A "$repo_dir")" ]; then + echo "Repository $repo_dir is empty, trying without -git suffix" + repo_url="https://aur.archlinux.org/${pkg_name}.git" + repo_dir="${pkg_name}" + + git clone "$repo_url" + if [ -d "$repo_dir" ] && [ -z "$(ls -A "$repo_dir")" ]; then + echo "Repository $repo_dir is empty, trying to install with pacman" + if sudo pacman -Sy --noconfirm "$pkg_name"; then + echo "$pkg_name" >> done.txt + else + echo "$pkg_name" >> failed.txt + fi + else + try_aur_install "$repo_url" "$pkg_name" + fi + else + try_aur_install "$repo_url" "$pkg_name" + fi + done < "$file_path" +} + +# Helper: Check if all subpackages are installed +# Returns 0 if ALL subpackages are installed, 1 otherwise +all_subpackages_installed() { + local -n sub_pkgs_ref=$1 + for subpkg in "${sub_pkgs_ref[@]}"; do + if ! pacman -Qi "$subpkg" &> /dev/null; then + return 1 + fi + done + return 0 +} diff --git a/linux_configuration/scripts/single_use/fresh-install/main.sh b/linux_configuration/scripts/single_use/fresh-install/main.sh index c020075c..b3952012 100755 --- a/linux_configuration/scripts/single_use/fresh-install/main.sh +++ b/linux_configuration/scripts/single_use/fresh-install/main.sh @@ -3,14 +3,9 @@ # shellcheck source=./detect_gpu_and_install.sh set -e -# Function to play a sound on error -play_error_sound() { - #pactl set-sink-volume @DEFAULT_SINK@ +50% - for _ in 1 2 3; do - paplay /usr/share/sounds/freedesktop/stereo/dialog-error.oga - done - #pactl set-sink-volume @DEFAULT_SINK@ -50% -} +# shellcheck source=lib/packages.sh +. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/lib/packages.sh" + # Trap errors and call the play_error_sound function trap 'play_error_sound' ERR @@ -29,100 +24,8 @@ else echo "GPU detection scripts not found; continuing without GPU specific installation." fi -install_from_aur() { - local repo_url pkg_name repo_dir - repo_url="$1" - pkg_name="$2" - mkdir -p "$HOME/aur" - cd "$HOME/aur" || return 1 - repo_dir="$(basename "$repo_url" .git)" - if [ ! -d "$repo_dir" ]; then - git clone "$repo_url" - else - echo "Repository $repo_dir already cloned; updating" - (cd "$repo_dir" && git fetch --all -q && git reset --hard origin/HEAD -q || git pull --ff-only || true) - fi - cd "$repo_dir" || return 1 - - if pacman -Qi "$pkg_name" > /dev/null 2>&1; then - echo "$pkg_name is already installed" - return 0 - fi - - echo "Cleaning old package artifacts to avoid duplicate -U targets" - find . -maxdepth 1 -type f -name '*.pkg.tar.*' -delete 2> /dev/null || true - - echo "Building $pkg_name (clean build)" - # -c (clean up work dirs after) -C (clean build - remove src/ and pkg/ first) - if ! yes | makepkg -s -c -C --noconfirm --nocheck --skipchecksums --skipinteg --skippgpcheck --needed; then - echo "Build failed for $pkg_name" >&2 - return 1 - fi - - # Collect only the freshly built packages (should now be only current version) - mapfile -t built_pkgs < <(find . -maxdepth 1 -type f -name '*.pkg.tar.zst' -printf './%f\n') - if [ ${#built_pkgs[@]} -eq 0 ]; then - echo "No package files produced for $pkg_name" >&2 - return 1 - fi - - echo "Installing built package(s): ${built_pkgs[*]}" - if ! yes | sudo pacman -U --noconfirm "${built_pkgs[@]}"; then - echo "Installation failed for $pkg_name" >&2 - return 1 - fi -} - -# Helper: try to install from AUR and log result to done.txt/failed.txt -try_aur_install() { - local repo_url="$1" - local pkg_name="$2" - if install_from_aur "$repo_url" "$pkg_name"; then - echo "$pkg_name" >> done.txt - else - echo "$pkg_name" >> failed.txt - fi -} - -process_packages() { - local file_path - file_path="$1" - : > failed.txt - : > done.txt - - while IFS= read -r pkg_name; do - if [ -z "$pkg_name" ]; then - continue - fi - - local repo_url repo_dir - repo_url="https://aur.archlinux.org/${pkg_name}-git.git" - repo_dir="${pkg_name}-git" - - git clone "$repo_url" - if [ -d "$repo_dir" ] && [ -z "$(ls -A "$repo_dir")" ]; then - echo "Repository $repo_dir is empty, trying without -git suffix" - repo_url="https://aur.archlinux.org/${pkg_name}.git" - repo_dir="${pkg_name}" - - git clone "$repo_url" - if [ -d "$repo_dir" ] && [ -z "$(ls -A "$repo_dir")" ]; then - echo "Repository $repo_dir is empty, trying to install with pacman" - if sudo pacman -Sy --noconfirm "$pkg_name"; then - echo "$pkg_name" >> done.txt - else - echo "$pkg_name" >> failed.txt - fi - else - try_aur_install "$repo_url" "$pkg_name" - fi - else - try_aur_install "$repo_url" "$pkg_name" - fi - done < "$file_path" -} sudo cp /etc/makepkg.conf /etc/makepkg.conf.bak sudo cp ./makepkg.conf /etc/makepkg.conf @@ -177,17 +80,6 @@ while IFS= read -r line; do fi done < "aur_packages.txt" -# Helper: Check if all subpackages are installed -# Returns 0 if ALL subpackages are installed, 1 otherwise -all_subpackages_installed() { - local -n sub_pkgs_ref=$1 - for subpkg in "${sub_pkgs_ref[@]}"; do - if ! pacman -Qi "$subpkg" &> /dev/null; then - return 1 - fi - done - return 0 -} # Read pacman packages from file declare -a pacman_packages