Nimbin[12]?Linux From Scratch / Packagemanager-LFS-PackageUser-System / stacks/shell-env.sh

Packagemanager-LFS-PackageUser-System git · main

Package-user based package manager for LFS/BLFS

lfs blfs package-manager bash · first commit 2026-08-18 · last commit 2026-10-26 (8 days ago) · synced 8 hours ago · upstream: github.com/nimbin2/Packagemanager-LFS-PackageUser-System

Shell 63.7% Markdown 35.8%
git clone https://git.christianimmanuel.de/linux-from-scratch/Packagemanager-LFS-PackageUser-System.gitwget https://git.christianimmanuel.de/linux-from-scratch/Packagemanager-LFS-PackageUser-System/archive/Packagemanager-LFS-PackageUser-System.tar.gz
stacks/shell-env.sh 10.3 KB · 239 lines raw
#!/bin/bash
# shell-env.sh -- interactive shell environment: the system locale, coloured
# ls, bash completion, and username completion for su (root step, idempotent).
#
# BLFS's "The Bash Shell Startup Files" page writes /etc/profile and
# /etc/bashrc, and /etc/bashrc is what makes ls colourful -- it evals
# dircolors and aliases ls --color=auto.  It is a config page, not a
# package, so nothing in a package stack ever ran it.
#
# bash-completion is not a BLFS package at all; it comes from upstream and
# is a `tar` entry in the stack beside this script.
set -u

# ROOT WROTE IT, SO ROOT OWNS IT.
#
# On a package-user system a file's owner records which package installed it,
# and anything written while a build window is open is attributed to that
# package user -- /etc/profile.d/pkgusr-system.sh owned by p_gstreamer10,
# 00-path-functions.sh by p_gettext, /etc/profile.d itself by p_libnice.
# For a file a PACKAGE installed that is correct and useful.  For a config
# file root writes it is wrong twice over: it says a package owns something
# no package installed, and every login shell sources it, so whoever can
# write it can run code as everyone who logs in.  Re-asserted on every run.
own_root() { [ -e "$1" ] && chown root:root "$1" 2>/dev/null; return 0; }

# 1. /etc/dircolors -- the database ls colours come from
if [ ! -f /etc/dircolors ]; then
    if command -v dircolors >/dev/null 2>&1; then
        dircolors -p > /etc/dircolors
        own_root /etc/dircolors
        echo "# wrote /etc/dircolors (from dircolors -p)"
    fi
else
    echo "# /etc/dircolors exists -- left alone"
fi

# 2. /etc/bashrc -- colours, prompt, and the completion loader.
#    Written only if absent: this is a file people edit.
if [ -f /etc/bashrc ]; then
    echo "# /etc/bashrc exists -- left alone (see /etc/bashrc.new)"
    _dest=/etc/bashrc.new
else
    _dest=/etc/bashrc
fi
cat > "$_dest" <<'BASHRC'
# Begin /etc/bashrc
# Based on the BLFS "Bash Shell Startup Files" page.

# Coloured ls and grep.
if [ -f /etc/dircolors ] ; then
    eval "$(dircolors -b /etc/dircolors)"
fi
if [ -f "$HOME/.dircolors" ] ; then
    eval "$(dircolors -b "$HOME/.dircolors")"
fi
alias ls='ls --color=auto'
alias grep='grep --color=auto'

# Prompt: red for root, green for everyone else.
NORMAL="\[\e[0m\]"
RED="\[\e[1;31m\]"
GREEN="\[\e[1;32m\]"
if [[ $EUID == 0 ]] ; then
    PS1="$RED\u [ $NORMAL\w$RED ]# $NORMAL"
else
    PS1="$GREEN\u [ $NORMAL\w$GREEN ]\$ $NORMAL"
fi
unset RED GREEN NORMAL

# bash-completion, if it is installed.  Loads every completion in
# /usr/share/bash-completion/completions, including the package-user
# accounts for `su`.
if [ -r /usr/share/bash-completion/bash_completion ] ; then
    . /usr/share/bash-completion/bash_completion
fi

# Complete USER NAMES after su and friends.  bash-completion supplies this
# too, but these three cost nothing and work without it -- which matters on
# a package-user system, where `su - p_<tab>` is a daily action.
complete -A user su sudo passwd chage groups id

# End /etc/bashrc
BASHRC
chmod 644 "$_dest"
own_root "$_dest"
echo "# wrote $_dest"

# 3. make sure /etc/profile actually sources it, and profile.d
if [ -f /etc/profile ] && ! grep -q '/etc/bashrc' /etc/profile 2>/dev/null; then
    echo "# NOTE: /etc/profile does not source /etc/bashrc."
    echo "#   Interactive non-login shells read ~/.bashrc, which on this"
    echo "#   system should contain:  . /etc/bashrc"
fi

# 4. the package users' own bashrc: they share /etc/pkgusr/bashrc
if [ -f /etc/pkgusr/bashrc ] && ! grep -q '/etc/bashrc' /etc/pkgusr/bashrc; then
    printf '\n# colours, completion and the shared aliases\n[ -r /etc/bashrc ] && . /etc/bashrc\n' \
        >> /etc/pkgusr/bashrc
    echo "# package users now source /etc/bashrc too"
fi


# 5. THE SYSTEM LOCALE -- $LANG, and why a terminal says the locale is "C"
#
#     warning: foot: 'C' is not a UTF-8 locale, falling back to 'C.UTF-8'
#
# Nothing is compiled wrong when that appears: LANG is simply not in the
# environment.  LFS's own /etc/profile reads /etc/locale.conf, but BLFS's
# "The Bash Shell Startup Files" page REPLACES /etc/profile with one that
# reads /etc/profile.d/*.sh instead -- and its i18n.sh, the piece that
# exports LANG, is a separate block on that page that is easy to walk past.
# Take either route: this writes both files, and neither profile minds the
# other's.
#
# Without it every program falls back to the C locale: sorting, dates,
# translated messages, and anything that has to decide whether a byte is a
# character.
LOCALE_CONF="${SHELL_ENV_LOCALE_CONF:-/etc/locale.conf}"   # overridable, for tests
I18N_SH="${PROFILE_D:-/etc/profile.d}/i18n.sh"
LFS_CONFIG_JSON="${LFS_STORE:-/usr/share/lfs}/config.json"
_norm() { printf '%s' "$1" | tr '[:upper:]' '[:lower:]' | sed 's/utf-8/utf8/; s/-//g'; }
_have_locale() {
    [ -n "$1" ] || return 1
    locale -a 2>/dev/null | while read -r l; do
        [ "$(_norm "$l")" = "$(_norm "$1")" ] && echo yes
    done | grep -q yes
}

# WHAT THIS MACHINE ALREADY SAYS IT WANTS, in order.  Nothing is guessed
# from the keyboard or the timezone: a wrong locale is worse than none.
conf_lang="$(sed -n 's/^[[:space:]]*LANG=//p' "$LOCALE_CONF" 2>/dev/null \
             | tr -d '"'"'"' ' | head -n1)"
asked="${LFS_LOCALE:-$conf_lang}"
[ -n "$asked" ] || asked="$(sed -n 's/.*"locale"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' \
                            "$LFS_CONFIG_JSON" 2>/dev/null | head -n1)"
# A NAME IS NOT A LOCALE UNTIL glibc HAS BUILT IT.  locale.conf may well name
# one nobody ever generated, and then LANG is set to something that does not
# exist -- which looks exactly like not setting it at all.
want=""
for _c in "$asked" en_US.UTF-8 C.UTF-8; do
    if _have_locale "$_c"; then want="$_c"; break; fi
done
if [ -n "$asked" ] && [ "$(_norm "$asked")" != "$(_norm "${want:-}")" ]; then
    echo "!! $asked is asked for but not installed (see: locale -a)"
    echo "#   make it as glibc's package user:  localedef -i ${asked%%.*} -f UTF-8 $asked"
    [ -n "$want" ] && echo "#   using $want until then"
fi

if [ -z "$want" ]; then
    echo "!! no UTF-8 locale is installed -- \$LANG cannot be set to one"
    echo "#   glibc builds them; the archive is glibc's, so make one as its"
    echo "#   package user (p_glibc here) rather than as root:"
    echo "#     localedef -i en_US -f UTF-8 en_US.UTF-8"
    echo "#   then run this step again."
else
    case "$(_norm "$want")" in
        *utf8) ;;
        *) echo "!! $want is not a UTF-8 locale -- a terminal will still complain" ;;
    esac
    if [ -f "$LOCALE_CONF" ] && [ "$(_norm "$conf_lang")" = "$(_norm "$want")" ]; then
        echo "# $LOCALE_CONF already says LANG=$conf_lang"
    elif [ -f "$LOCALE_CONF" ]; then
        echo "# $LOCALE_CONF says LANG=${conf_lang:-nothing} -- left alone (it is yours)"
        echo "#   $want is what works here; change it there if you want it"
    else
        printf 'LANG=%s\n' "$want" > "$LOCALE_CONF"
        chmod 644 "$LOCALE_CONF"; own_root "$LOCALE_CONF"
        echo "# wrote $LOCALE_CONF (LANG=$want)"
    fi
    # i18n.sh: JUDGED BY WHAT IT PRODUCES, NOT BY WHO WROTE IT.
    #
    # BLFS's page ships an i18n.sh of its own, and it is usually already
    # there -- `. /etc/locale.conf` and export what came out.  "Exists and is
    # not ours, left alone" would have been the end of it, and $LANG would
    # still be empty: the file was never the problem, the missing
    # /etc/locale.conf above was.  So source it in an empty environment and
    # look at the LANG that comes out.  TERM is set to something that is not
    # `linux`, because both the LFS and BLFS versions special-case the
    # console with a hard-coded C.UTF-8.
    _i18n_gives() {
        env -i HOME=/root TERM=xterm bash --noprofile --norc -c \
            ". \"\$1\" >/dev/null 2>&1; printf '%s' \"\${LANG:-}\"" _ "$1" 2>/dev/null
    }
    # WHOSE FILE IS IT.  Three writers put an i18n.sh here and all three can
    # leave LANG unset, so all three are ours to replace:
    #   this script          -- its own marker
    #   the book             -- `Begin /etc/profile.d/i18n.sh`, placeholder
    #                           LANG that was never filled in
    #   lfs-helper           -- its marker, and before 1.14.298 an i18n.sh
    #                           with the export COMMENTED OUT:
    #                               : # export LANG=en_US.UTF-8
    #                           which is what is on a machine built with it
    _ours_i18n() {
        grep -q '^# written by stacks/shell-env.sh' "$1" \
        || grep -q '^# written by lfs-helper' "$1" \
        || grep -q 'Begin /etc/profile.d/i18n.sh' "$1" \
        || grep -q '^[[:space:]]*:[[:space:]]*#[[:space:]]*export LANG=' "$1"
    }
    got=""
    [ -f "$I18N_SH" ] && got="$(_i18n_gives "$I18N_SH")"
    if [ -f "$I18N_SH" ] && [ "$(_norm "$got")" = "$(_norm "$want")" ]; then
        echo "# $I18N_SH already gives LANG=$got -- left alone"
    elif [ -f "$I18N_SH" ] && ! _ours_i18n "$I18N_SH"; then
        # not ours, and not the book's either: somebody wrote this on purpose
        echo "# $I18N_SH is hand-written and gives LANG=${got:-nothing} -- left alone"
        echo "#   $want is what this machine has; set it there, or delete the file"
    else
        if [ -f "$I18N_SH" ]; then
            bak="$I18N_SH.bak-$(date +%Y%m%d-%H%M%S)"
            cp -p "$I18N_SH" "$bak" \
                && echo "# $I18N_SH gave LANG=${got:-nothing} -- replaced (backup: $(basename "$bak"))"
        fi
        cat > "$I18N_SH" <<'I18N'
# written by stacks/shell-env.sh -- safe to delete, regenerated
# Begin /etc/profile.d/i18n.sh
#
# $LANG for every login shell, from /etc/locale.conf.  BLFS's /etc/profile
# reads this directory; LFS's reads /etc/locale.conf itself.  Either way the
# locale comes from one file.
if [ -r /etc/locale.conf ]; then
    . /etc/locale.conf
    export LANG
    if [ -n "${LC_ALL:-}" ]; then export LC_ALL; fi
fi
# less(1) needs telling separately -- it reads this, not the locale.
export LESSCHARSET="${LESSCHARSET:-utf-8}"
# End /etc/profile.d/i18n.sh
:
I18N
        chmod 644 "$I18N_SH"; own_root "$I18N_SH"
        echo "# wrote $I18N_SH (exports LANG from $LOCALE_CONF)"
    fi
    if [ "$(_norm "${LANG:-C}")" != "$(_norm "$want")" ]; then
        echo "#   this shell still has LANG=${LANG:-unset}: /etc/profile.d is read at"
        echo "#   LOGIN, so log in again -- or now:  . $I18N_SH"
    fi
fi

echo "# done -- open a new shell, or:  . /etc/bashrc"