d8a917020f
patch-iso-debian script uses the mkisofs command
to generate an ISO. Some flags are used there to define
the El Torito configs, but one of these flags needs to be
updated in order to work in the LAT container.
Changes:
- Replaced the '-e' flag for '-eltorito-boot'
- Added '-eltorito-platform 0xEF' flag, following [1]
- Removed "-quiet" flag
- Added support for mkisofs backup option: xorrisofs
Note:
The mkisofs command available in the LAT container has
been customized in order to properly support El Torito
configs [2]. In order to execute patch-iso-debian outside
the LAT container, the developer can use cmds from the
xorriso package: either "xorrisofs" or "xorriso -as mkisofs" [3]
Ref:
[1] https://github.com/yoctoproject/poky/blob/master/scripts/lib/wic/plugins/source/isoimage-isohybrid.py#L424
[2] 19c6b9a324
[3] https://wiki.debian.org/RepackBootableISO
Test Plan: Generate and install pre-patched ISO produced
pass - Script ran inside LAT
pass - Script ran outside LAT
Story: 2011098
Task: 50217
Depends-On: https://review.opendev.org/c/starlingx/root/+/923771
Change-Id: I9c6b15b16385d3e2b4d385378211113d02438c23
Signed-off-by: Leonardo Fagundes Luz Serrano <Leonardo.FagundesLuzSerrano@windriver.com>
316 lines
9.2 KiB
Bash
Executable File
316 lines
9.2 KiB
Bash
Executable File
#!/bin/bash -e
|
|
#
|
|
# Copyright (c) 2024 Wind River Systems, Inc.
|
|
#
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
#
|
|
# 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.
|
|
#
|
|
|
|
BUILD_TOOLS_DIR="$(dirname "$0")"
|
|
|
|
# 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
|
|
MY_REPO="$(dirname "${BUILD_TOOLS_DIR}")"
|
|
fi
|
|
|
|
function usage() {
|
|
echo ""
|
|
echo "Usage: "
|
|
echo " $(basename "$0") -i <input filename.iso> -o <output filename.iso> [ -p ] <patch> ..."
|
|
echo " -i <file>: Specify input ISO file"
|
|
echo " -o <file>: Specify output ISO file"
|
|
echo " -p <file>: Patch files. You can call it multiple times."
|
|
echo ""
|
|
}
|
|
|
|
function extract_ostree_commit_from_metadata_xml() {
|
|
local XML_PATH=$1
|
|
local XPATH="//contents/ostree/commit1/commit"
|
|
|
|
# Check if xmllint is available. Otherwise, use python's xml standard lib
|
|
if (which xmllint &>/dev/null); then
|
|
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
|
|
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
|
|
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="$(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}"
|
|
fi
|
|
}
|
|
|
|
declare INPUT_ISO=
|
|
declare OUTPUT_ISO=
|
|
|
|
while getopts "i:o:p:" opt; do
|
|
case $opt in
|
|
i)
|
|
INPUT_ISO=$OPTARG
|
|
;;
|
|
o)
|
|
OUTPUT_ISO=$OPTARG
|
|
;;
|
|
p)
|
|
PATCH_FILES+=("$OPTARG")
|
|
;;
|
|
*)
|
|
usage
|
|
exit 1
|
|
;;
|
|
esac
|
|
done
|
|
|
|
if [ -z "$INPUT_ISO" ] || [ -z "$OUTPUT_ISO" ]; then
|
|
usage
|
|
exit 1
|
|
fi
|
|
|
|
if [ ! -f "${INPUT_ISO}" ]; then
|
|
echo "ERROR: Input file does not exist: ${INPUT_ISO}"
|
|
exit 1
|
|
fi
|
|
|
|
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 "ERROR: Patch file dos not exists: ${PATCH}"
|
|
exit 1
|
|
fi
|
|
|
|
if [[ ! "${PATCH}" =~ \.patch$ ]]; then
|
|
echo "ERROR: Specified file ${PATCH} does not have .patch extension"
|
|
exit 1
|
|
fi
|
|
done
|
|
|
|
shift $((OPTIND-1))
|
|
|
|
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=(
|
|
7z # p7zip-full
|
|
mkisofs__xorrisofs # genisoimage / xorriso
|
|
isohybrid # syslinux-utils
|
|
implantisomd5 # isomd5sum
|
|
ostree # ostree
|
|
xmllint__python3 # libxml2-utils / python3
|
|
)
|
|
|
|
local -i missing=0
|
|
local reqA
|
|
local reqB
|
|
|
|
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
|
|
echo "Unable to find required utility: either ${reqA} or ${reqB}" >&2
|
|
missing=$(( missing+1 ))
|
|
fi
|
|
|
|
else
|
|
if ! (which "${req}" &>/dev/null); then
|
|
echo "Unable to find required utility: ${req}" >&2
|
|
missing=$(( missing+1 ))
|
|
fi
|
|
fi
|
|
done
|
|
|
|
if [ "${missing}" -gt 0 ]; then
|
|
echo "ERROR: One or more required utilities are missing" >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
function cleanup() {
|
|
# Delete temporary build directory
|
|
if [ -n "$BUILDDIR" ] && [ -d "$BUILDDIR" ]; then
|
|
chmod -R +w "$BUILDDIR"
|
|
\rm -rf "$BUILDDIR"
|
|
fi
|
|
}
|
|
|
|
check_requirements
|
|
|
|
# Run cleanup() when finishing/interrupting execution
|
|
trap cleanup EXIT
|
|
|
|
# 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
|
|
|
|
# Fix for permission denied if not running as root
|
|
chmod +w "${BUILDDIR}"
|
|
if [ -d "${BUILDDIR}/isolinux" ]; then
|
|
chmod -R +w "${BUILDDIR}/isolinux"
|
|
fi
|
|
|
|
# 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
|
|
|
|
# Create the directory where patch metadata will be stored
|
|
mkdir -p "${BUILDDIR}/patches"
|
|
chmod -R +w "${BUILDDIR}/patches"
|
|
|
|
echo "Extracting Input ISO contents..."
|
|
if ! 7z x "${INPUT_ISO}" -o"${BUILDDIR}" 1>/dev/null ; then
|
|
echo "ERROR: Extract ISO contents"
|
|
exit 1
|
|
fi
|
|
|
|
# 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" "${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
|
|
echo "Error: Failed to get iso install label"
|
|
fi
|
|
echo "ISO Label: ${ISO_LABEL}"
|
|
|
|
function pack_iso(){
|
|
if (which xorrisofs &>/dev/null); then
|
|
PACK_ISO_CMD="xorrisofs"
|
|
else
|
|
PACK_ISO_CMD="mkisofs"
|
|
fi
|
|
echo "ISO packaging command: ${PACK_ISO_CMD}"
|
|
|
|
# Command Reference:
|
|
# https://github.com/yoctoproject/poky/blob/master/scripts/lib/wic/plugins/source/isoimage-isohybrid.py#L419
|
|
|
|
${PACK_ISO_CMD} \
|
|
-V "${ISO_LABEL}" \
|
|
-o "${OUTPUT_ISO}" -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 \
|
|
-eltorito-platform "0xEF" -eltorito-boot "efi.img" \
|
|
-no-emul-boot "${BUILDDIR}"
|
|
|
|
isohybrid --uefi "${OUTPUT_ISO}"
|
|
implantisomd5 "${OUTPUT_ISO}"
|
|
}
|
|
if ! pack_iso; then
|
|
if [ "${PACK_ISO_CMD}" = "mkisofs" ]; then
|
|
echo "NOTE: mkisofs has a customization in the LAT container to provide the '-eltorito-boot' flag."
|
|
echo " To execute this script outside the LAT container, install the 'xorriso' package and run again."
|
|
fi
|
|
|
|
echo "ERROR: Failed to build output ISO!"
|
|
exit 1
|
|
fi
|
|
|
|
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}")"
|