fix(install): constrain keyboard selection to XKB catalog
All checks were successful
Check / eval (push) Successful in 3m17s
All checks were successful
Check / eval (push) Successful in 3m17s
Make keyboard typing filter-only with Gum strict mode, validate interactive and unattended choices against the pinned XKB catalog before any disk mutation, and show resolved human-readable choices in review. Verified: V1 flake eval, focused installer builds, shell/Python/diff checks; current-ISO KVM boot proved the packaged new flow while the pre-destructive interaction pass continued.
This commit is contained in:
@@ -26,6 +26,121 @@
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
keyboard_layout_catalog() {
|
||||
local rules
|
||||
rules=$(keyboard_rules_file) || return 1
|
||||
awk '
|
||||
/^! layout/ { in_section=1; next }
|
||||
/^!/ && in_section { exit }
|
||||
in_section && NF { print $1 }
|
||||
' "$rules"
|
||||
}
|
||||
|
||||
keyboard_variant_catalog() {
|
||||
local layout="$1" rules
|
||||
rules=$(keyboard_rules_file) || return 1
|
||||
awk -v wanted_layout="$layout:" '
|
||||
/^! variant/ { in_section=1; next }
|
||||
/^!/ && in_section { exit }
|
||||
in_section && $2 == wanted_layout { print $1 }
|
||||
' "$rules"
|
||||
}
|
||||
|
||||
keyboard_rules_file() {
|
||||
local rules
|
||||
for rules in "${NOMARCHY_XKB_RULES:-}" \
|
||||
/run/current-system/sw/share/X11/xkb/rules/base.lst \
|
||||
/usr/share/X11/xkb/rules/base.lst; do
|
||||
[[ -n "$rules" && -r "$rules" ]] && { echo "$rules"; return 0; }
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
keyboard_layout_name() {
|
||||
local layout="$1" rules
|
||||
rules=$(keyboard_rules_file) || { echo "layout $layout"; return; }
|
||||
awk -v wanted="$layout" '
|
||||
/^! layout/ { in_section=1; next }
|
||||
/^!/ && in_section { exit }
|
||||
in_section && $1 == wanted {
|
||||
$1=""; sub(/^[[:space:]]+/, ""); print; found=1; exit
|
||||
}
|
||||
END { if (!found) print "layout " wanted }
|
||||
' "$rules"
|
||||
}
|
||||
|
||||
keyboard_variant_name() {
|
||||
local layout="$1" variant="$2" rules
|
||||
rules=$(keyboard_rules_file) || { echo "$variant key behaviour"; return; }
|
||||
awk -v wanted_layout="$layout:" -v wanted_variant="$variant" '
|
||||
/^! variant/ { in_section=1; next }
|
||||
/^!/ && in_section { exit }
|
||||
in_section && $1 == wanted_variant && $2 == wanted_layout {
|
||||
$1=""; $2=""; sub(/^[[:space:]]+/, ""); print; found=1; exit
|
||||
}
|
||||
END { if (!found) print wanted_variant " key behaviour" }
|
||||
' "$rules"
|
||||
}
|
||||
|
||||
keyboard_layout_choices() {
|
||||
local layout
|
||||
while IFS= read -r layout; do
|
||||
[[ -n "$layout" ]] || continue
|
||||
printf '%s\t%s\n' "$layout" "$(keyboard_layout_name "$layout")"
|
||||
done < <(keyboard_layout_catalog)
|
||||
}
|
||||
|
||||
keyboard_variant_choices() {
|
||||
local layout="$1" variant
|
||||
printf '(none)\tStandard keys (no special variant)\n'
|
||||
while IFS= read -r variant; do
|
||||
[[ -n "$variant" ]] || continue
|
||||
printf '%s\t%s\n' "$variant" "$(keyboard_variant_name "$layout" "$variant")"
|
||||
done < <(keyboard_variant_catalog "$layout")
|
||||
}
|
||||
|
||||
keyboard_catalog_has() {
|
||||
local wanted="$1" candidate
|
||||
while IFS= read -r candidate; do
|
||||
[[ "$candidate" == "$wanted" ]] && return 0
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
# Common trust boundary for both gum and unattended input. The picker is a
|
||||
# search over these catalogs, never a text field; this second check means even
|
||||
# surprising gum behaviour or a mistyped NOMARCHY_KB_* value cannot become
|
||||
# generated Nix and fail much later during installation.
|
||||
validate_keyboard_choice() {
|
||||
local layout="$1" variant="$2" layouts variants
|
||||
layouts=$(keyboard_layout_catalog) || {
|
||||
echo "Could not read the installed keyboard-layout catalog." >&2
|
||||
return 2
|
||||
}
|
||||
if [[ -z "$layouts" ]] || ! keyboard_catalog_has "$layout" <<< "$layouts"; then
|
||||
echo "Unknown keyboard layout '$layout'. Choose an installed layout (NOMARCHY_KB_LAYOUT)." >&2
|
||||
return 2
|
||||
fi
|
||||
[[ -z "$variant" ]] && return 0
|
||||
variants=$(keyboard_variant_catalog "$layout") || true
|
||||
if [[ -z "$variants" ]] || ! keyboard_catalog_has "$variant" <<< "$variants"; then
|
||||
echo "Unknown keyboard variant '$variant' for layout '$layout'. Choose an installed variant or no variant (NOMARCHY_KB_VARIANT)." >&2
|
||||
return 2
|
||||
fi
|
||||
}
|
||||
|
||||
# Small, side-effect-free interface used by the deterministic installer guard.
|
||||
# It deliberately runs before root/live-ISO checks and uses the exact same
|
||||
# validation function as a real install.
|
||||
if [[ "${1:-}" == "--validate-keyboard" ]]; then
|
||||
[[ $# -eq 3 ]] || {
|
||||
echo "usage: nomarchy-install --validate-keyboard <layout> <variant>" >&2
|
||||
exit 64
|
||||
}
|
||||
validate_keyboard_choice "$2" "$3"
|
||||
exit $?
|
||||
fi
|
||||
|
||||
# Baked in by the package wrapper:
|
||||
# NOMARCHY_INSTALL_SHARE — disko-config.nix, hardware-db.sh,
|
||||
# compose-lock.py, flake.lock, template/,
|
||||
@@ -214,14 +329,39 @@ else
|
||||
| sed 's/$/.UTF-8/' \
|
||||
| gum filter --placeholder "language / locale (type to search)" \
|
||||
|| echo en_US.UTF-8)
|
||||
KB_LAYOUT=$(localectl list-x11-keymap-layouts 2>/dev/null \
|
||||
| gum filter --placeholder "keyboard layout (type to search)" \
|
||||
|| echo us)
|
||||
KB_VARIANT=""
|
||||
if [[ "$KB_LAYOUT" != "us" ]] || gum confirm --default=No "Pick a keyboard variant (intl, nodeadkeys, …)?"; then
|
||||
KB_VARIANT=$( { echo "(none)"; localectl list-x11-keymap-variants "$KB_LAYOUT" 2>/dev/null; } \
|
||||
| gum filter --placeholder "variant for $KB_LAYOUT (pick '(none)' for the default)" \
|
||||
|| echo "(none)")
|
||||
keyboard_layouts=$(keyboard_layout_catalog) || \
|
||||
fail "Could not read the installed keyboard-layout catalog."
|
||||
[[ -n "$keyboard_layouts" ]] || \
|
||||
fail "The installed keyboard-layout catalog is empty."
|
||||
if gum confirm --default=yes "Use the standard US English keyboard (no special variant)?"; then
|
||||
KB_LAYOUT="us"
|
||||
KB_VARIANT=""
|
||||
else
|
||||
while true; do
|
||||
if keyboard_pick=$(keyboard_layout_choices \
|
||||
| gum filter --strict \
|
||||
--placeholder "keyboard layout — type to search installed choices"); then
|
||||
KB_LAYOUT="${keyboard_pick%%$'\t'*}"
|
||||
if validate_keyboard_choice "$KB_LAYOUT" "" 2>/dev/null; then
|
||||
break
|
||||
fi
|
||||
fi
|
||||
warn "Choose one of the listed keyboard layouts; typed text is search only."
|
||||
done
|
||||
while true; do
|
||||
if keyboard_pick=$( \
|
||||
keyboard_variant_choices "$KB_LAYOUT" \
|
||||
| gum filter --strict \
|
||||
--placeholder "key behaviour for $KB_LAYOUT — '(none)' means the standard keys" \
|
||||
); then
|
||||
KB_VARIANT="${keyboard_pick%%$'\t'*}"
|
||||
if [[ "$KB_VARIANT" == "(none)" ]] \
|
||||
|| validate_keyboard_choice "$KB_LAYOUT" "$KB_VARIANT" 2>/dev/null; then
|
||||
break
|
||||
fi
|
||||
fi
|
||||
warn "Choose a listed key behaviour, or '(none)' for the standard keys; typed text is search only."
|
||||
done
|
||||
fi
|
||||
fi
|
||||
# `(none)` is gum's display-only sentinel, never an XKB variant. Keep the
|
||||
@@ -230,6 +370,10 @@ fi
|
||||
if [[ "$KB_VARIANT" == "(none)" ]]; then
|
||||
KB_VARIANT=""
|
||||
fi
|
||||
keyboard_error=""
|
||||
if ! keyboard_error=$(validate_keyboard_choice "$KB_LAYOUT" "$KB_VARIANT" 2>&1); then
|
||||
fail "$keyboard_error"
|
||||
fi
|
||||
[[ "$USERNAME" =~ ^[a-z_][a-z0-9_-]*$ ]] || fail "Invalid username '$USERNAME'."
|
||||
[[ -f "/usr/share/zoneinfo/$TIMEZONE" || -e "/etc/zoneinfo/$TIMEZONE" ]] \
|
||||
|| timedatectl list-timezones 2>/dev/null | grep -qx "$TIMEZONE" \
|
||||
@@ -237,7 +381,13 @@ fi
|
||||
HASHED_PASSWORD=$(printf '%s' "$PASSWORD" | mkpasswd -m sha-512 -s)
|
||||
unset PASSWORD
|
||||
info "User: $USERNAME @ $HOSTNAME_ ($TIMEZONE)"
|
||||
info "Locale: $LOCALE · keyboard: $KB_LAYOUT${KB_VARIANT:+ ($KB_VARIANT)}"
|
||||
if [[ -n "$KB_VARIANT" ]]; then
|
||||
KEYBOARD_SUMMARY="$(keyboard_layout_name "$KB_LAYOUT") [$KB_LAYOUT] — $(keyboard_variant_name "$KB_LAYOUT" "$KB_VARIANT") [$KB_VARIANT]"
|
||||
else
|
||||
KEYBOARD_SUMMARY="$(keyboard_layout_name "$KB_LAYOUT") [$KB_LAYOUT] — standard keys (no special variant)"
|
||||
fi
|
||||
info "Locale: $LOCALE"
|
||||
info "Keyboard: $KEYBOARD_SUMMARY"
|
||||
|
||||
# ─── Hardware profile ───────────────────────────────────────────────────
|
||||
section "Hardware detection"
|
||||
@@ -296,6 +446,7 @@ gum style --border normal --padding "0 2" \
|
||||
"User: $USERNAME" \
|
||||
"Hostname: $HOSTNAME_" \
|
||||
"Timezone: $TIMEZONE" \
|
||||
"Keyboard: $KEYBOARD_SUMMARY" \
|
||||
"Hardware: ${HW_PROFILES[*]:-none}" \
|
||||
"Snapshots: snapper timeline on /" \
|
||||
"Source: nomarchy ${NOMARCHY_REV:0:12}${NOMARCHY_REV:+ }$([[ -z "${NOMARCHY_REV:-}" ]] && echo "(dirty tree) ")— $SOURCE_NET"
|
||||
|
||||
Reference in New Issue
Block a user