diff --git a/build-tools/patch-iso-debian b/build-tools/patch-iso-debian
index 7227dab3..222e28dc 100755
--- a/build-tools/patch-iso-debian
+++ b/build-tools/patch-iso-debian
@@ -1,37 +1,37 @@
#!/bin/bash
#
-# Copyright (c) 2023 Wind River Systems, Inc.
+# Copyright (c) 2024 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
-# Utility for adding patch metadata to the iso
-# Debian patches are sequential and uses ostree
-# so any patch will produce an updated iso, this
-# utility injects the patch metadata into the iso.
-# During install the kickstart will copy the metadata
-# into the right location and the sw-patch query
-# command will output the correct patch level
+# This script takes as input an ISO and one or more patches
+# and generates as output an ISO with the following changes:
+#
+# - Contains only the latest ostree commit from the input ISO
+# - ISO has a "patches" folder with the patches' metadata files.
+# This folder is processed by kickstart during install, so that
+# 'sw-patch query' has access to this info.
+#
+# The intent is for the system to have record of the patches that are
+# already pre-installed in the system.
#
-source "$(dirname $0)/image-utils.sh"
+BUILD_TOOLS_DIR="$(dirname "$0")"
-if [ -z "${STX_BUILD_HOME}" ]; then
- echo "Required environment variable STX_BUILD_HOME is not set"
- exit 1
-fi
+# shellcheck source="./build-tools/image-utils.sh"
+source "${BUILD_TOOLS_DIR}/image-utils.sh"
+# Define MY_REPO, which is the path to the 'root' repo. Eg.: $REPO_ROOT/cgcs_root
+# Value is used to locate the following file for ISO signing:
+# ${MY_REPO}/build-tools/signing/dev-private-key.pem
if [ -z "${MY_REPO}" ]; then
- echo "Required environment variable MY_REPO is not set"
- exit 1
+ MY_REPO="$(dirname "${BUILD_TOOLS_DIR}")"
fi
-DEPLOY_DIR="${STX_BUILD_HOME}/localdisk/deploy"
-OSTREE_REPO="${DEPLOY_DIR}/ostree_repo"
-
function usage() {
echo ""
echo "Usage: "
- echo " $(basename $0) -i -o [ -p ] ..."
+ echo " $(basename "$0") -i -o [ -p ] ..."
echo " -i : Specify input ISO file"
echo " -o : Specify output ISO file"
echo " -p : Patch files. You can call it multiple times."
@@ -44,28 +44,33 @@ function extract_ostree_commit_from_metadata_xml() {
# Check if xmllint is available. Otherwise, use python's xml standard lib
if (which xmllint &>/dev/null); then
- xmllint --xpath "string(${XPATH})" ${XML_PATH}
+ xmllint --xpath "string(${XPATH})" "${XML_PATH}"
else
python3 -c "import xml.etree.ElementTree as ET ; print(ET.parse('${XML_PATH}').find('.${XPATH}').text, end='')"
fi
}
function extract_metadata() {
- local patchesdir=${BUILDDIR}/patches
- local patchfile=$1
- local patchid=$(basename $patchfile .patch)
- local ostree_log=$(ostree --repo=${OSTREE_REPO} log starlingx)
+ local patchesdir
+ local patchfile
+ local patchid
+ local ostree_log
+ local ostree_commit
+
+ patchesdir="${BUILDDIR}/patches"
+ patchfile="$1"
+ patchid="$(basename "$patchfile" .patch)"
+ ostree_log="$(ostree --repo="${2}" log starlingx)"
echo "Extracting ${patchfile}"
# Extract it
- tar xf ${patchfile} -O metadata.tar | tar x -O > ${patchesdir}/${patchid}-metadata.xml
- if [ $? -ne 0 ]; then
- echo "Failed to extract metadata from ${patchfile}"
+ if ! tar xf "${patchfile}" -O metadata.tar | tar x -O > "${patchesdir}/${patchid}-metadata.xml"; then
+ echo "ERROR: Failed to extract metadata from ${patchfile}"
exit 1
fi
# Verify if top commit from metadata exist in ostree log
- patch_ostree_commit1=$(xmllint --xpath "string(//contents/ostree/commit1/commit)" ${patchesdir}/${patchid}-metadata.xml)
+ patch_ostree_commit1="$(extract_ostree_commit_from_metadata_xml "${patchesdir}/${patchid}-metadata.xml")"
if [[ "$ostree_log" != *"$patch_ostree_commit1"* ]]; then
echo "WARNING: Patch ostree commit 1 not found in input ISO."
echo "Patch ostree commit 1: ${patch_ostree_commit1}"
@@ -74,8 +79,6 @@ function extract_metadata() {
declare INPUT_ISO=
declare OUTPUT_ISO=
-declare ORIG_PWD=$PWD
-declare DO_UPGRADES=1
while getopts "i:o:p:" opt; do
case $opt in
@@ -86,7 +89,7 @@ while getopts "i:o:p:" opt; do
OUTPUT_ISO=$OPTARG
;;
p)
- PATCH_FILES+=($OPTARG)
+ PATCH_FILES+=("$OPTARG")
;;
*)
usage
@@ -95,183 +98,162 @@ while getopts "i:o:p:" opt; do
esac
done
-if [ -z "$INPUT_ISO" -o -z "$OUTPUT_ISO" ]; then
+if [ -z "$INPUT_ISO" ] || [ -z "$OUTPUT_ISO" ]; then
usage
exit 1
fi
-if [ ! -f ${INPUT_ISO} ]; then
- echo "Input file does not exist: ${INPUT_ISO}"
+if [ ! -f "${INPUT_ISO}" ]; then
+ echo "ERROR: Input file does not exist: ${INPUT_ISO}"
exit 1
fi
-if [ -f ${OUTPUT_ISO} ]; then
- echo "Output file already exists: ${OUTPUT_ISO}"
+if [ -f "${OUTPUT_ISO}" ]; then
+ echo "ERROR: Output file already exists: ${OUTPUT_ISO}"
exit 1
fi
for PATCH in "${PATCH_FILES[@]}";
do
- if [ ! -f ${PATCH} ]; then
- echo "Patch file dos not exists: ${PATCH}"
+ if [ ! -f "${PATCH}" ]; then
+ echo "ERROR: Patch file dos not exists: ${PATCH}"
exit 1
fi
- if [[ ! ${PATCH} =~ \.patch$ ]]; then
- echo "Specified file ${PATCH} does not have .patch extension"
+ if [[ ! "${PATCH}" =~ \.patch$ ]]; then
+ echo "ERROR: Specified file ${PATCH} does not have .patch extension"
exit 1
fi
done
shift $((OPTIND-1))
-declare MNTDIR=
declare BUILDDIR=
function check_requirements {
+ # Next to each requirement is the deb package which provides the command listed.
+ # Run "apt install ..."
# Declare "require reqA or reqB" as "reqA__reqB"
local -a required_utils=(
- rsync
- mkisofs
- isohybrid
- implantisomd5
- ostree
- xmllint__python3
+ 7z # p7zip-full
+ mkisofs # genisoimage
+ isohybrid # syslinux-utils
+ implantisomd5 # isomd5sum
+ ostree # ostree
+ xmllint__python3 # libxml2-utils
)
- if [ $UID -ne 0 ]; then
- # If running as non-root user, additional utils are required
- required_utils+=(
- guestmount
- guestunmount
- )
- fi
local -i missing=0
local reqA
local reqB
- for req in ${required_utils[@]}; do
+ for req in "${required_utils[@]}"; do
if [[ "$req" = *"__"* ]]; then
reqA="${req%__*}" # select everything before "__"
reqB="${req#*__}" # select everything after "__"
- if ! (which ${reqA} &>/dev/null) && ! (which ${reqB} &>/dev/null); then
+ if ! (which "${reqA}" &>/dev/null) && ! (which "${reqB}" &>/dev/null); then
echo "Unable to find required utility: either ${reqA} or ${reqB}" >&2
- let missing++
+ missing=$(( missing+1 ))
fi
else
- if ! (which ${req} &>/dev/null); then
+ if ! (which "${req}" &>/dev/null); then
echo "Unable to find required utility: ${req}" >&2
- let missing++
+ missing=$(( missing+1 ))
fi
fi
done
- if [ ${missing} -gt 0 ]; then
- echo "One or more required utilities are missing. Aborting..." >&2
+ if [ "${missing}" -gt 0 ]; then
+ echo "ERROR: One or more required utilities are missing" >&2
exit 1
fi
}
-function mount_iso {
- if [ $UID -eq 0 ]; then
- # Mount the ISO
- mount -o loop ${INPUT_ISO} ${MNTDIR}
- if [ $? -ne 0 ]; then
- echo "Failed to mount ${INPUT_ISO}" >&2
- exit 1
- fi
- else
- # As non-root user, mount the ISO using guestmount
- guestmount -a ${INPUT_ISO} -m /dev/sda1 --ro ${MNTDIR}
- rc=$?
- if [ $rc -ne 0 ]; then
- # Add a retry
- echo "Call to guestmount failed with rc=$rc. Retrying once..."
-
- guestmount -a ${INPUT_ISO} -m /dev/sda1 --ro ${MNTDIR}
- rc=$?
- if [ $rc -ne 0 ]; then
- echo "Call to guestmount failed with rc=$rc. Aborting..."
- exit $rc
- fi
- fi
- fi
-}
-
-function unmount_iso {
- if [ $UID -eq 0 ]; then
- umount ${MNTDIR}
- else
- guestunmount ${MNTDIR}
- fi
- rmdir ${MNTDIR}
-}
-
function cleanup() {
- if [ -n "$MNTDIR" -a -d "$MNTDIR" ]; then
- unmount_iso
+ # Delete temporary build directory
+ if [ -n "$BUILDDIR" ] && [ -d "$BUILDDIR" ]; then
+ chmod -R +w "$BUILDDIR"
+ \rm -rf "$BUILDDIR"
fi
-
- if [ -n "$BUILDDIR" -a -d "$BUILDDIR" ]; then
- chmod -R +w $BUILDDIR
- \rm -rf $BUILDDIR
- fi
-
}
check_requirements
+# Run cleanup() when finishing/interrupting execution
trap cleanup EXIT
-MNTDIR=$(mktemp -d -p $PWD patchiso_mnt_XXXXXX)
-if [ -z "${MNTDIR}" -o ! -d ${MNTDIR} ]; then
- echo "Failed to create mntdir. Aborting..."
- exit $rc
+# Create temporary build directory
+BUILDDIR=$(mktemp -d -p "$PWD" patchiso_build_XXXXXX)
+if [ -z "${BUILDDIR}" ] || [ ! -d "${BUILDDIR}" ]; then
+ echo "ERROR: Failed to create temporary build directory"
+ exit 1
fi
-BUILDDIR=$(mktemp -d -p $PWD patchiso_build_XXXXXX)
-if [ -z "${BUILDDIR}" -o ! -d ${BUILDDIR} ]; then
- echo "Failed to create builddir. Aborting..."
- exit $rc
-fi
-
-# Mount the ISO
-mount_iso
-
-rsync -a --exclude 'ostree_repo' ${MNTDIR}/ ${BUILDDIR}/
-rc=$?
-if [ $rc -ne 0 ]; then
- echo "Call to rsync ISO content. Aborting..."
- exit $rc
-fi
-
-unmount_iso
-
# Fix for permission denied if not running as root
-chmod +w ${BUILDDIR}
-chmod -R +w ${BUILDDIR}/isolinux
+chmod +w "${BUILDDIR}"
+if [ -d "${BUILDDIR}/isolinux" ]; then
+ chmod -R +w "${BUILDDIR}/isolinux"
+fi
-# Create the directory where metadata will be stored
-mkdir -p ${BUILDDIR}/patches
-chmod -R +w ${BUILDDIR}/patches
+# Erase current patch metadata from ISO if it exists
+# This way, this script can be used on pre-patched ISOs
+if [ -d "${BUILDDIR}/patches" ]; then
+ rm -rf "${BUILDDIR}/patches"
+fi
-echo "Copying only the latest commit from ostree_repo..."
-ostree --repo=${BUILDDIR}/ostree_repo init --mode=archive-z2
-ostree --repo=${BUILDDIR}/ostree_repo pull-local --depth=0 ${OSTREE_REPO} starlingx
-ostree --repo=${BUILDDIR}/ostree_repo summary --update
-echo "Updated iso ostree commit:"
-ostree --repo=${BUILDDIR}/ostree_repo log starlingx
+# Create the directory where patch metadata will be stored
+mkdir -p "${BUILDDIR}/patches"
+chmod -R +w "${BUILDDIR}/patches"
-echo "Extracting patch metadata"
+echo "Extracting Input ISO contents..."
+7z x "${INPUT_ISO}" -o"${BUILDDIR}" 1>/dev/null
+
+# Delete boot directory. It will be re-created when packing the output ISO
+if [ -d "${BUILDDIR}/[BOOT]" ]; then
+ rm -rf "${BUILDDIR}/[BOOT]"
+fi
+
+echo "Extracting patch metadata..."
for PATCH in "${PATCH_FILES[@]}";
do
- extract_metadata $PATCH
+ extract_metadata "$PATCH" "${BUILDDIR}/ostree_repo"
done
+echo "Original ostree repo history:"
+echo "----------------------------------------------------------------------------------"
+ostree --repo="${BUILDDIR}/ostree_repo" log starlingx
+echo "----------------------------------------------------------------------------------"
+
+echo "Clean up all commits from ostree repo except the latest one..."
+
+function clean_ostree(){
+ # Create array of ostree commit IDs
+ mapfile -t ostree_commits < <(ostree --repo="${BUILDDIR}/ostree_repo" log starlingx | grep '^commit' | cut -d ' ' -f 2)
+
+ # Delete each commit except the latest one
+ for ostree_commit in "${ostree_commits[@]:1}"; do
+ echo "Removing commit: ${ostree_commit}"
+ ostree --repo="${BUILDDIR}/ostree_repo" prune --delete-commit="${ostree_commit}"
+ done
+
+ ostree --repo="${BUILDDIR}/ostree_repo" summary --update
+}
+
+if ! clean_ostree; then
+ echo "ERROR: Failed to clean ostree repo!"
+ exit 1
+fi
+
+echo "Output ISO ostree history:"
+echo "----------------------------------------------------------------------------------"
+ostree --repo="${BUILDDIR}/ostree_repo" log starlingx
+echo "----------------------------------------------------------------------------------"
+
echo "Packing iso..."
+
# get the install label
ISO_LABEL=$(grep -ri instiso "${BUILDDIR}"/isolinux/isolinux.cfg | head -1 | xargs -n1 | awk -F= /instiso/'{print $2}')
if [ -z "${ISO_LABEL}" ] ; then
@@ -279,30 +261,39 @@ if [ -z "${ISO_LABEL}" ] ; then
fi
echo "ISO Label: ${ISO_LABEL}"
-# Repack the ISO
-mkisofs -o "${OUTPUT_ISO}" \
- -A "${ISO_LABEL}" -V "${ISO_LABEL}" \
- -quiet -U -J -joliet-long -r -iso-level 2 \
- -b isolinux/isolinux.bin -c isolinux/boot.cat -no-emul-boot \
- -boot-load-size 4 -boot-info-table \
- -eltorito-alt-boot \
- -e efi.img \
- -no-emul-boot \
- "${BUILDDIR}"
+function pack_iso(){
+ # Repack the ISO
+ mkisofs -o "${OUTPUT_ISO}" \
+ -A "${ISO_LABEL}" -V "${ISO_LABEL}" \
+ -quiet -U -J -joliet-long -r -iso-level 2 \
+ -b isolinux/isolinux.bin -c isolinux/boot.cat -no-emul-boot \
+ -boot-load-size 4 -boot-info-table \
+ -eltorito-alt-boot \
+ -e efi.img \
+ -no-emul-boot \
+ "${BUILDDIR}"
-isohybrid --uefi ${OUTPUT_ISO}
-implantisomd5 ${OUTPUT_ISO}
-
-# Sign the .iso with the developer private key
-openssl dgst -sha256 \
- -sign ${MY_REPO}/build-tools/signing/dev-private-key.pem \
- -binary \
- -out ${OUTPUT_ISO/%.iso/.sig} \
- ${OUTPUT_ISO}
-rc=$?
-if [ $rc -ne 0 ]; then
- echo "Call to $(basename ${SETUP_PATCH_REPO}) failed with rc=$rc. Aborting..."
- exit $rc
+ isohybrid --uefi "${OUTPUT_ISO}"
+ implantisomd5 "${OUTPUT_ISO}"
+}
+if ! pack_iso; then
+ echo "ERROR: Failed to build output ISO!"
+ exit 1
fi
-echo "Patched ISO: ${OUTPUT_ISO}"
+echo "Signing the .iso with the developer private key.."
+
+function sign_iso(){
+ openssl dgst -sha256 \
+ -sign "${MY_REPO}/build-tools/signing/dev-private-key.pem" \
+ -binary \
+ -out "${OUTPUT_ISO/%.iso/.sig}" \
+ "${OUTPUT_ISO}"
+}
+if ! sign_iso; then
+ echo "ERROR: Failed to sign ISO!"
+ exit 1
+fi
+
+echo ""
+echo "Output ISO: $(realpath "${OUTPUT_ISO}")"