diff --git a/scripts/setup/machine-setup/lib/system.sh b/scripts/setup/machine-setup/lib/system.sh new file mode 100644 index 00000000..0bd40550 --- /dev/null +++ b/scripts/setup/machine-setup/lib/system.sh @@ -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 +} diff --git a/scripts/setup/machine-setup/machine-setup.sh b/scripts/setup/machine-setup/machine-setup.sh index 8335a1f7..1aa8b3c9 100755 --- a/scripts/setup/machine-setup/machine-setup.sh +++ b/scripts/setup/machine-setup/machine-setup.sh @@ -22,6 +22,8 @@ source "$SCRIPT_DIR/lib/base.sh" source "$SCRIPT_DIR/lib/packages.sh" # shellcheck source=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 # that file is definitions only and a trap is a side effect on whoever sources it. @@ -147,13 +149,51 @@ if ! skip; then 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 # ============================================================================= # # 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 · # git config · docker · zsh + prompt · tailscale · neovim · js runtimes · # dev tools · ufw · zshrc · disk ballast