fresh-install: split main.sh 308 -> 200 + 121

The five package-installation functions move to lib/packages.sh; main.sh
keeps the top-level flow and sources the library after `set -e`.

The first attempt sliced by line range and was thrown away. This file
interleaves top-level commands - sudo -v, git config, the cp of
makepkg.conf and pacman.conf - between its function definitions, so a range
slice swept them into the library, where they would have run at source time
and in the wrong order. Sourcing that version blocked on a sudo password
prompt, which is how it was caught. The rebuild extracts function blocks by
brace counting and leaves every other line exactly where it was.

Verified with the new shfmt harness (all 5 functions verbatim), by a
structural comparison of the top-level flow showing exactly one line added
and none missing, and by sourcing the library to confirm it defines all five
functions with no side effects. The script itself was NOT run: it does
`sudo pacman -Sy`, overwrites /etc/makepkg.conf and /etc/pacman.conf, and
installs AUR packages.

Over-cap files 32 -> 31.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
This commit is contained in:
Krzysztof Rudnicki 2026-08-17 14:11:26 +02:00
parent 23baf9d63f
commit 9b30329ca3
3 changed files with 182 additions and 111 deletions

View File

@ -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 <each of the five>'",
"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 <both 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."
]
}

View File

@ -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
}

View File

@ -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