offer to add an ssh key even when one already exists

The zip option was already gone — it never came across in the port, since the
file is key material that cannot live in the repository and the script no longer
sits next to it. Pasting a public key was already the first option. What was
missing is the case where the account HAS a key: the section went straight to
hardening, so there was no way to authorise a second machine, a rebuilt laptop or
anyone else, and the original had no way to do it at all.

The same menu is now offered either way. What differs is whether it can be
declined without consequence: with no key, declining means the hardening below
refuses too, and the run says so rather than quietly moving on.

confirm() takes an optional default so this one can be [y/N]. Most questions in
this script are "do the thing you already asked for" and Enter should mean yes; a
genuine extra defaulting to yes is how people end up agreeing to things by
reflex.

A pasted key is trimmed before validation. Copying from a terminal or a password
manager routinely brings leading or trailing whitespace, and ssh-keygen will not
parse a key with it attached — which would have read as "that is not a valid
key" for a key that is perfectly fine.

Also corrected the reason unzip is in core utils, which still said it was there
to open ssh-keys.zip.

Verified: the add-another prompt appears and defaults to no, a whitespace-wrapped
key is trimmed and accepted, and the already-hardened path is unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-12 18:42:58 +00:00
co-authored by Claude Opus 5
parent 9591f917f5
commit 192293cdca
3 changed files with 39 additions and 12 deletions
+12 -4
View File
@@ -145,18 +145,26 @@ prompt_value() {
#
# ASSUME_YES=1 answers all of them, for an unattended run.
confirm() {
local message="${1:-Proceed?}" answer
[[ "${ASSUME_YES:-}" == "1" ]] && return 0
local message="${1:-Proceed?}"
# Second argument flips the default. Most questions here are "do the thing you
# already asked for" and Enter should mean yes; a few are genuine extras, where
# defaulting to yes would have people agreeing to them by reflex.
local default="${2:-y}" answer prompt
[[ "${ASSUME_YES:-}" == "1" ]] && { [[ "$default" == "y" ]] && return 0 || return 1; }
if [[ "$default" == "y" ]]; then prompt="[Y/n]"; else prompt="[y/N]"; fi
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
if ! read -rp " ${message} ${prompt}: " answer; then
echo ""
fail "No answer. Set ASSUME_YES=1 to run without prompts."
fi
[[ -z "$answer" ]] && answer="$default"
case "$answer" in
"" | y | Y | yes | Yes) return 0 ;;
y | Y | yes | Yes) return 0 ;;
n | N | no | No) return 1 ;;
*) warn "Answer y or n." ;;
esac
+5 -5
View File
@@ -45,11 +45,11 @@ LAST_SKIPPED=()
# that make a machine worth sitting at.
#
# The first six are load-bearing and each is used by a later step — curl fetches
# in nine of them, jq parses the lazygit release API, unzip opens ssh-keys.zip,
# gnupg dearmors the Docker keyring, git clones the Neovim config, and
# ca-certificates is what makes any of the fetching work. The rest are the
# environment: nothing calls them, they are here because a box you use should
# have them.
# in nine of them, jq parses the lazygit release API, gnupg dearmors the Docker
# keyring, git clones the Neovim config, unzip opens anything that arrives as an
# archive, and ca-certificates is what makes any of the fetching work. The rest
# are the environment: nothing calls them, they are here because a box you use
# should have them.
#
# build-essential is the one entry with any reach beyond itself: it is a
# meta-package (gcc, g++, make, libc6-dev, dpkg-dev), so on a machine where a