one section per concern, each announced and confirmed before it acts
Three sections where there were two, and none of them touches the machine until you say so: 2. System update upgrades what is already installed 3. Core utils what the distribution provides 4. Command-line tools lazydocker, lazygit, starship, fastfetch The split matters because these are different kinds of change and deserve separate answers. System update is the only step in the whole script that moves versions of software already on the machine; core utils only ever adds what is absent; and the four tools are upstream binaries the distribution does not ship at all. Previously the update and the core packages were one step and the tools were tacked onto the end of it, so agreeing to "essentials" meant agreeing to all three at once. Every section now prints what it will install and what it is leaving alone, then asks. Enter means yes — unlike the machine-role question, which has no default, because these are "do the thing you already asked for" and making twenty of them require a deliberate keystroke would train people to hold the y key down. ASSUME_YES=1 answers all of them for an unattended run, and EOF fails with that named rather than spinning. Refusing is recorded rather than glossed: LAST_SKIPPED feeds the summary, so a declined section reads "Core utils: SKIPPED by request — cowsay neofetch" instead of quietly reporting nothing installed. Nothing to install means no prompt at all — there is nothing to agree to. announce_plan takes the array NAMES rather than their contents, because once a list has been through word splitting an empty one cannot be told from a missing one. Verified all three paths: accept, refuse, and nothing-to-do. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -135,6 +135,34 @@ prompt_value() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Ask before acting. Every section that changes the machine goes through this, so
|
||||||
|
# a run is a sequence of things you agreed to rather than a wall of output you
|
||||||
|
# read afterwards to find out what happened.
|
||||||
|
#
|
||||||
|
# Enter means yes — unlike the machine-role question, which has no default. These
|
||||||
|
# are "do the thing you already asked for", and making twenty of them require a
|
||||||
|
# deliberate keystroke would train people to hold the y key down.
|
||||||
|
#
|
||||||
|
# ASSUME_YES=1 answers all of them, for an unattended run.
|
||||||
|
confirm() {
|
||||||
|
local message="${1:-Proceed?}" answer
|
||||||
|
[[ "${ASSUME_YES:-}" == "1" ]] && return 0
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
# EOF is not a yes. Without this an unattended run without ASSUME_YES would
|
||||||
|
# spin here forever.
|
||||||
|
if ! read -rp " ${message} [Y/n]: " answer; then
|
||||||
|
echo ""
|
||||||
|
fail "No answer. Set ASSUME_YES=1 to run without prompts."
|
||||||
|
fi
|
||||||
|
case "$answer" in
|
||||||
|
"" | y | Y | yes | Yes) return 0 ;;
|
||||||
|
n | N | no | No) return 1 ;;
|
||||||
|
*) warn "Answer y or n." ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
# Run a block as the created user (login shell, inherits HOME)
|
# Run a block as the created user (login shell, inherits HOME)
|
||||||
as_user() {
|
as_user() {
|
||||||
sudo -u "$USERNAME" -i bash -c "$1"
|
sudo -u "$USERNAME" -i bash -c "$1"
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ MACHINE_SETUP_PACKAGES_LOADED=1
|
|||||||
# packages it deliberately left alone.
|
# packages it deliberately left alone.
|
||||||
LAST_INSTALLED=()
|
LAST_INSTALLED=()
|
||||||
LAST_KEPT=()
|
LAST_KEPT=()
|
||||||
|
LAST_SKIPPED=()
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# The sections
|
# The sections
|
||||||
@@ -155,6 +156,7 @@ pkg_install() {
|
|||||||
|
|
||||||
local pkg
|
local pkg
|
||||||
local -a missing=() present=()
|
local -a missing=() present=()
|
||||||
|
LAST_SKIPPED=()
|
||||||
for pkg in "$@"; do
|
for pkg in "$@"; do
|
||||||
if pkg_is_installed "$pkg"; then present+=("$pkg"); else missing+=("$pkg"); fi
|
if pkg_is_installed "$pkg"; then present+=("$pkg"); else missing+=("$pkg"); fi
|
||||||
done
|
done
|
||||||
@@ -162,23 +164,50 @@ pkg_install() {
|
|||||||
LAST_INSTALLED=("${missing[@]}")
|
LAST_INSTALLED=("${missing[@]}")
|
||||||
LAST_KEPT=("${present[@]}")
|
LAST_KEPT=("${present[@]}")
|
||||||
|
|
||||||
info "$label — installs what is missing, keeps what you already have"
|
announce_plan "$label" present missing || return 0
|
||||||
((${#present[@]})) && echo " already here: ${present[*]}"
|
pkg_install_now "${missing[@]}"
|
||||||
|
}
|
||||||
|
|
||||||
if ((${#missing[@]} == 0)); then
|
# Print what a section is about to do and ask permission for it.
|
||||||
|
#
|
||||||
|
# Takes the NAMES of the two arrays rather than their contents, because a list
|
||||||
|
# passed by value cannot be told apart from an empty one once it has been through
|
||||||
|
# word splitting.
|
||||||
|
#
|
||||||
|
# Returns non-zero when there is nothing to do, or when the answer was no — in
|
||||||
|
# both cases the caller should skip its action. LAST_INSTALLED is cleared on a
|
||||||
|
# refusal so the summary does not claim work that never happened.
|
||||||
|
announce_plan() {
|
||||||
|
local label="$1"
|
||||||
|
local -n _present="$2"
|
||||||
|
local -n _missing="$3"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
info "${label} — installs what is missing, keeps what you already have"
|
||||||
|
((${#_present[@]})) && echo " already here: ${_present[*]}"
|
||||||
|
|
||||||
|
if ((${#_missing[@]} == 0)); then
|
||||||
echo " to install: nothing, all present"
|
echo " to install: nothing, all present"
|
||||||
return 0
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo " to install: ${missing[*]}"
|
echo " to install: ${_missing[*]}"
|
||||||
pkg_install_now "${missing[@]}"
|
if ! confirm "Proceed?"; then
|
||||||
|
warn "skipped by request"
|
||||||
|
LAST_INSTALLED=()
|
||||||
|
LAST_SKIPPED=("${_missing[@]}")
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
# One summary line describing what a section actually did, from LAST_INSTALLED
|
# One summary line describing what a section actually did, from LAST_INSTALLED
|
||||||
# and LAST_KEPT. Call straight after pkg_install or tools_install.
|
# and LAST_KEPT. Call straight after pkg_install or tools_install.
|
||||||
summarise_last() {
|
summarise_last() {
|
||||||
local label="$1"
|
local label="$1"
|
||||||
if ((${#LAST_INSTALLED[@]} == 0)); then
|
if ((${#LAST_SKIPPED[@]})); then
|
||||||
|
SUMMARY+=("$label: SKIPPED by request — ${LAST_SKIPPED[*]}")
|
||||||
|
elif ((${#LAST_INSTALLED[@]} == 0)); then
|
||||||
SUMMARY+=("$label: already present, nothing installed")
|
SUMMARY+=("$label: already present, nothing installed")
|
||||||
elif ((${#LAST_KEPT[@]} == 0)); then
|
elif ((${#LAST_KEPT[@]} == 0)); then
|
||||||
SUMMARY+=("$label installed: ${LAST_INSTALLED[*]}")
|
SUMMARY+=("$label installed: ${LAST_INSTALLED[*]}")
|
||||||
|
|||||||
@@ -109,6 +109,7 @@ tools_install() {
|
|||||||
|
|
||||||
local tool
|
local tool
|
||||||
local -a missing=() present=()
|
local -a missing=() present=()
|
||||||
|
LAST_SKIPPED=()
|
||||||
for tool in "$@"; do
|
for tool in "$@"; do
|
||||||
if tool_is_installed "$tool"; then present+=("$tool"); else missing+=("$tool"); fi
|
if tool_is_installed "$tool"; then present+=("$tool"); else missing+=("$tool"); fi
|
||||||
done
|
done
|
||||||
@@ -116,15 +117,8 @@ tools_install() {
|
|||||||
LAST_INSTALLED=("${missing[@]}")
|
LAST_INSTALLED=("${missing[@]}")
|
||||||
LAST_KEPT=("${present[@]}")
|
LAST_KEPT=("${present[@]}")
|
||||||
|
|
||||||
info "$label — installs what is missing, keeps what you already have"
|
announce_plan "$label" present missing || return 0
|
||||||
((${#present[@]})) && echo " already here: ${present[*]}"
|
|
||||||
|
|
||||||
if ((${#missing[@]} == 0)); then
|
|
||||||
echo " to install: nothing, all present"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo " to install: ${missing[*]}"
|
|
||||||
for tool in "${missing[@]}"; do
|
for tool in "${missing[@]}"; do
|
||||||
info " installing ${tool}..."
|
info " installing ${tool}..."
|
||||||
"tool_install_${tool}"
|
"tool_install_${tool}"
|
||||||
|
|||||||
@@ -75,35 +75,56 @@ fi
|
|||||||
USER_HOME="/home/$USERNAME"
|
USER_HOME="/home/$USERNAME"
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# 2. System Update & Essentials
|
# 2. System update
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
#
|
||||||
|
# Its own section because it is the only thing in the script that moves versions
|
||||||
|
# of software already on the machine. Everything else only ever adds what is
|
||||||
|
# absent, so this is the one that deserves to be refused on its own.
|
||||||
|
|
||||||
step "System Update & Essentials"
|
step "System update"
|
||||||
if ! skip; then
|
if ! skip; then
|
||||||
info "Refreshing the package index..."
|
info "Refreshing the package index..."
|
||||||
pkg_refresh
|
pkg_refresh >/dev/null
|
||||||
|
|
||||||
# The one place that deliberately moves versions of things already installed.
|
echo ""
|
||||||
# Everything else in this script only ever adds what is absent.
|
info "System update — upgrades packages already installed to their latest versions"
|
||||||
info "Upgrading installed packages..."
|
echo " this is the only step that changes software already on this machine"
|
||||||
|
|
||||||
|
if confirm "Proceed?"; then
|
||||||
pkg_upgrade_all
|
pkg_upgrade_all
|
||||||
|
ok "System upgraded"
|
||||||
# shellcheck disable=SC2046 # word splitting is how the list is passed
|
SUMMARY+=("System packages upgraded")
|
||||||
pkg_install "Core packages" $(pkgs_core)
|
else
|
||||||
|
warn "skipped by request"
|
||||||
summarise_last "Core packages"
|
SUMMARY+=("System update: SKIPPED by request")
|
||||||
ok "System updated and core packages in place"
|
fi
|
||||||
step_ok
|
step_ok
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# 2b. Command-line tools
|
# 3. Core utils
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
#
|
#
|
||||||
# Separate from the packages above because they are a different thing: upstream
|
# What the distribution provides: the six this script would break without, and
|
||||||
# binaries on their own release cadence, not anything the distribution ships.
|
# the command-line tools that make a machine worth sitting at.
|
||||||
# Lumping them in made a run look like it was installing system packages and
|
|
||||||
# then start downloading tarballs unannounced.
|
step "Core utils"
|
||||||
|
if ! skip; then
|
||||||
|
# shellcheck disable=SC2046 # word splitting is how the list is passed
|
||||||
|
pkg_install "Core utils" $(pkgs_core)
|
||||||
|
summarise_last "Core utils"
|
||||||
|
step_ok
|
||||||
|
fi
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# 4. Command-line tools
|
||||||
|
# =============================================================================
|
||||||
|
#
|
||||||
|
# A different thing from core utils, and kept apart from them: upstream binaries
|
||||||
|
# fetched from upstream, on their own release cadence, none of which the
|
||||||
|
# distribution ships. Lumping them in made a run look like it was installing
|
||||||
|
# system packages and then start pulling tarballs unannounced.
|
||||||
|
|
||||||
step "Command-line tools"
|
step "Command-line tools"
|
||||||
if ! skip; then
|
if ! skip; then
|
||||||
|
|||||||
Reference in New Issue
Block a user