#!/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 } # ----------------------------------------------------------------------------- # Timezone # ----------------------------------------------------------------------------- # The shortlist offered at the prompt. Any zone name can be typed instead, so # this is a convenience rather than a limit. TZ_OPTIONS=(UTC Europe/Lisbon Europe/London Europe/Berlin Europe/Stockholm US/Eastern US/Pacific Asia/Tokyo) # What the machine is set to now. # # Three sources because they disagree about availability rather than about the # answer: timedatectl is absent without systemd (containers, WSL), /etc/timezone # is Debian-specific, and the /etc/localtime symlink is the one thing that is # always true when any of them are. timezone_current() { if command -v timedatectl &>/dev/null && timedatectl show -p Timezone --value 2>/dev/null | grep -q .; then timedatectl show -p Timezone --value 2>/dev/null elif [[ -r /etc/timezone ]]; then tr -d '[:space:]' /dev/null ;; *) # timedatectl where there is a systemd to talk to; the files directly # otherwise, which is the same thing it would have written. if command -v timedatectl &>/dev/null && [[ "$IS_WSL" != true ]]; then timedatectl set-timezone "$tz" else ln -sf "/usr/share/zoneinfo/${tz}" /etc/localtime echo "$tz" >/etc/timezone fi ;; esac }