Nimbin2git.christianimmanuel.de / Linux From Scratch / Packagemanager-LFS-PackageUser-System / last_build_step.sh

Packagemanager-LFS-PackageUser-System git · main

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
last_build_step.sh 11.5 KB · 251 lines raw
#!/bin/bash
# last_build_step.sh -- the final step of an LFS build.
#
# EDIT THIS FILE.  It is yours: whatever a freshly built system should have
# that the book does not provide goes here, and it runs as the last step of
# `lfs-helper build-all`.
#
#   * SOURCES below is read by `lfs build-system get-sources`, so anything you
#     add is downloaded along with the book's own packages.
#   * The rest runs as root inside the chroot, after every book package.
#   * It is copied into the chroot by `lfs build-system gen-chroot-scripts`,
#     so edit it HERE (on the host) and regenerate -- not in the chroot.
#
# It ships with wget because LFS has no download tool at all: without one a
# freshly booted system cannot fetch even the sources for its next package.

# --- extra sources -------------------------------------------------------- #
# Downloaded into $LFS/sources by `lfs build-system get-sources`.
SOURCES=(
    # wget, so the new system can fetch anything at all
    "https://ftpmirror.gnu.org/wget/wget-1.25.0.tar.gz"

    # What `lfs` and `blfs` need to parse the books.  WHEELS, not source
    # tarballs: a modern sdist needs its build backend (beautifulsoup4 wants
    # hatchling, requests wants setuptools) and with no package index reachable
    # pip cannot fetch one --
    #     BackendUnavailable: Cannot import 'hatchling.build'
    # A wheel is already built, so pip just unpacks it.  Both are pure Python,
    # so the "any" wheel works on every architecture.
    "https://files.pythonhosted.org/packages/py3/r/requests/requests-2.32.3-py3-none-any.whl"
    "https://files.pythonhosted.org/packages/py3/b/beautifulsoup4/beautifulsoup4-4.12.3-py3-none-any.whl"
    # urllib3, charset-normalizer, idna and certifi are what requests imports
    "https://files.pythonhosted.org/packages/py3/u/urllib3/urllib3-2.2.3-py3-none-any.whl"
    "https://files.pythonhosted.org/packages/py3/c/charset_normalizer/charset_normalizer-3.4.0-py3-none-any.whl"
    "https://files.pythonhosted.org/packages/py3/i/idna/idna-3.10-py3-none-any.whl"
    "https://files.pythonhosted.org/packages/py3/c/certifi/certifi-2024.8.30-py3-none-any.whl"
    "https://files.pythonhosted.org/packages/py3/s/soupsieve/soupsieve-2.6-py3-none-any.whl"
)

# --- the work ------------------------------------------------------------- #
set -e

# Build one of the SOURCES tarballs as its own package user.
#   build_as_package_user <user> <tarball-glob> <configure options...>
build_as_package_user() {
    local user="$1" glob="$2"; shift 2
    local src="/sources"

    local tarball=""
    local c
    for c in $src/$glob; do [ -e "$c" ] && { tarball="$c"; break; }; done
    if [ -z "$tarball" ]; then
        echo "!! no $glob in $src -- add it to SOURCES and run:" >&2
        echo "     lfs build-system get-sources --run" >&2
        return 1
    fi

    lfs-helper add-user "$user" >/dev/null 2>&1 || true

    local dir
    dir="$(tar tf "$tarball" | head -n1 | cut -d/ -f1)"
    rm -rf "${src:?}/${dir:?}"
    tar --no-same-owner -xf "$tarball" -C "$src"
    chown -R "$user:$user" "$src/$dir"

    echo "# building $user from $(basename "$tarball") as package user '$user'"
    su - "$user" -c "cd '$src/$dir' && ./configure $* && make && make install"

    # record what was installed, the same way lfs-helper does
    printf '%s\n# installed %s by last_build_step.sh\n' \
           "$dir" "$(date '+%Y-%m-%d %H:%M:%S')" > "/usr/src/$user/VERSION" \
        2>/dev/null || true
    chown "$user:$user" "/usr/src/$user/VERSION" 2>/dev/null || true
}

# Install a Python module as ITS OWN package user, the same as any other
# package.  requests and beautifulsoup4 are what `lfs` and `blfs` need to parse
# the books, so without them the tools cannot run on the new system.
# Let a package user install into site-packages.
#
# site-packages belongs to the python package and is shared through a collector
# group.  Ask lfs-helper for access, exactly as a failed build would: it joins
# the collector group the directory already carries, or asks which group should
# own it.  It must NOT be handed to the install group -- that means "anyone may
# install here", and site-packages is python's tree, not shared infrastructure.
_grant_site_packages() {
    local user="$1" sp
    sp="$(python3 -c 'import sysconfig; print(sysconfig.get_paths()["purelib"])' \
          2>/dev/null)"
    if [ -z "$sp" ] || [ ! -d "$sp" ]; then
        echo "note: cannot find site-packages -- skipping $user" >&2
        return 1
    fi
    lfs-helper grant-dir "$sp" "$user" --run
}

install_python_module() {
    local user="$1" module="$2" glob="$3"
    local src="/sources" wheel="" c
    for c in $src/$glob; do [ -e "$c" ] && { wheel="$c"; break; }; done
    if [ -z "$wheel" ]; then
        echo "note: no $glob in $src -- skipping $module" >&2
        echo "      add it to SOURCES and run: lfs build-system get-sources --run" >&2
        return 0
    fi
    command -v python3 >/dev/null 2>&1 || {
        echo "note: no python3 yet -- skipping $module" >&2; return 0; }

    lfs-helper add-user "$user" >/dev/null 2>&1 || true
    chown "$user:$user" "$wheel" 2>/dev/null || true
    _grant_site_packages "$user" || return 0

    echo "# installing $module from $(basename "$wheel") as package user '$user'"
    # A wheel needs no build backend and no network: pip unpacks it.
    # PIP_USER=0 / PYTHONNOUSERSITE keep it out of the package user's ~/.local,
    # where nothing else would ever find it.
    su - "$user" -c "PIP_USER=0 PYTHONNOUSERSITE=1 \
        python3 -m pip install --no-index --no-deps '$wheel'" \
        || echo "!! $module did not install -- do it later with:
     packagemanager pip install $module" >&2
}

# wget: the one thing a new system cannot bootstrap without.
# Deliberately minimal -- reinstall it properly once the system boots:
#     packagemanager install wget --recursive --run
build_as_package_user wget "wget-*.tar.*" \
    --prefix=/usr --sysconfdir=/etc --with-ssl=openssl

# The two Python modules the tools themselves need.  Each gets its own package
# user, exactly like every other package -- there is no "last-step" package and
# nothing here is owned by one.
# Dependencies first -- --no-deps means pip will not pull them in itself.
install_python_module urllib3            urllib3            "urllib3-*.whl"
install_python_module charset-normalizer charset-normalizer "charset_normalizer-*.whl"
install_python_module idna               idna               "idna-*.whl"
install_python_module certifi            certifi            "certifi-*.whl"
install_python_module soupsieve          soupsieve          "soupsieve-*.whl"
install_python_module requests           requests           "requests-*.whl"
install_python_module beautifulsoup4     beautifulsoup4     "beautifulsoup4-*.whl"

# --- wget without certificates -------------------------------------------- #
# A freshly built LFS has no CA certificates -- they come from BLFS' make-ca --
# so every HTTPS fetch fails with "cannot verify ... certificate".  That is a
# chicken-and-egg problem: you need wget to fetch make-ca.
#
# Turn verification off in /etc/wgetrc rather than with a wrapper script in
# $PATH.  wget reads /etc/wgetrc no matter how it is invoked or what PATH the
# caller has, so this works for builds run through `su -` with the package
# user's own environment -- where a $PATH wrapper is simply never seen.
#
# This is a deliberate, TEMPORARY weakening: downloads are unverified until
# make-ca is installed.  The marker below is how the tools find and remove it.
disable_wget_verification() {
    local rc=/etc/wgetrc
    grep -q 'lfs-temporary-no-verify' "$rc" 2>/dev/null && return 0
    cat >> "$rc" <<'WRC'

# --- lfs-temporary-no-verify ---------------------------------------------
# This system has no CA certificates yet, so wget cannot verify anything.
# Downloads are UNVERIFIED until you install make-ca:
#     packagemanager install make-ca --recursive --run
# The tools remove these two lines automatically once certificates exist.
check_certificate = off
# --- end lfs-temporary-no-verify -----------------------------------------
WRC
    echo "# certificate checking disabled in $rc until make-ca is installed"
    echo "#   (downloads are UNVERIFIED until then -- the tools re-enable it"
    echo "#    automatically as soon as certificates exist)"
}

disable_wget_verification

# --- add your own steps below --------------------------------------------- #
# e.g. a package the book does not carry, a config file, a user account.

# --- accounts you can actually log in with -------------------------------- #
# A freshly built system has root with NO password.  Depending on the login
# manager that is either "anyone can log in as root" or "nobody can log in at
# all" -- and it is discovered at the worst moment, after a reboot, with no
# way in.  Ask now, while there is still a working shell here.
set_up_login_accounts() {
    # No terminal (a scripted or resumed run): say what is missing rather than
    # blocking, and leave the system unbootable-but-known instead of hanging.
    if [ ! -t 0 ]; then
        echo
        echo "!! Not running interactively, so no passwords were set."
        echo "   Before rebooting, set at least the root password:"
        echo "       chroot /mnt/lfs /usr/bin/passwd root"
        return 0
    fi

    echo
    echo "--- accounts ------------------------------------------------"

    # root
    if grep -qE '^root:[^:]*:' /etc/shadow 2>/dev/null &&
       ! grep -qE '^root:(\*|!|)?:' /etc/shadow 2>/dev/null; then
        echo "root already has a password."
    else
        echo "root has NO password yet.  Without one you may not be able to"
        echo "log in after rebooting."
        passwd root || echo "!! setting the root password failed -- do it before rebooting" >&2
    fi

    # a login account for a person: the name is already configured, so this
    # only has to create it and set a password
    local main
    main="$(sed -n 's/^main_user=\(.*\)$/\1/p' /etc/pkgusr/packagemanager.conf \
            2>/dev/null | head -n1)"
    if [ -z "$main" ]; then
        echo
        echo "No login account is configured.  You can add one now, or later"
        echo "with:  useradd -m -G users <name> && passwd <name>"
        printf "Create a login account now?  [name, or blank to skip]: "
        read -e -r main
    fi
    [ -n "$main" ] || { echo; return 0; }

    if id "$main" >/dev/null 2>&1; then
        echo "Account '$main' already exists."
    else
        echo "Creating login account '$main' ..."
        useradd -m -k /etc/skel -G users -s /bin/bash "$main" \
            || { echo "!! could not create $main" >&2; return 0; }
    fi
    if grep -qE "^$main:(\*|!|)?:" /etc/shadow 2>/dev/null; then
        echo "Set a password for '$main':"
        passwd "$main" || echo "!! setting the password for $main failed" >&2
    fi

    # so the account can reach the application users created later
    echo
    echo "  '$main' can be given access to application users with:"
    echo "      packagemanager user create <app> --shared --launcher"
}

set_up_login_accounts

echo
echo "=============================================================="
echo " The base system is built."
echo
echo " wget was installed with a minimal configure line so the system"
echo " can fetch things at all.  Reinstall it properly -- with its"
echo " dependencies, from the BLFS book -- once you have booted:"
echo
echo "     packagemanager install wget --recursive --run"
echo
echo " Anything else you want on a fresh system belongs in"
echo " last_build_step.sh on the host."
echo "=============================================================="