port the locale section

Was three unconditional lines that ran on every pass and reported success either
way. Now it checks, says what it found, and asks.

The original tracked one fact where there are two:

  what a new login shell is told to use   LANG in /etc/default/locale
  whether that locale actually exists     whether it has been generated

Setting the first without the second is what produces "setlocale: LC_ALL: cannot
change locale" on every ssh login and every perl invocation. They fail
differently, so the step names whichever one is actually missing rather than
reporting a flat "locale not set".

Also fixes two things the original would have hit on a minimal image:

  locale-gen comes from the `locales` package, which cloud base images do not
  ship and which is not in core utils. It is installed on demand rather than
  assumed, instead of failing with "locale-gen: command not found".

  The locale is uncommented in /etc/locale.gen rather than only passed to
  locale-gen as an argument. A locale generated by argument alone disappears the
  next time anything regenerates from that file.

`locale -a` prints en_US.utf8 where the configuration spells it en_US.UTF-8, so
both sides are folded before comparing — a literal match reports a working locale
as missing.

LOCALE in the environment overrides the default. pacman, dnf and brew branches
are written but unreachable while the pre-flight gate is apt-only; macOS has no
system locale to set and says so.

Verified both paths on this host: en_US.UTF-8 reports already set and generated,
pt_PT.UTF-8 correctly reports both facts missing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-12 17:41:49 +00:00
co-authored by Claude Opus 5
parent 1beb357f2e
commit 163f8d5899
2 changed files with 131 additions and 1 deletions
+90
View File
@@ -0,0 +1,90 @@
#!/bin/bash
# =============================================================================
# machine-setup — system configuration
# =============================================================================
#
# Definitions only, like the other lib/ files. Locale, and the system-level
# settings that follow it.
[[ -n "${MACHINE_SETUP_SYSTEM_LOADED:-}" ]] && return 0
MACHINE_SETUP_SYSTEM_LOADED=1
# -----------------------------------------------------------------------------
# Locale
# -----------------------------------------------------------------------------
#
# Two separate facts, and the original only handled one of them:
#
# what a new login shell is told to use — LANG in /etc/default/locale
# whether that locale actually exists — whether it has been generated
#
# Setting LANG to a locale that was never generated is the state that produces
# "setlocale: LC_ALL: cannot change locale" on every ssh login and every perl
# invocation. Both are checked, so the step can say which one is missing.
# What a new login shell will be handed, or empty if nothing is configured.
locale_current() {
if [[ -r /etc/default/locale ]]; then
awk -F= '/^LANG=/ { gsub(/"/, "", $2); print $2 }' /etc/default/locale
elif [[ -r /etc/locale.conf ]]; then
awk -F= '/^LANG=/ { gsub(/"/, "", $2); print $2 }' /etc/locale.conf
fi
}
# Has this locale actually been built?
#
# `locale -a` prints en_US.utf8 where the configuration spells it en_US.UTF-8,
# so both sides are folded to lower case with the dashes removed before
# comparing. A literal match here would report a perfectly good locale missing.
locale_is_generated() {
local want="${1,,}"
want="${want//-/}"
locale -a 2>/dev/null | tr '[:upper:]' '[:lower:]' | tr -d '-' | grep -qx "$want"
}
locale_set() {
local want="$1"
local escaped="${want//./\\.}"
case "$PM" in
apt)
# locale-gen comes from the `locales` package, which minimal images and
# most cloud base images do not ship. Without this the step fails with
# "locale-gen: command not found" halfway through.
if ! pkg_is_installed locales; then
info " installing locales, which provides locale-gen"
pkg_install_now locales
fi
# Uncomment it if it is there commented out, add it if it is absent.
# Editing the file rather than passing the name to locale-gen is what makes
# it survive: a locale generated by argument alone is lost the next time
# anything regenerates from /etc/locale.gen.
if grep -qE "^#[[:space:]]*${escaped}[[:space:]]" /etc/locale.gen 2>/dev/null; then
sed -i "s/^#[[:space:]]*\(${escaped}[[:space:]]\)/\1/" /etc/locale.gen
elif ! grep -qE "^${escaped}[[:space:]]" /etc/locale.gen 2>/dev/null; then
# The charset is the part after the dot: en_US.UTF-8 -> UTF-8
echo "${want} ${want##*.}" >>/etc/locale.gen
fi
locale-gen
update-locale LANG="$want"
;;
pacman)
if grep -qE "^#[[:space:]]*${escaped}[[:space:]]" /etc/locale.gen 2>/dev/null; then
sed -i "s/^#[[:space:]]*\(${escaped}[[:space:]]\)/\1/" /etc/locale.gen
fi
locale-gen
echo "LANG=${want}" >/etc/locale.conf
;;
dnf)
# No locale.gen here — the locales come prebuilt in langpack packages.
pkg_install_now "glibc-langpack-${want%%_*}"
localectl set-locale "LANG=${want}"
;;
brew)
warn "macOS has no system locale to set — it is per-user, from the terminal's settings"
return 1
;;
esac
}