full read: fix the set -e footguns a full run would have hit

Read the whole thing — 2392 lines of entry point and 2700 of libraries — looking
for what shellcheck cannot see. shellcheck itself is clean at error level; its
warnings are cross-file false positives and one deliberate tilde in a display
string. Everything below is a real defect.

── The Git section aborted on any machine where git was not already configured ──

`git config --global --get <key>` exits NON-ZERO when the key is simply unset,
and `VAR="$(git_get …)"` propagates that under `set -e`. So on a fresh machine —
the case this script exists for — the section died at its first assignment,
before printing anything, and took the remaining nine sections with it.

It passed every earlier test because those harnesses sourced the section under a
`bash -c` with no `set -e`. Verified now against a genuinely fresh account with
the real script: the section completes and writes a correct .gitconfig.

── An optional step failing aborted the whole run ──

Twelve functions ended on a command that can fail — `systemctl enable --now
earlyoom`, `systemctl restart systemd-logind`, `chsh`, `sysctl -w`, `chown -R`,
the oh-my-zsh installer, and others. Called as plain commands under `set -e`, any
one of them failing ends the script, so a masked unit or a container without
systemd would abort a 28-section run over an optional improvement.

They now return 0 explicitly and the callers verify the outcome instead — which
also fixed a lie: the sleep section printed "sleep disabled, logind reloaded"
whether or not the restart had worked. It now checks the targets and the logind
values and reports honestly.

── chown user:user assumed the primary group is named after the user ──

True on Debian and Ubuntu, which create a group per user. Not true for an account
from LDAP, or made with `useradd -g users`, or on an image with a shared group —
there `install -g <user>` fails with "invalid group" and the step aborts. Proved
it against an account whose primary group is `oddgroup`: the old form fails, the
new one gets ownership right. Eight call sites now ask `id -gn`.

── Also hardened ──

agent_path and current_editor gained `|| true` for the same reason git_get needed
it: "nothing is set" is an answer, not a failure.

Verified afterwards: shellcheck clean at error level, every section runs
standalone without aborting, and the two apparent failures in that sweep are
correct behaviour — Timezone and Git refusing an empty answer from /dev/null.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-12 21:34:20 +00:00
co-authored by Claude Opus 5
parent 30052e3295
commit e120dfa36e
8 changed files with 102 additions and 16 deletions
+21 -4
View File
@@ -42,7 +42,12 @@ MACHINE_SETUP_DEV_LOADED=1
# Found because the writes failed silently: the section reported "written" while
# nothing had been. Both wrappers now run in a subshell from /, which every
# account can stat, and their exit status is checked by the caller.
git_get() { (cd / && sudo -H -u "$USERNAME" git config --global --get "$1" 2>/dev/null); }
# `git config --get` exits NON-ZERO when the key is simply unset, and
# `VAR="$(git_get …)"` propagates that under `set -e`. So on a machine where git
# has never been configured — the fresh machine this script exists for — reading
# the current value aborted the run before the section had printed anything.
# Missing a value is an answer here, not a failure.
git_get() { (cd / && sudo -H -u "$USERNAME" git config --global --get "$1" 2>/dev/null) || true; }
git_set() { (cd / && sudo -H -u "$USERNAME" git config --global "$1" "$2"); }
# Is there anything configured at all?
@@ -89,6 +94,11 @@ install_oh_my_zsh() {
# only ever called when there is none.
sudo -H -u "$USERNAME" sh -c \
"$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" "" --unattended >/dev/null 2>&1
# Returns 0 whatever happens. This is an optional improvement, and a
# function that ends on a failing command is fatal under `set -e` when it
# is called as a plain command — which would abort the remaining sections
# over something the run could simply report. The caller checks the outcome.
return 0
}
# `chsh` is what actually changes the login shell. Asked separately from
@@ -98,6 +108,11 @@ set_login_shell() {
local shell="$1"
grep -qxF "$shell" /etc/shells || echo "$shell" >>/etc/shells
chsh -s "$shell" "$USERNAME"
# Returns 0 whatever happens. This is an optional improvement, and a
# function that ends on a failing command is fatal under `set -e` when it
# is called as a plain command — which would abort the remaining sections
# over something the run could simply report. The caller checks the outcome.
return 0
}
# -----------------------------------------------------------------------------
@@ -162,7 +177,7 @@ nvim_install() {
nvim_clone_config() {
local repo="$1" dest="${USER_HOME}/.config/nvim"
install -d -m 0755 -o "$USERNAME" -g "$USERNAME" "${USER_HOME}/.config"
install -d -m 0755 -o "$USERNAME" -g "$(user_group)" "${USER_HOME}/.config"
(cd / && sudo -H -u "$USERNAME" git clone --depth 1 "$repo" "$dest" >/dev/null 2>&1) || return 1
# The starter is a template, not something to track. Left in place for a
@@ -325,7 +340,7 @@ agent_path() {
echo "$bin"
return
}
command -v "$name" 2>/dev/null
command -v "$name" 2>/dev/null || true
}
agent_is_npm_install() { [[ "$(readlink -f "$(agent_path "$1")" 2>/dev/null)" == */node_modules/* ]]; }
@@ -368,7 +383,9 @@ editor_candidates() {
for e in nvim vim nano; do command -v "$e" &>/dev/null && echo "$e"; done
}
current_editor() { (cd / && sudo -H -u "$USERNAME" bash -lc 'echo "${EDITOR:-}"' 2>/dev/null); }
# `|| true` for the same reason git_get has it: "not set" is an answer, and an
# assignment from a function that exits non-zero aborts the run under `set -e`.
current_editor() { (cd / && sudo -H -u "$USERNAME" bash -lc 'echo "${EDITOR:-}"' 2>/dev/null) || true; }
set_system_editor() {
local editor="$1" path