3e7d239e2b
This commit reverts a change made in [1].
The input ostree repo should be the one in DEPLOY_DIR,
not the one in the Input ISO.
This is because the script that generates patches has a
"reuse initramfs" feature, which modifies the ostree commit before creating the patch and saves this change on DEPLOY_DIR as well [2],
but not in the "raw" ISO (the one created by "build-image --keep").
The requirement for the DEPLOY_DIR env variable
has been re-introduced.
Note that, instead of pulling just the latest ostree commit,
all commits are pulled and then the old ones (all except the latest)
are deleted from the repo. By doing this, their respective
"tombstone" files are created, which are internal ostree flags
stating that those commits are missing *intentionally*.
Without them, some ostree operations throw errors
for the missing commits.
Ref:
[1] https://review.opendev.org/c/starlingx/root/+/923771
[2] d963ae7f51/sw-patch/cgcs-patch/cgcs_make_patch/make_patch.py (L794-L804)
Test Plan:
pass - create pre-patched ISO by running script in LAT container
pass - install pre-patched ISO
pass - "sw-patch query" shows previous patches
pass - output ostree repo has tombstones for deleted ostree commits
Story: 2011098
Task: 51227
Change-Id: If2ed17220484d6900976d6413983791c50f53516
Signed-off-by: Leonardo Fagundes Luz Serrano <Leonardo.FagundesLuzSerrano@windriver.com>
352 lines
10 KiB
Bash
Executable File
352 lines
10 KiB
Bash
Executable File
#!/bin/bash -e
|
|
#
|
|
# Copyright (c) 2024 Wind River Systems, Inc.
|
|
#
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
#
|
|
# Script to generate pre-patched ISOs.
|
|
#
|
|
|
|
BUILD_TOOLS_DIR="$(dirname "$0")"
|
|
|
|
# shellcheck source="./build-tools/image-utils.sh"
|
|
source "${BUILD_TOOLS_DIR}/image-utils.sh"
|
|
|
|
usage="
|
|
Script to generate pre-patched ISOs.
|
|
|
|
Inputs:
|
|
- an ISO
|
|
- one or more patches
|
|
- ostree repo (assumed to be in \${DEPLOY_DIR}/ostree_repo/
|
|
or \${STX_BUILD_HOME}/localdisk/deploy/ostree_repo/)
|
|
|
|
It generates as output an ISO with the following changes:
|
|
|
|
- Contains only the latest ostree commit from the input ostree repo
|
|
- 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 to list the patches
|
|
(each of them refers to one of the older commits in the ostree repo)
|
|
|
|
The intent is for the system to have record of the patches that are
|
|
already pre-installed in the system.
|
|
|
|
Usage:
|
|
$(basename "$0") -i <input filename.iso> -o <output filename.iso> [ -p ] <patch> ...
|
|
-i <file>: Specify input ISO file
|
|
-o <file>: Specify output ISO file
|
|
-p <file>: Patch files. Can be called multiple times.
|
|
|
|
Attention:
|
|
- Either the DEPLOY_DIR or the STX_BUILD_HOME env variable must be defined.
|
|
It's used to find the input ostree repo.
|
|
|
|
"
|
|
|
|
function usage() {
|
|
echo "${usage}"
|
|
}
|
|
|
|
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=
|
|
declare BUILDDIR=
|
|
|
|
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))
|
|
|
|
|
|
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
|
|
|
|
# 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
|
|
|
|
# Define DEPLOY_DIR, which is the directory containing the input ostree repo
|
|
if [ -z "${DEPLOY_DIR}" ]; then
|
|
if [ -n "${STX_BUILD_HOME}" ]; then
|
|
DEPLOY_DIR="${STX_BUILD_HOME}/localdisk/deploy"
|
|
else
|
|
echo "ERROR: Please define either the DEPLOY_DIR or the STX_BUILD_HOME env variables."
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
# 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
|
|
|
|
echo "Extracting Input ISO contents (except ostree repo)..."
|
|
if ! 7z x "${INPUT_ISO}" -o"${BUILDDIR}" -x\!ostree_repo 1>/dev/null ; then
|
|
echo "ERROR: Failed to extract ISO contents"
|
|
exit 1
|
|
fi
|
|
|
|
# Deleting '[BOOT]' directory. It will be re-created when packing the output ISO.
|
|
if [ -d "${BUILDDIR}/[BOOT]" ]; then
|
|
rm -rf "${BUILDDIR}/[BOOT]"
|
|
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
|
|
|
|
echo "List contents extracted from Input ISO (after adjustments):"
|
|
ls -lh "${BUILDDIR}"
|
|
|
|
# Create the directory where patch metadata will be stored
|
|
mkdir -p "${BUILDDIR}/patches"
|
|
chmod -R +w "${BUILDDIR}/patches"
|
|
|
|
echo "Create a copy of the input ostree repo in the temp build directory..."
|
|
echo "Input ostree repo: ${DEPLOY_DIR}/ostree_repo/"
|
|
ostree --repo="${BUILDDIR}/ostree_repo" init --mode=archive-z2
|
|
ostree --repo="${BUILDDIR}/ostree_repo" pull-local --depth=-1 "${DEPLOY_DIR}/ostree_repo/" starlingx
|
|
ostree --repo="${BUILDDIR}/ostree_repo" summary --update
|
|
|
|
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}")"
|