#!/bin/bash set -eu shopt -s globstar nullglob # A simple tool to grab and extract debian-installer netboot images. # # Copyright (C) 2008 Frank Lin PIAT # latest version is available from: # http://wiki.debian.org/DebianInstaller/NetbootAssistant # # This file is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St - Suite 330, Boston, MA 02110, USA. # ------------------ Declare the constants ------------------- # PACKAGE_NAME=di-netboot-assistant PACKAGE_VERSION=0.79 # -------------- Initialize the global variables ------------- # OFFLINE=false VERBOSE=false DEBUG=false IGNOR_SIG=false DISOURCELIST=/etc/di-netboot-assistant/di-sources.list SYSLINUX=/usr/lib/syslinux/ DL_CACHE=/var/cache/di-netboot-assistant STATUS_LIB=/var/lib/di-netboot-assistant TEMPLATES=/etc/di-netboot-assistant TFTP_ROOT=/var/lib/tftpboot N_A_DIR=d-i/n-a # where di-netboot-assistant images are set up DI_PKG_DIR=d-i/n-pkg # where the debian-installer-*-netboot-* image is copied or bind mounted LIVE_DIR=d-i/n-live # where the Debian Live ISO images are mounted (sub-dirs) CLI_ALIAS="" REWRITEPKGPATH='\(debian\|ubuntu\)-installer' SQUASHIMG="tftp://\\\${pxe_default_server}/\$LIVE_DIR/\${ISO_NAME}/live/filesystem.squashfs" CHECKSUM_FILE="SHA256SUMS" CHECKSUM_BIN="sha256sum" DEBIAN_KEYRING="/usr/share/keyrings/debian-archive-keyring.gpg" UBUNTU_KEYRING="/usr/share/keyrings/ubuntu-archive-keyring.gpg" DI_ARGS= TARGET_ARGS= ARCH= DEFAULT_ARCH="" HOME="${HOME:-}" #MIRROR_REGEXPS=# Not defined on purpose, so user can pass the variable umask "$(umask | sed -e 's/.$/2/')" # files must be public. if [ -n "$HOME" ] && [ -f "$HOME/.di-netboot-assistant/di-netboot-assistant.conf" ]; then # shellcheck source=/dev/null . "$HOME/.di-netboot-assistant/di-netboot-assistant.conf" else if [ -f "/etc/di-netboot-assistant/di-netboot-assistant.conf" ]; then . "/etc/di-netboot-assistant/di-netboot-assistant.conf" fi fi # ------------------- Declare the functions ------------------ # # ------------------------------------------------------------ # # usage() # Print script usage help. # Parameters: release # Returns: (EXIT STATUS) 0=Success # ------------------------------------------------------------ # usage() { cat </dev/null 2>&1; then dpkg --print-architecture elif command -v rpm >/dev/null 2>&1; then s=$(rpm --eval "%{_arch}") s=$(tr -d " " < /usr/lib/rpm/rpmrc | grep "^buildarchtranslate:$a:") s=$(echo "$s" | cut -d: -f3) s=$(echo "$s" | sed -e 's/^x86_64$/amd64/' -e 's/^sparc[0-9]*$/sparc/' -e 's/ppc[0-9]*$/powerpc/' -e 's/^armv[3456]*$/armel/' -e 's/^armv7hl$/armhf/' -e 's/^m68kmint$/m68k/') echo "$s" else echo "i386" fi } # ------------------------------------------------------------ # # check_di_source_list() # Check the validity of di-source.list # Parameters: release # Returns: (EXIT STATUS) 0=Success, 1=Error # ------------------------------------------------------------ # check_di_source_list() { local valid_regex='^(#.*|[[:blank:]]*|[[:alnum:]_\\.-]+[ ][[:alnum:]_\\.-]+[ ][^ ]+([ ][^" ]+)+)$' if [ ! -f "$DISOURCELIST" ]; then echo "E: Debian Installer source file missing ($DISOURCELIST)" 1>&2 return 1 fi if grep -qvEl "$valid_regex" "$DISOURCELIST" ; then echo -n "E: Syntax error lines #" 1>&2 grep -vnE "$valid_regex" "$DISOURCELIST" \ | cut -d ":" -f 1 | tr "\n" "," 1>&2 echo " in file '$DISOURCELIST'." 1>&2 return 1 fi return 0 } # ------------------------------------------------------------ # # list_declared_arch_for_repo() # List archs declared for the repository in di-sources.list # Parameters: repository # Returns: (STRING) List of architectures # ------------------------------------------------------------ # list_declared_arch_for_repo() { local release=$1 echo -n "I: Declared architecures for '$1': " get_declared_arch_for_repo "$release" | tr '\n' ' ' echo "" } # ------------------------------------------------------------ # # get_declared_arch_for_repo() # List archs declared for the repository in di-sources.list # Parameters: repository # Returns: (STRING) List of architectures # ------------------------------------------------------------ # get_declared_arch_for_repo() { local release=$1 if [ "$1" ]; then grep -E "^$release\>" "$DISOURCELIST" | cut -f 2 | sort -u fi echo -n "" } # ------------------------------------------------------------ # # print_do_not_edit_header() # Print a "Do no edit this file" warning # Parameters: templatename # Returns: (STRING) file header comment # ------------------------------------------------------------ # print_do_not_edit_header() { local templatename=$1 # Template filename [[ "$templatename" =~ "ipxe" ]] && echo '#!ipxe' echo "##" echo "## DO NOT EDIT THIS FILE" echo "##" echo "## It is automatically generated by '$PACKAGE_NAME'" [ -n "$templatename" ] && echo "## using '$templatename' as template." echo "##" } # ------------------------------------------------------------ # # find_file() # Return the name of the first file matching criteria. # Parameters: name dir [dir...] # Returns: (STRING) file # ------------------------------------------------------------ # find_file() { if [ "$1" ] && [ "$2" ]; then local name=$1; shift find "$@" -type f -name "$name" | head -n 1 else echo "" fi } # ------------------------------------------------------------ # # version_lte() # Compare two "software" version (like 1.2.1 and 1.3) # Parameters: V1 V2 # Returns: (EXIT STATUS) 0=v1 <= v2, 1= V1 > V2 # ------------------------------------------------------------ # version_lte() { if command -v dpkg > /dev/null 2>&1; then dpkg --compare-versions "$1" "<=" "$2" return $? else printf "%s\n%s\n" "$1" "$2" | sort -V | head -n 1 | grep -q "^$1\$" return $? fi } # ------------------------------------------------------------ # # prepare_grub() # Install grub-EFI. # Parameters: opt # Returns: (EXIT STATUS) 0=Success, 1=Error # ------------------------------------------------------------ # prepare_grub() { local v="" opt=$1 VERS V GRUB AR DIR $VERBOSE && v="-v" [ -z "$opt" ] && [ -d $TFTP_ROOT/$N_A_DIR/grub ] && return 0 [ -d "$TFTP_ROOT/$N_A_DIR/grub/x86_64-efi" ] && rm -r "$TFTP_ROOT/$N_A_DIR/grub/x86_64-efi" [ -e "$TFTP_ROOT/$N_A_DIR/grub/font.pf2" ] && rm -r "$TFTP_ROOT/$N_A_DIR/grub/font.pf2" [ ! -e "$TFTP_ROOT/debian-installer" ] && \ ln -srv $TFTP_ROOT/$N_A_DIR/ $TFTP_ROOT/debian-installer for AR in x64 aa64 ; do ## We link bootnet*.efi and grub*.efi from the latest available image: echo "I: Preparing EFI executables for '${AR}'." GRUB="" VERS=0 for FILE in $(find "$TFTP_ROOT/$DI_PKG_DIR/" -name grub${AR}.efi) \ $(find "$TFTP_ROOT/$N_A_DIR/" -name grub${AR}.efi) ; do V=$(strings "$FILE" | grep -A1 'GNU GRUB version' | tail -1) $VERBOSE && echo "I: Found '$FILE' (version $V)." if dpkg --compare-versions "$V" gt "$VERS" ; then VERS="$V" GRUB="$FILE" fi done if [ -n "$GRUB" ] ; then DIR=$(dirname "$GRUB") echo "I: Using 'bootnet${AR}.efi' and 'grub${AR}.efi' (version $VERS) from '$DIR'." ln $v -fsr "$DIR/grub${AR}.efi" "$TFTP_ROOT/$N_A_DIR/" ln $v -fsr "$DIR/bootnet${AR}.efi" "$TFTP_ROOT/$N_A_DIR/" if [ "$AR" = "x64" ] ; then ln $v -fsr "$DIR/grub/x86_64-efi" "$TFTP_ROOT/$N_A_DIR/grub/" ln $v -fsr "$DIR/grub/font.pf2" "$TFTP_ROOT/$N_A_DIR/grub/" [ ! -e "$TFTP_ROOT/$N_A_DIR/amd64" ] && \ ln $v -sr "$TFTP_ROOT/$N_A_DIR" "$TFTP_ROOT/$N_A_DIR/amd64" elif [ "$AR" = "aa64" ] ; then ln $v -fsr "$DIR/grub/arm64-efi" "$TFTP_ROOT/$N_A_DIR/grub/" ln $v -fsr "$DIR/grub/font.pf2" "$TFTP_ROOT/$N_A_DIR/grub/" [ ! -e "$TFTP_ROOT/$N_A_DIR/arm64" ] && \ ln $v -sr "$TFTP_ROOT/$N_A_DIR" "$TFTP_ROOT/$N_A_DIR/arm64" else echo "E: Unknown architecture: $AR." fi else $VERBOSE && echo "I: No 'grub${AR}.efi' available." fi done return 0 } # ------------------------------------------------------------ # # copy_syslinux_bin() # Install pxelinux binaries in the target folder. # Parameters: src dst # Returns: (EXIT STATUS) 0=Success, 1=Error # ------------------------------------------------------------ # copy_syslinux_bin() { local src=$1 # Source directory local dst=$2 # Target directory local c32_dir=$dst/pxelinux.cfg local f srcf newbin pxe_new_ver pxe_cur_ver [ ! "$src" ] || [ ! "$dst" ] && return 1 if [ "$SYSLINUX" = "$src" ]; then # avoid recent SYSLINUX EFI binaries incompatible with PXELINUX [ ! -d "$src/modules/bios" ] || src="$src/modules/bios" # recent SYSLINUX ships PXELINUX at separate location newbin=$(find_file pxelinux.0 /usr/lib/PXELINUX "$SYSLINUX" 2>/dev/null) else newbin=$(find_file pxelinux.0 "$src" 2>/dev/null) fi [ ! -f "$dst/pxelinux.0" ] && [ ! -f "$newbin" ] && return 1 pxe_new_ver="$(pxelinux_version "$newbin")" pxe_cur_ver="$(pxelinux_version "$dst/pxelinux.0")" if version_lte "$pxe_new_ver" "$pxe_cur_ver"; then return 0 fi if [ -n "$pxe_cur_ver" ] && [ -n "$pxe_new_ver" ] ; then echo "I: Upgrading PXE-Linux ($pxe_cur_ver to $pxe_new_ver)" else echo "I: Installing PXE-Linux ($pxe_new_ver)" fi for f in pxelinux.0 menu.c32 vesamenu.c32; do if [ pxelinux.0 = "$f" ]; then srcf="$newbin" else srcf="$(find_file $f "$src")" fi [ "${f#*c32}" ] || f="pxelinux.cfg/$f" [ -L "$dst/$f" ] && rm "$dst/$f" if [ -f "$srcf" ]; then cp "$srcf" "$dst/$f" else [ -f "$dst/$f" ] && rm "$dst/$f" fi done # Smooth transition to vesamenu [ ! -f "$c32_dir/menu.c32" ] && ln -s "vesamenu.c32" "$c32_dir/menu.c32" # Add core modules at root (see ) if [ "$TFTP_ROOT/$N_A_DIR/" = "$dst" ] ; then for f in ldlinux.c32 libcom32.c32 libutil.c32 ; do srcf="$(find_file $f "$src")" [ -z "$srcf" ] || cp -np "$srcf" "$TFTP_ROOT/$N_A_DIR/$f" done fi return 0 } # ------------------------------------------------------------ # # update_menu() # Create the bootloader's top menu. # Parameters: (NONE) # Returns: (EXIT STATUS) 0 # ------------------------------------------------------------ # update_menu() { if [ ! -d "$TFTP_ROOT/$N_A_DIR" ] ; then if [ ! -d "$TFTP_ROOT/$DI_PKG_DIR" ] ; then return else mkdir "$TFTP_ROOT/$N_A_DIR" fi fi cd "$TFTP_ROOT/$N_A_DIR" update_pxe_grub_menu include_packages_live prepare_grub "" if find "$TFTP_ROOT/$N_A_DIR" -mindepth 1 -type d | grep -q "." || \ [ -d $TFTP_ROOT/$DI_PKG_DIR ] ; then sed -e 's/^\s*//' > "$TFTP_ROOT/$N_A_DIR/README.txt" < /dev/null 2>&1 || true fi rmdir --ignore-fail-on-non-empty "$TFTP_ROOT/$N_A_DIR" return 0 } # ------------------------------------------------------------ # # update_pxe_grub_menu() # Create PXElinux and grub-EFI bootloader top menu. # Parameters: (NONE) # Returns: (EXIT STATUS) 0 # ------------------------------------------------------------ # update_pxe_grub_menu() { local x i s e entry tag echo "I: Building menu entries for the netboot-images." [ ! -d "pxelinux.cfg" ] && mkdir "pxelinux.cfg" print_do_not_edit_header "$TEMPLATES/pxelinux.HEAD" > pxelinux.cfg/default print_do_not_edit_header "$TEMPLATES/pxeuboot.HEAD" > pxelinux.cfg/default-arm [ ! -d "grub" ] && mkdir -p "grub" print_do_not_edit_header "$TEMPLATES/grub.cfg.HEAD" > grub/grub.cfg print_do_not_edit_header "$TEMPLATES/ipxemenu.HEAD" > menu.ipxe if [ -n "$(find "$TFTP_ROOT/$N_A_DIR" -type d -name pxelinux.cfg.serial-9600 2>/dev/null)" ]; then echo -e "CONSOLE 0\nSERIAL 0 9600" > pxelinux.cfg/default.serial-9600 cat pxelinux.cfg/default >> pxelinux.cfg/default.serial-9600 else [ -f "pxelinux.cfg/default.serial-9600" ] && rm pxelinux.cfg/default.serial-9600 fi [ -f $TEMPLATES/pxelinux.HEAD ] && grep -Ev "^##" $TEMPLATES/pxelinux.HEAD >> pxelinux.cfg/default [ -f $TEMPLATES/pxeuboot.HEAD ] && grep -Ev "^##" $TEMPLATES/pxeuboot.HEAD >> pxelinux.cfg/default-arm [ -f $TEMPLATES/grub.cfg.HEAD ] && grep -Ev "^##" $TEMPLATES/grub.cfg.HEAD >> grub/grub.cfg [ -f $TEMPLATES/ipxemenu.HEAD ] && grep -Ev "^##" $TEMPLATES/ipxemenu.HEAD >> menu.ipxe i=0 for x in "$STATUS_LIB"/*.pxelinux.menu.fragment ; do i=$((i + 1)) grep -Ev "^##" "$x" >> pxelinux.cfg/default echo -n "I: • " grep -E "^[[:space:]]*MENU BEGIN" "$x" | sed -e "s/.*MENU BEGIN[[:space:]]\+//" done if s=$(find . -path "*/stable/*/boot-screens/splash.png" | head -1) && [ -n "$s" ] ; then echo -e "MENU BACKGROUND ::/$N_A_DIR${s#.}\n" >> pxelinux.cfg/default echo "I: Using splash screen from 'stable' image." elif [ -d $TFTP_ROOT/$DI_PKG_DIR ] && \ s=$(find $TFTP_ROOT/$DI_PKG_DIR -name "splash.png" | head -1) && [ -n "$s" ] ; then echo -e "MENU BACKGROUND ::${s#"$TFTP_ROOT"}\n" >> pxelinux.cfg/default echo "I: Using splash screen from debian-installer-*-netboot-* package." else echo "I: Splash screen not found. Install 'stable' to use its splash screen." fi [ $i -eq 0 ] && [ ! -d $TFTP_ROOT/$DI_PKG_DIR ] && rm pxelinux.cfg/default rm pxelinux.cfg/default.mig-bak 2>/dev/null || true echo "I: Building menu entries for u-boot." i=0 for x in "$STATUS_LIB"/*.uboot.menu.fragment ; do i=$((i + 1)) grep -Ev "^##" "$x" >> pxelinux.cfg/default-arm echo -n "I: • " grep -E "^[[:space:]]*LABEL" "$x" | sed -e "s/.*LABEL[[:space:]]\+//" done [ $i -eq 0 ] && [ ! -d $TFTP_ROOT/$DI_PKG_DIR ] && rm pxelinux.cfg/default-arm i=0 for x in "$STATUS_LIB"/*.grub.menu.fragment ; do i=$((i + 1)) grep -Ev "^##" "$x" >> grub/grub.cfg done [ $i -eq 0 ] && [ ! -d $TFTP_ROOT/$DI_PKG_DIR ] && rm grub/grub.cfg i=0 for x in "$STATUS_LIB"/*.ipxe.menu.fragment ; do i=$((i + 1)) e="$(grep -Ev "^##" "$x")" echo -e "$e\n" >> menu.ipxe entry="$(echo "$e" | sed -n -e "s/[[:space:]]\+/ /g" -e "s/[[:space:]]*echo Booting //" -e 2p)" tag="$(echo "$e" | sed -n -e "s/://" -e 1p)" sed -i "s%\(\# END_INSTALLER_MENU.*\)%item $tag $entry\n\1%" menu.ipxe done [ $i -eq 0 ] && [ ! -d $TFTP_ROOT/$DI_PKG_DIR ] && rm menu.ipxe i=0 if [ -f "pxelinux.cfg/default.serial-9600" ]; then i=$((i + 1)) [ -f "$TEMPLATES/pxelinux.HEAD" ] && cat "$TEMPLATES/pxelinux.HEAD" >> pxelinux.cfg/default.serial-9600 for x in "$STATUS_LIB/"*pxelinux.menu.serial-9600.fragment ; do grep -Ev "^##" "$x" >> pxelinux.cfg/default.serial-9600 echo "" >> pxelinux.cfg/default.serial-9600 done [ $i -eq 0 ] && rm pxelinux.cfg/default.serial-9600 fi return 0 } # ------------------------------------------------------------ # # include_packages_live() # Create PXElinux bootloader menu for installed # debian-installer-*-netboot-* packages and debian-live iso images. # Parameters: (NONE) # Returns: (EXIT STATUS) 0 # ------------------------------------------------------------ # include_packages_live() { local x cfg ncfgdir gcfg ngcfg title relpath ISO_NAME AR if [ ! -e "$TFTP_ROOT/$N_A_DIR/pxelinux.0" ] ; then copy_syslinux_bin "$SYSLINUX" "$TFTP_ROOT/$N_A_DIR/" || \ copy_syslinux_bin "${TFTP_ROOT}/${DI_PKG_DIR}" "$TFTP_ROOT/$N_A_DIR/" || \ echo "E: No PXE binaries found and installed." 1>&2 fi echo "I: Building menu entries for debian-installer-*-netboot-* packages and Debian Live images." echo -e "MENU SEPARATOR\n" >> pxelinux.cfg/default for x in "${TFTP_ROOT}/${DI_PKG_DIR}"/images/*/*/*/version.info ; do relpath="$(dirname "$x" | sed -e "s#${TFTP_ROOT}##" -e "s#^/*##")" title="$(tr -d "\n" < "$x" |sed -e "s#Installer##" -e "s# version: ##" \ -e "s#build: ##") $(echo "$relpath" | sed -re "s#^.+/(\w+/\w+)\$#\1#")" cat >> pxelinux.cfg/default <> menu.ipxe < "${TFTP_ROOT}/$N_A_DIR/$ngcfg" sed -e "s#$REWRITEPKGPATH#$relpath/debian-installer#" \ -e "s#isolinux/splash.png#$relpath/debian-installer/${AR}/boot-screens/splash.png#" \ "$gcfg" >> "$ngcfg" cat >> grub/grub.cfg <> pxelinux.cfg/default echo "I: Building menu entries for Debian Live ISOs." for x in "${TFTP_ROOT}/${LIVE_DIR}"/*/.disk/info ; do relpath=$(dirname "$x" | sed -e "s#${TFTP_ROOT}##" -e "s#^/*##" -e "s#\/.disk##") # shellcheck disable=SC2034 ISO_NAME=$(basename "$relpath") title="$(sed -e "s#Official ##" -e "s#\(T\|-\)[0-9:]\{5,8\}.\?\$##" "$x")" ## We cannot modify the iso images, copy grub.cfg instead: cfg="${TFTP_ROOT}/${relpath}/isolinux/menu.cfg" ncfgdir="live/pxe-${relpath//'/'/'_'}" mkdir -p "$ncfgdir" cp -a "${TFTP_ROOT}/${relpath}/isolinux/" "$ncfgdir" print_do_not_edit_header "$cfg" > "${TFTP_ROOT}/$N_A_DIR/$ncfgdir/isolinux/menu.cfg" sed -e "s#live#$relpath/live#" -e "s#isolinux#$relpath/isolinux#" \ -e "s#quiet#quiet fetch=$(eval echo "${SQUASHIMG}")#" \ -e "s#linux #kernel ::#" -e "s#initrd=#\n append initrd=::#" \ "$cfg" >> "$ncfgdir/isolinux/menu.cfg" cat >> pxelinux.cfg/default <> menu.ipxe < "${TFTP_ROOT}/$N_A_DIR/$ngcfg" sed -e "s#live#$relpath/live#" -e "s#isolinux#$relpath/isolinux#" \ -e "s#\"\${loopback}\"#fetch=$(eval echo "${SQUASHIMG}")#" \ -e "s# findiso=\${iso_path}# fetch=$(eval echo "${SQUASHIMG}")#" \ "$gcfg" >> "$ngcfg" cat >> grub/grub.cfg <> pxelinux.cfg/default-arm <&2 exit 1 fi if [[ ! "$TFTP_ROOT" =~ ^/ ]] ; then echo "E: Invalid TFTP root specified ($TFTP_ROOT). Path needs to be absolute." 1>&2 exit 1 fi if [ ! -d "$TFTP_ROOT" ]; then echo "E: TFTP root directory doesn't exists ($TFTP_ROOT)" 1>&2 echo "I: Make sure you installed a tftp server like tftpd-hpa or atftpd." exit 1 fi [ ! -d "$TFTP_ROOT/$N_A_DIR" ] && mkdir -p "$TFTP_ROOT/$N_A_DIR" if [ ! -w "$TFTP_ROOT/$N_A_DIR" ]; then echo "E: Can't write to DI directory ($TFTP_ROOT/$N_A_DIR)" 1>&2 exit 1 fi } #This function should be kept in sync with function "uninstall_repo" in debian/postrm # ------------------------------------------------------------ # # uninstall_repo() # Remove the specfied repository. # Parameters: dist_conf # Returns: (EXIT STATUS) 0 # ------------------------------------------------------------ # uninstall_repo() { dist_conf="$1" # Repository's .conf file local s metadatabasename tarfile expand_dir dist_dir metadatabasename="${dist_conf%.conf}" expand_dir="$(grep -E "^[[:blank:]]*expand_dir=" "$dist_conf" | sed -e 's/^[[:blank:]]*expand_dir=//')" [ "$expand_dir" != "/" ] && [ -d "$expand_dir" ] && rm -Rf "$expand_dir" dist_dir=$(dirname "$expand_dir") # shellcheck disable=SC2086 rmdir $RM_VERBOSITY --ignore-fail-on-non-empty "$dist_dir" s="$metadatabasename.pxelinux.menu.fragment" # shellcheck disable=SC2086 [ -f "$s" ] && rm $RM_VERBOSITY "$s" s="$metadatabasename.uboot.menu.fragment" # shellcheck disable=SC2086 [ -f "$s" ] && rm $RM_VERBOSITY "$s" s="$metadatabasename.grub.menu.fragment" # shellcheck disable=SC2086 [ -f "$s" ] && rm $RM_VERBOSITY "$s" s="$metadatabasename.ipxe.menu.fragment" # shellcheck disable=SC2086 [ -f "$s" ] && rm $RM_VERBOSITY "$s" s="$metadatabasename.pxelinux.menu.serial-9600.fragment" # shellcheck disable=SC2086 [ -f "$s" ] && rm $RM_VERBOSITY "$s" # shellcheck disable=SC2086 rm $RM_VERBOSITY "$dist_conf" return 0 } # ------------------------------------------------------------ # # get_installed_repos() # List the installed repositories. # Parameters: none # Returns: (STRINGS) Installed repos # ------------------------------------------------------------ # get_installed_repos() { find "$STATUS_LIB/" -name \*--\*.conf \ | sed -e 's,^.*/,,' -e 's/--.*\.conf//' \ | uniq | tr '\n' ' ' } # ------------------------------------------------------------ # # uninstall_repos() # Remove the specfied repository for all specified archs. # Parameters: repo ignore_missing # Returns: (EXIT STATUS) 0=Success, 1=Error # ------------------------------------------------------------ # uninstall_repos() { local repo="$1" # Name of the repository local ignore_missing="$2" # Don't repo. local a archs installed_archs installed_repos $DEBUG && set -x if [ ! -d "$STATUS_LIB" ]; then echo "E: Failed to uninstall repository, lib folder not found." 1>&2 exit 1 fi installed_archs="$(find "$STATUS_LIB/" -name \*"$repo"--\*.conf | sed -e 's/^.*--//' -e 's/\.conf//' | tr '\n' ' ')" if [ ! "$installed_archs" ] && [ "$ignore_missing" != "ignore_missing" ]; then installed_repos="$(get_installed_repos)" echo "E: Repository '$repo' not installed." 1>&2 echo -e "E: Installed repositories are:\n${installed_repos}" 1>&2 exit 1 fi [ ! "$ARCH" ] && ARCH=$DEFAULT_ARCH if echo "$ARCH" | grep -qE "\" ; then archs="$installed_archs" else archs="$(echo "$ARCH" | tr ',' ' ')" fi for a in $archs ; do if [ -f "$STATUS_LIB/$repo--$a.conf" ]; then uninstall_repo "$STATUS_LIB/$repo--$a.conf" else if [ "$ignore_missing" != "ignore_missing" ]; then echo "E: Repository '$repo' for architecture '$a' doesn't exists." 1>&2 echo -e "E: Installed are:\n$(echo "$installed_archs" | tr "\n" " ")" 1>&2 return 1 fi fi done $DEBUG && set +x return 0 } #This function should be kept in sync with function "url2filename" in debian/postrm # ------------------------------------------------------------ # # url2filename() # Convert an URL into a valid filename. # Parameters: (PIPE) url # Returns: (STRING) filename # ------------------------------------------------------------ # url2filename() { sed -e 's#//\+#/#g' -e 's#[^[:alnum:]@+_~\.-]#_#g' } #This function should be kept in sync with function "remove_repocache" in debian/postrm # ------------------------------------------------------------ # # remove_repocache() # Remove the cached file. # Parameters: metadatafile # Returns: (EXIT STATUS) 0 # ------------------------------------------------------------ # remove_repocache() { local metadatafile="$1" # repository to uncache local base file base="${metadatafile%~~*}" sed -n -e 's/^[[:blank:]]*dl_file=[[:blank:]]*//p' "$metadatafile" | while read -r file ; do # shellcheck disable=SC2086 rm $RM_VERBOSITY "${base}_$(echo "$file" | url2filename)" done #Purge remaing files (MD5SUMs...) if there are no more cached #distribution from the same repository. if ! echo "${base}~~"*.meta | grep -qv "$metadatafile" ; then # shellcheck disable=SC2086 rm $RM_VERBOSITY "${base}"_* fi # shellcheck disable=SC2086 [ -f "$metadatafile" ] && rm $RM_VERBOSITY "$metadatafile" return 0 } # ------------------------------------------------------------ # # remove_repocaches() # Remove the cached file. # Parameters: metadatafile # Returns: (EXIT STATUS) 0=Success, 1=Error # ------------------------------------------------------------ # remove_repocaches() { local del_repo="$1" local ignore_missing="$2" local count cached_archs archs metadatafile a $DEBUG && set -x if [ ! -d "$DL_CACHE" ]; then echo "E: Failed to clean the cache, cache folder not found." 1>&2 exit 1 fi cached_archs="$(find $DL_CACHE -name "*~~$del_repo--*.meta" | sed -e 's/^.*--//' -e 's/\.meta//' | tr '\n' ' ')" if [ ! "$cached_archs" ] && [ "$ignore_missing" != "ignore_missing" ]; then cached_repos="$(find $DL_CACHE -name \*~~\*--\*.meta | sed -e 's/^.*~~//' -e 's/--.*//' | tr '\n' ' ')" echo "E: Repository '$del_repo' not cached." 1>&2 echo "I: (cached repositories are: $cached_repos)" 1>&2 exit 1 fi [ ! "$ARCH" ] && ARCH=$DEFAULT_ARCH if echo "$ARCH" | grep -qE "\" ; then archs="$cached_archs" else archs="$(echo "$ARCH" | tr ',' ' ')" fi for a in $archs ; do count=0 for metadatafile in "$DL_CACHE"/*~~"${del_repo}--${a}".meta ; do remove_repocache "$metadatafile" count=$(( count + 1 )) done if [ $count -eq 0 ] && [ "$ignore_missing" != "ignore_missing" ]; then echo "E: Repository '$del_repo' for architecture '$a' doesn't exists." 1>&2 echo "I: (cached archs are: $cached_archs)" 1>&2 exit 1 fi done $DEBUG && set +x return 0 } # ------------------------------------------------------------ # # check_sum() # Validate a file's checksum. # Parameters: csum_file fname actual_file # Returns: (EXIT STATUS) 0=checksum is ok, 1=checksum mismatch # ------------------------------------------------------------ # check_sum() { local csum_file=$1 # file containing checksums local fname=$2 # file to look for in the checksum file local actual_file=$3 # file to calulate checksum from local sum regex if [ ! -f "$actual_file" ] || [ ! -f "$csum_file" ]; then return 1 fi sum=$($CHECKSUM_BIN "$actual_file" | cut -d " " -f 1) $VERBOSE && echo -e "I: $CHECKSUM_BIN of '$actual_file':\n$sum" 1>&2 regex="^[[:blank:]]*${sum}[[:blank:]]+([[:digit:]]+[[:blank:]]+)?(\./|)${fname}[[:blank:]]*$" if ! grep -qiE "$regex" "$csum_file" ; then $VERBOSE && echo "Checksum not found in '$csum_file'." 1>&2 return 1 elif $VERBOSE ; then echo "I: Checksum found in '$csum_file':" grep -iE "$regex" "$csum_file" fi return 0 } # ------------------------------------------------------------ # # check_signature() # Validate signature of checksum file. # Parameters: download URL # Returns: (EXIT STATUS) 0=signature is ok, # 1=signature wrong, # other error codes for fatal errors # ------------------------------------------------------------ # check_signature() { local file="$1" # downloaded checksum file local getter="$2" # download program local baseurl="$3" # URL of the checksum file directory local s dmp ret=4 dmp=$(mktemp) if [[ "$url" == *"ubuntu"* ]] ; then if $getter "$file.gpg" -- "${baseurl}/${CHECKSUM_FILE}.gpg" ; then # shellcheck disable=SC2086 LANG=C gpgv $GPG_VERBOSITY --keyring "$UBUNTU_KEYRING" "$file.gpg" "$file" > "$dmp" 2>&1 ret=$? else echo "E: Could not download '${baseurl}/${CHECKSUM_FILE}.gpg'." 1>&2 ret=3 fi else if $getter "$file.Release" -- "${baseurl}/../../../../Release" && \ $getter "$file.Release.gpg" -- "${baseurl}/../../../../Release.gpg" ; then s=$(echo "${baseurl}" | sed -E "s#.+/(.+/.+/.+/.+)\$#\1#" ) if check_sum "$file.Release" "${s}/${CHECKSUM_FILE}" "$file" ; then # shellcheck disable=SC2086 LANG=C gpgv $GPG_VERBOSITY --keyring $DEBIAN_KEYRING \ "$file.Release.gpg" "$file.Release" > "$dmp" 2>&1 ret=$? else ret=3 fi else if $IGNOR_SIG ; then echo "W: Could not download '${baseurl}/../../../../Release' and/or 'Release.gpg'." 2>&1 else echo "E: Could not download '${baseurl}/../../../../Release' and/or 'Release.gpg'." 1>&2 fi ret=3 fi fi $VERBOSE && cat "$dmp" grep "Good signature\|Can't check" "$dmp" | sed "s/gpgv/I/" if grep -q "^gpgv: Good signature" "$dmp" && [ $ret != 0 ] ; then echo "W: Not all signatures could be verified:" 2>&1 sed 's/.*/ &/' "$dmp" echo " Perhaps an outdated public key has been removed from the keyring." 2>&1 ret=0 fi [ -f "$dmp" ] && rm "$dmp" # shellcheck disable=SC2086 [ -f "$file.gpg" ] && rm $RM_VERBOSITY "$file.gpg" # shellcheck disable=SC2086 [ -f "$file.Release" ] && rm $RM_VERBOSITY "$file.Release" # shellcheck disable=SC2086 [ -f "$file.Release.gpg" ] && rm $RM_VERBOSITY "$file.Release.gpg" return $ret } # ------------------------------------------------------------ # # fetch_files() # Download netboot image(s) and save them in the cache. # Parameters: relase arch baseurl repo_loc tarfile # Returns: (EXIT STATUS) 0=Success, 1=Error # ------------------------------------------------------------ # fetch_files() { local release="$1" # Release (or variant) local arch="$2" # Architecture local baseurl="$3" # Download base URL local repo_loc="$4" # Destination dir local tarfile="$5" # File to download local getter file givenfile metadatafile success f local csum_file url cached fetch_date $DEBUG && set -x if ! $OFFLINE ; then if command -v wget > /dev/null ; then getter="wget -c -x $WGET_VERBOSITY -O" elif command -v curl > /dev/null ; then getter="curl --location --fail $CURL_VERBOSITY -o" else echo "E: Can't download file. No download program (wget or curl) found." 1>&2 return 1 fi fi metadatafile="$DL_CACHE/$(echo "${repo_loc}~~${release}--${arch}.meta" | url2filename)" success=true csum_file=/dev/null for givenfile in $CHECKSUM_FILE $tarfile ; do f=$(echo "$givenfile" | sed -e 's#^\./##' -e 's#^/##' -e 's#//#/#g') url="$baseurl/$f" file="$DL_CACHE/$(echo "$repo_loc/$f" | url2filename)" [ -e "$file.tmp" ] && rm "$file.tmp" # Does the checksum of the previous file match the new one? cached=false if [ "$givenfile" = "$CHECKSUM_FILE" ]; then csum_file="$file.tmp" $OFFLINE && cp "$file" "$file.tmp" elif check_sum "$csum_file" "$givenfile" "$file" ; then cached=true cp "$file" "$file.tmp" echo "I: File $givenfile is already cached." else [ -f "$file.tmp" ] && rm "$file.tmp" $VERBOSE && ! $OFFLINE && echo "I: File '$givenfile' not cached, or obsolete." $OFFLINE && success=false fi # Download the file, if needed. if ! $OFFLINE && ! $cached ; then echo "I: Downloading '$givenfile'." if $getter "$file.tmp" -- "$url" ; then if [ "$givenfile" = "$CHECKSUM_FILE" ] ; then $VERBOSE && echo "Verify signature for '$url':" if ! check_signature "$file.tmp" "$getter" "$baseurl" ; then cat <&2 success=false break fi else echo "E: Can't download '$release' for '$arch' ($url)." 1>&2 if [ -f "$file" ]; then echo "I: You have a previous version in your cache (see --offline option)." fi success=false break fi else if [ ! -f "$file.tmp" ]; then success=false echo "E: Can't process '$release' in offline mode, the file is missing:" 1>&2 echo "E: (expecting '$file' from '$url')" 1>&2 break elif [ -f "$metadatafile" ] ; then fetch_date="$( grep "^fetch_date=" "$metadatafile" | \ cut -d "=" -f 2- 2>/dev/null )" else # Fall back, in case the file is manually added to the cache. fetch_date="$(date -R --reference="$file.tmp" )" fi fi done $VERBOSE && echo "I: Moving and/or removing temporary file(s):" for givenfile in $CHECKSUM_FILE $tarfile ; do f=$(echo "$givenfile" | sed -e 's#^\./##' -e 's#^/##' -e 's#//#/#g') file="$DL_CACHE/$(echo "$repo_loc"/"$f" | url2filename)" if $success ; then # shellcheck disable=SC2086 [ -f "$file.tmp" ] && mv $MV_VERBOSITY "$file.tmp" "$file" else # shellcheck disable=SC2086 [ -f "$file.tmp" ] && rm $RM_VERBOSITY "$file.tmp" fi done # Save metadata if $success ; then if ! $OFFLINE ; then { echo "#$PACKAGE_NAME for '$release' ($arch)" ; echo "format=1.0" ; \ echo "fetch_date=$fetch_date" ; echo "repo=$baseurl" ; echo "dl_file=$tarfile" ; \ echo "dist=$release" ; } > "$metadatafile" fi else return 1 fi $DEBUG && set +x return 0 } # ------------------------------------------------------------ # # extract_files() # Extract (or copy) netboot image. # Parameters: repo_loc file expand_dir # Returns: (EXIT STATUS) 0=Success, 1=Error # ------------------------------------------------------------ # extract_files() { local repo_loc=$1 # Repository URL local file=$2 # Downloaded (tar) file local expand_dir=$3 # Target location fo extracted files local arch=$4 # Architecture local dl_file tar_opts nstrp file=$(echo "$file" | sed -e 's#^\./##' -e 's#^/##' -e 's#//#/#g') dl_file="$DL_CACHE/$(echo "$repo_loc/$file" | url2filename)" echo "I: Extracting '$dl_file'." if [ -d "$expand_dir" ]; then rm -Rf "${expand_dir:?}/*" # get existing metadata from the downloaded repository else mkdir -p "$expand_dir" fi : tar_opts=("$dl_file" --directory "$expand_dir" --no-same-permissions) ## Some older archives have no leading './' directory (arch dependent): if tar --list -f "$dl_file" './' >/dev/null 2>&1 ; then nstrp=3 else nstrp=2 fi case "$(basename "$dl_file")" in *.tar.gz) # shellcheck disable=SC2086 tar $TAR_VERBOSITY -zxf "${tar_opts[@]}" --strip-components "$nstrp" --exclude "./pxelinux.0" --exclude "./pxelinux.cfg" # shellcheck disable=SC2086 tar $TAR_VERBOSITY -zxf "${tar_opts[@]}" ./version.info 2>/dev/null || true ;; *.tar.bz2) # shellcheck disable=SC2086 tar $TAR_VERBOSITY -jxf "${tar_opts[@]}" --strip-components "$nstrp" --exclude "./pxelinux.0" --exclude "./pxelinux.cfg" # shellcheck disable=SC2086 tar $TAR_VERBOSITY -jxf "${tar_opts[@]}" ./version.info 2>/dev/null || true ;; *.img) # shellcheck disable=SC2086 cp $CP_VERBOSITY "$dl_file" "$expand_dir/$(basename "$file")" ;; *) echo "E: Don't know how to handle (unpack...) the file: $dl_file" 1>&2 return 1 ;; esac return 0 } # ------------------------------------------------------------ # # pxelinux_version() # Retrieve PXElinux version. # Parameters: bin # Returns: (STRING) PXElinux version # ------------------------------------------------------------ # pxelinux_version() { local bin="$1" # pxelinux.0 file if [ -f "$bin" ]; then tr -c '[:print:] ' '\n' < "$bin" | sed -n -r "/PXELINUX [.0-9]+/ s/^[^ ]* ([0-9^.]+).*/\1/ p" | sort -r | head -n 1 else echo "" fi } # ------------------------------------------------------------ # # tweak_syslinux_arguments() # Tweak the kernel arguments in pxelinux configuration # files. # Parameters: (PIPE) pristine configuration file # Returns: (STRING) tweaked configuration file # ------------------------------------------------------------ # tweak_syslinux_arguments() { sed -e "/^[[:blank:]]*label[[:blank:]]\+install\$/I,/^\([[:blank:]]*label[[:blank:]]^+[^\(install\)]\|[[:blank:]]*\)\$/I{s!append \(.*\)--\(.*\)!append \1 $DI_ARGS -- \2 $TARGET_ARGS!}" } # ------------------------------------------------------------ # # setup_syslinux() # Install and configure syslinux menu. # Parameters: (PIPE) release arch metadatabasename expand_dir # Returns: (EXIT STATUS) 0 # ------------------------------------------------------------ # setup_syslinux() { local release=$1 # D-I image release name local arch=$2 # Architecture local metadatabasename=$3 # metadata location local expand_dir=$4 # Target installation dir. local pxelinuxbin pxeuboot pxelinuxcfg ver f fd dist title local menufragment menufragment_grub menufragment_ipxe menufragment_uboot menufragment_serial9600 ## generate title for menu entries: if grep "^repo=" "${metadatabasename}.conf" | grep -q ubuntu ; then dist="Ubuntu" else dist="Debian" fi fd=$(grep fetch_date "${metadatabasename}.conf" | sed "s/fetch_date=//") fd=$(date --date="$fd" "+%Y%m%d %R") title=$(printf "%-35s ${fd}\n" "$dist ${REPO_ALIAS} ($arch)") ## generate u-boot pxe menu fragment if device tree is available: pxeuboot="$(find "$expand_dir" -type d -name "dtbs" 2>/dev/null )" if [ "$pxeuboot" ] ; then [ ! -d "$TFTP_ROOT/$N_A_DIR/pxelinux.cfg" ] && mkdir "$TFTP_ROOT/$N_A_DIR/pxelinux.cfg" menufragment_uboot="$metadatabasename.uboot.menu.fragment" cat > "$menufragment_uboot" </dev/null )" if [ "$pxelinuxbin" ]; then pxelinuxcfg="${pxelinuxbin%%.0}.cfg/default" ver="$(sed -ne 's/# D-I config version \(.*\)/\1/p' "$pxelinuxcfg" 2>/dev/null)" if [ ! -f "$pxelinuxcfg" ] || echo "${ver:-1.0}" | grep -q -v "^[12]\.0" ; then echo "W: The format of this image may not be supported." 1>&2 fi [ ! -d "$TFTP_ROOT/$N_A_DIR/pxelinux.cfg" ] && mkdir "$TFTP_ROOT/$N_A_DIR/pxelinux.cfg" copy_syslinux_bin "$SYSLINUX" "$TFTP_ROOT/$N_A_DIR/" || \ copy_syslinux_bin "$expand_dir" "$TFTP_ROOT/$N_A_DIR/" || \ echo "E: No PXE binaries installed. Please file a bug." 1>&2 # ensure only a single PXELINUX version is used for all its modules for f in "$expand_dir"/**/*.c32 ; do case $(basename "$f") in vesamenu.c32|menu.c32) cp -pft "$(dirname "$f")" "$TFTP_ROOT/$N_A_DIR/pxelinux.cfg/$(basename "$f")" ;; ldlinux.c32|libcom32.c32|libutil.c32) cp -pft "$(dirname "$f")" "$TFTP_ROOT/$N_A_DIR/$(basename "$f")" ;; *) echo "W: Unusual PXELINUX module \"$f\" may not work." 1>&2 continue ;; esac done fi for f in "$expand_dir"/**/default "$expand_dir"/**/boot.txt "$expand_dir"/**/*.cfg ; do [ -f "$f" ] || continue mv "$f" "$f.ORIG" print_do_not_edit_header "" > "$f" if [ "$(basename "$f")" = "grub.cfg" ] ; then sed -e "s#$REWRITEPKGPATH/$arch/#$N_A_DIR/$REPO_ALIAS/$arch/#" \ "$f.ORIG" >> "$f" else sed -e "s#$REWRITEPKGPATH/$arch/#::/$N_A_DIR/$REPO_ALIAS/$arch/#" \ -e "s/^\([[:space:]]*default .*$\)/\#\1/" \ -e "s/^\([[:space:]]*menu default[[:space:]]*$\)/\#\1/" \ "$f.ORIG" | tweak_syslinux_arguments >> "$f" fi done menufragment="$metadatabasename.pxelinux.menu.fragment" menufragment_grub="$metadatabasename.grub.menu.fragment" menufragment_ipxe="$metadatabasename.ipxe.menu.fragment" menufragment_serial9600="$metadatabasename.pxelinux.menu.serial-9600.fragment" cat > "$menufragment" </dev/null)" ]; then cp "$menufragment" "$menufragment_serial9600" else [ -f "$menufragment_serial9600" ] && rm "$menufragment_serial9600" fi # Create top-menu fragment: cat >> "$menufragment" <> "$menufragment_ipxe" <> "$menufragment_grub" <> "$menufragment_grub" <> "$menufragment_serial9600" fi return 0 } # ------------------------------------------------------------ # # install_repo_for_arch() # Extract/copy the downloaded file for given arch. # Parameters: arch release # Returns: (EXIT STATUS) 0=Success, 1=Error # ------------------------------------------------------------ # install_repo_for_arch() { local arch=$1 # Architecture local release=$2 # D-I image release name local reg metadatabasename file fetch_date local metadatafile repo_orig repo_mirror expand_dir echo "I: Processing $release/$arch." $DEBUG && set -x metadatabasename="$STATUS_LIB/${REPO_ALIAS}--${arch}" metadatafile="$metadatabasename.conf" repo_orig="$(grep -E "^${release}[[:blank:]]$arch\>" "$DISOURCELIST")" repo_mirror="$repo_orig" for reg in $MIRROR_REGEXPS "s=/$==" ; do repo_mirror="$(echo "$repo_mirror" | sed -e "$reg")" done repo="$(echo "$repo_orig" | cut -f 3 | sed -e 's#\([^:]/\)/#\1#g' -e 's#/$##' )" repo_mirror="$(echo "$repo_mirror" | cut -f 3 | sed -e 's#\([^:]/\)/#\1#g' -e 's#/$##' )" if [ -z "$repo" ]; then echo "E: There is no entry declared for architecture '$arch' for repository '$release' in $DISOURCELIST" 1>&2 list_declared_arch_for_repo "$release" return 1 fi if [ "$release" != "$REPO_ALIAS" ] ; then echo "I: Repository '$release' filed as '$REPO_ALIAS'." fi repo_loc="${repo##*://}" expand_dir="$TFTP_ROOT/$N_A_DIR/$REPO_ALIAS/$arch" file=$(grep -E "^${release}[[:blank:]]$arch" "$DISOURCELIST" | cut -f 4- ) fetch_date="" if ! fetch_files "$release" "$arch" "$repo_mirror" "$repo_loc" "$file" ; then return 1 fi if ! extract_files "$repo_loc" "$file" "$expand_dir" "$arch" ; then return 1 fi # save metadata of this repository grep -v -E "^format=.*" \ "$DL_CACHE/$(echo "${repo_loc}~~${release}--${arch}.meta" | url2filename)" \ | sed -e "s/^fetch_date=/format=1.0\n\0/" > "$metadatafile" { echo "expand_dir=$expand_dir" ; echo "di_args=$DI_ARGS" ; echo "target_args=$TARGET_ARGS" ; } >> "$metadatafile" # PXELINUX/GRUB MENUs setup_syslinux "$release" "$arch" "$metadatabasename" "$expand_dir" $DEBUG && set +x return 0 } # ------------------------------------------------------------ # # toggle_fw_for_arch() # Add/remove non-free firmware for given arch. # Parameters: arch release # Returns: (EXIT STATUS) 0=Success, 1=Error # ------------------------------------------------------------ # toggle_fw_for_arch() { local arch=$1 # Architecture local release=$2 # D-I image release name local reg file fetch_date repo_orig repo_mirror expdirs expand_dir echo "I: Processing non-free firmware for $release/$arch." if [ "$release" != "$REPO_ALIAS" ] ; then echo "I: Repository '$release' filed as '$REPO_ALIAS'." elif [ "$release" = "daily" ] ; then release="sid" fi $DEBUG && set -x if [ "$release" = "n-pkg" ] ; then expdirs="$(find "$TFTP_ROOT/$DI_PKG_DIR/" -name 'initrd.gz' | \ grep "$arch" | rev | cut -f2- -d '/' | rev)" release="stable" else expdirs="$TFTP_ROOT/$N_A_DIR/$REPO_ALIAS/$arch" fi repo_mirror="$NONFREE_FW/$release/current" repo_loc="${repo_mirror##*://}" for expand_dir in $expdirs ; do echo "I: Processing '$expand_dir/'." if [ -f "$expand_dir/initrd.gz.orig" ] && [ -f "$expand_dir/initrd.gz.fw" ] && \ cmp --silent "$expand_dir/initrd.gz" "$expand_dir/initrd.gz.fw" ; then ## → The initrd is the original with firmware added. (It has not been overwritten.) echo "I: Removing non-free firmware." # shellcheck disable=SC2086 cp -p $CP_VERBOSITY "$expand_dir/initrd.gz.orig" "$expand_dir/initrd.gz" else ## → The initrd has either been overwritten, or it is the (old) original. file="firmware.cpio.gz" fetch_date="" if ! fetch_files "$release" "$arch" "$repo_mirror" "$repo_loc" $file ; then return 1 fi ## Ignore the missing signature of the fw file from now on: IGNOR_SIG=true echo "I: Adding non-free firmware." fw_file="$DL_CACHE/$(echo "$repo_loc/$file" | url2filename)" # shellcheck disable=SC2086 cp -p $CP_VERBOSITY "$expand_dir/initrd.gz" "$expand_dir/initrd.gz.orig" cat "$expand_dir/initrd.gz.orig" "$fw_file" > "$expand_dir/initrd.gz.fw" # shellcheck disable=SC2086 cp -p $CP_VERBOSITY "$expand_dir/initrd.gz.fw" "$expand_dir/initrd.gz" fi done $DEBUG && set +x return 0 } # ------------------------------------------------------------ # # install_repo_for_archs() # Extract/copy the downloaded file for specified archs. # Parameters: release # Returns: (EXIT STATUS) 0=Success, 1=Error # ------------------------------------------------------------ # install_repo_for_archs() { local release="$1" # release name to install local archs ret if [ -z "$release" ]; then echo "E: No repository specified (valid repositories are: ${releases})." 1>&2 return 1 fi if ! grep -Eq "^$release\>" "$DISOURCELIST" ; then echo "E: Invalid repository name specified ($release)." 1>&2 echo -e "E: Declared repositories are:\n${releases}" 1>&2 return 1 fi [ ! "$ARCH" ] && ARCH=$DEFAULT_ARCH if echo "$ARCH" | grep -qE "\" ; then archs="$(get_declared_arch_for_repo "$release")" else archs="$(echo "$ARCH" | tr ',' ' ')" fi for arch in $archs ; do install_repo_for_arch "$arch" "$release" ret=$? if [ $ret != 0 ] ; then return $ret fi done return 0 } # ------------------------------------------------------------ # # toggle-firmware() # Add/remove non-free firmware to/from the initrd. # Parameters: release # Returns: (EXIT STATUS) 0=Success, 1=Error # ------------------------------------------------------------ # toggle-firmware() { local release="$1" # release name to install local archs ret [ ! "$ARCH" ] && ARCH=$DEFAULT_ARCH if echo "$ARCH" | grep -qE "\" ; then if [ "$release" != "n-pkg" ] ; then archs="$(get_declared_arch_for_repo "$release")" else archs="$(find "$TFTP_ROOT/$DI_PKG_DIR/" -name 'initrd.gz' | rev | cut -f2 -d '/' | rev | uniq)" fi else archs="$(echo "$ARCH" | tr ',' ' ')" fi for arch in $archs ; do if [ "$release" != "n-pkg" ] && [ ! -d "$TFTP_ROOT/$N_A_DIR/$REPO_ALIAS/$arch" ] ; then echo "E: Repository '$REPO_ALIAS/$arch' is not available." 1>&2 return 1 fi toggle_fw_for_arch "$arch" "$release" ret=$? if [ $ret != 0 ] ; then return $ret fi done return 0 } # --------------------------- Main ------------------------- # ACTION= COUNT=0 for option in "$@"; do case "$option" in -h | --help) usage exit 0 ;; -V | --version) echo "$PACKAGE_NAME $PACKAGE_VERSION" exit 0 ;; --arch=*) ARCH="$(echo "$option" | sed -e 's/--arch[=]\?//' -e 's/,,/,/' -e 's/^,\+//' -e 's/,\+$//' )" #Note: if echo "$ARCH" | grep -qE '^[[:alnum:]_,]\+$' ; then echo "E: Invalid architecture specified ($ARCH)" 1>&2 exit 1 elif [ -z "$ARCH" ] ; then ARCH=EMPTY fi ;; --alias=*) CLI_ALIAS="$(echo $option | sed -e 's/--alias[=]\?//' | grep -E "^[[:alnum:]_-]+$" )" #Note: if echo "$CLI_ALIAS" | grep -qE '^[[:alnum:]_,]\+$' ; then echo "E: Invalid alias name ($option)" 1>&2 exit 1 fi ;; --di-args=*) DI_ARGS="$DI_ARGS ${option#--di-args=}" ;; --ignore-sig) IGNOR_SIG=true ;; --target-args=*) TARGET_ARGS="$TARGET_ARGS ${option#--target-args=}" ;; --tftproot=*) TFTP_ROOT="${option#--tftproot=}" ;; --offline) OFFLINE=true ;; -v | --verbose) VERBOSE=true ;; --debug) # This is an undocumented feature... DEBUG=true ;; --di-args| --target-args) echo "E: Option $option requires a value after equal sign." 1>&2 exit 1 ;; -*) echo "E: Unrecognized option ($option)" 1>&2 exit 1 ;; rebuild-menu|install|uninstall|uncache|purge|fw-toggle) #Actions are processed in the loop below if [ "$ACTION" ]; then echo "E: Unexpected command '$option'. '$ACTION' was already specified." 1>&2 exit 1 fi ACTION=$option ;; *) COUNT=$(( COUNT + 1 )) ;; esac done if $VERBOSE; then WGET_VERBOSITY="" CURL_VERBOSITY="" RM_VERBOSITY="${RM_VERBOSITY:=-v}" MV_VERBOSITY="${MV_VERBOSITY:=-v}" CP_VERBOSITY="${CP_VERBOSITY:=-v}" TAR_VERBOSITY="${TAR_VERBOSITY:=-v}" GPG_VERBOSITY="${GPG_VERBOSITY:=-v}" else WGET_VERBOSITY="--quiet" CURL_VERBOSITY="--silent" RM_VERBOSITY="${RM_VERBOSITY:=}" MV_VERBOSITY="${MV_VERBOSITY:=}" CP_VERBOSITY="${CP_VERBOSITY:=}" TAR_VERBOSITY="${TAR_VERBOSITY:=}" GPG_VERBOSITY="${GPG_VERBOSITY:=-q}" fi DEFAULT_ARCH="$(detect_current_arch)" if ! check_di_source_list; then exit $? fi releases="$(grep -vE '^#' "$DISOURCELIST" | cut -f 1 | sort -u | tr "\n" " " |\ sed -e 's/^[[:blank:]]\+//' -e 's/[[:blank:]]\+$//')" if [ -n "$CLI_ALIAS" ]; then if [ "$ACTION" = "install" ] && [ $COUNT -gt 1 ] ; then echo "E: Option --alias can't be used with multiple repositories." 1>&2 exit 1 fi fi case "$ACTION" in '') #Skip, if no action specified ;; rebuild-menu|rebuild-grub) if [ $COUNT -ne 0 ]; then echo "E: Unexpected argument after command '$ACTION'." 1>&2 exit 1 fi ;; *) if [ $COUNT -eq 0 ]; then echo "E: No repository name was passed for '$ACTION'." 1>&2 ! $OFFLINE && [ "$ACTION" = "install" ] && echo "I: Declared repositories are:" &&\ echo "${releases}" cached_repos="$( find $DL_CACHE -name "*~~*--*.meta" | \ sed -e 's/^.*~~//' -e 's/--.*\.meta//' | sort -u | tr "\n" " " | \ sed -e 's/[[:blank:]]\+$//')" installed_repos="$(get_installed_repos)" purgabled_repos="$(echo "$cached_repos" "$installed_repos" | tr " " "\n" | sort -u | \ tr "\n" " " | sed -e 's/[[:blank:]]\+$//')" [ "$ACTION" = "uncache" ] && echo -e "I: Cached repositories are:\n${cached_repos}" [ "$ACTION" = "uninstall" ] || [ "$ACTION" = "fw-toggle" ] && echo -e "I: Installed repositories are:\n${installed_repos}" [ "$ACTION" = "purge" ] && echo -e "I: Purgable repositories are:\n${purgabled_repos}" exit 1 fi ;; esac ACTION= COUNT=0 for option in "$@"; do case "$option" in -*) # Ignore options on this pass ;; install|uninstall|uncache|purge|fw-toggle) ACTION=$option ;; rebuild-menu) ACTION=$option update_menu ;; rebuild-grub) ACTION=$option prepare_grub "$option" ;; *) if [ -n "$CLI_ALIAS" ] ; then REPO_ALIAS="$CLI_ALIAS" else REPO_ALIAS="$option" fi if [ "$ARCH" = "EMPTY" ] ; then if [ "$ACTION" = "install" ] ; then list_declared_arch_for_repo "$option" else echo "I: Installed architectures for '$option': $(find "$STATUS_LIB/" -name \*$option--\*.conf | sed -e 's/^.*--//' -e 's/\.conf//' | tr '\n' ' ')" fi exit 0 fi case "$ACTION" in install) check_tftp_root cd "$TFTP_ROOT/$N_A_DIR" if install_repo_for_archs "$option" ; then update_menu else rmdir --ignore-fail-on-non-empty "$TFTP_ROOT/$N_A_DIR" exit 1 fi ;; fw-toggle) toggle-firmware "$option" ;; uninstall) uninstall_repos "$option" "" update_menu ;; uncache) remove_repocaches "$option" "" ;; purge) uninstall_repos "$option" "ignore_missing" update_menu remove_repocaches "$option" "ignore_missing" ;; rebuild-menu|rebuild-grub) echo "W: Argument '$option' ignored ($ACTION expects no argument)." 1>&2 ;; *) echo "E: Unexpected keyword: '$option'. No action was specified." 1>&2 exit 1 ;; esac COUNT=$(( COUNT + 1 )) esac done if [ ! "$ACTION" ]; then usage 1>&2 exit 1 fi