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:
@@ -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
|
||||||
|
}
|
||||||
@@ -22,6 +22,8 @@ source "$SCRIPT_DIR/lib/base.sh"
|
|||||||
source "$SCRIPT_DIR/lib/packages.sh"
|
source "$SCRIPT_DIR/lib/packages.sh"
|
||||||
# shellcheck source=lib/tools.sh
|
# shellcheck source=lib/tools.sh
|
||||||
source "$SCRIPT_DIR/lib/tools.sh"
|
source "$SCRIPT_DIR/lib/tools.sh"
|
||||||
|
# shellcheck source=lib/system.sh
|
||||||
|
source "$SCRIPT_DIR/lib/system.sh"
|
||||||
|
|
||||||
# Trap errors with context. Installed here rather than in lib/base.sh, because
|
# Trap errors with context. Installed here rather than in lib/base.sh, because
|
||||||
# that file is definitions only and a trap is a side effect on whoever sources it.
|
# that file is definitions only and a trap is a side effect on whoever sources it.
|
||||||
@@ -147,13 +149,51 @@ if ! skip; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# 5. Locale
|
||||||
|
# =============================================================================
|
||||||
|
#
|
||||||
|
# LOCALE in the environment overrides the default.
|
||||||
|
|
||||||
|
step "Locale"
|
||||||
|
if ! skip; then
|
||||||
|
LOCALE="${LOCALE:-en_US.UTF-8}"
|
||||||
|
CURRENT_LOCALE="$(locale_current)"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
info "Locale — the system language and character encoding"
|
||||||
|
echo " current: ${CURRENT_LOCALE:-none configured}"
|
||||||
|
echo " to set: ${LOCALE}"
|
||||||
|
|
||||||
|
if [[ "$CURRENT_LOCALE" == "$LOCALE" ]] && locale_is_generated "$LOCALE"; then
|
||||||
|
echo " already set and generated, nothing to do"
|
||||||
|
SUMMARY+=("Locale: already ${LOCALE}")
|
||||||
|
else
|
||||||
|
# Say which of the two is actually wrong, since they fail differently: a
|
||||||
|
# missing LANG means the C locale, a missing generation means every login
|
||||||
|
# prints a setlocale warning.
|
||||||
|
[[ "$CURRENT_LOCALE" != "$LOCALE" ]] && echo " LANG is not set to it"
|
||||||
|
locale_is_generated "$LOCALE" || echo " the locale has not been generated on this machine"
|
||||||
|
|
||||||
|
if confirm "Proceed?"; then
|
||||||
|
locale_set "$LOCALE"
|
||||||
|
ok "Locale set to ${LOCALE}"
|
||||||
|
SUMMARY+=("Locale: ${LOCALE}")
|
||||||
|
else
|
||||||
|
warn "skipped by request"
|
||||||
|
SUMMARY+=("Locale: SKIPPED by request — left at ${CURRENT_LOCALE:-unset}")
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
step_ok
|
||||||
|
fi
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# NOT PORTED YET
|
# NOT PORTED YET
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
#
|
#
|
||||||
# Sections still to move across from scripts/setup-old/setup-ubuntu.sh, in order:
|
# Sections still to move across from scripts/setup-old/setup-ubuntu.sh, in order:
|
||||||
#
|
#
|
||||||
# locale · timezone · swap · auto-suspend · boot-hang fix · user creation ·
|
# timezone · swap · auto-suspend · boot-hang fix · user creation ·
|
||||||
# ssh keys · ssh hardening · dns · static ip · fail2ban · unattended-upgrades ·
|
# ssh keys · ssh hardening · dns · static ip · fail2ban · unattended-upgrades ·
|
||||||
# git config · docker · zsh + prompt · tailscale · neovim · js runtimes ·
|
# git config · docker · zsh + prompt · tailscale · neovim · js runtimes ·
|
||||||
# dev tools · ufw · zshrc · disk ballast
|
# dev tools · ufw · zshrc · disk ballast
|
||||||
|
|||||||
Reference in New Issue
Block a user