From fd795d8daeab0fb2746dc98b2a1a6609de4c5a25 Mon Sep 17 00:00:00 2001 From: Sergii Dmytruk Date: Sun, 12 Apr 2026 00:48:19 +0300 Subject: [PATCH 1/7] configs/config.msi_*: bump to v1.1.7-rc1 and v0.9.5-rc1 Upstream-Status: Inappropriate [Dasharo downstream] Change-Id: Iedbcfcbca5c048774ae66cd4cf4566500cd615e8 Signed-off-by: Sergii Dmytruk --- configs/config.msi_ms7d25_ddr4 | 6 +++--- configs/config.msi_ms7d25_ddr5 | 6 +++--- configs/config.msi_ms7e06_ddr4 | 6 +++--- configs/config.msi_ms7e06_ddr5 | 6 +++--- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/configs/config.msi_ms7d25_ddr4 b/configs/config.msi_ms7d25_ddr4 index 9782b981fa3..2dae927a92d 100644 --- a/configs/config.msi_ms7d25_ddr4 +++ b/configs/config.msi_ms7d25_ddr4 @@ -1,4 +1,4 @@ -CONFIG_LOCALVERSION="v1.1.5" +CONFIG_LOCALVERSION="v1.1.7-rc1" CONFIG_OPTION_BACKEND_NONE=y CONFIG_VENDOR_MSI=y CONFIG_ONBOARD_VGA_IS_PRIMARY=y @@ -26,8 +26,8 @@ CONFIG_PCIEXP_LANE_ERR_STAT_CLEAR=y CONFIG_DRIVERS_EFI_VARIABLE_STORE=y CONFIG_DRIVERS_EFI_FW_INFO=y CONFIG_DRIVERS_EFI_MAIN_FW_GUID="0f3e8ba6-19d3-461b-bbbc-8b5689fe8097" -CONFIG_DRIVERS_EFI_MAIN_FW_VERSION=0x01010580 -CONFIG_DRIVERS_EFI_MAIN_FW_LSV=0x01010580 +CONFIG_DRIVERS_EFI_MAIN_FW_VERSION=0x01010701 +CONFIG_DRIVERS_EFI_MAIN_FW_LSV=0x01010701 CONFIG_DRIVERS_EFI_UPDATE_CAPSULES=y CONFIG_TPM2=y CONFIG_BOOTMEDIA_LOCK_CONTROLLER=y diff --git a/configs/config.msi_ms7d25_ddr5 b/configs/config.msi_ms7d25_ddr5 index aaf9e7014ab..6117e2225fb 100644 --- a/configs/config.msi_ms7d25_ddr5 +++ b/configs/config.msi_ms7d25_ddr5 @@ -1,4 +1,4 @@ -CONFIG_LOCALVERSION="v1.1.5" +CONFIG_LOCALVERSION="v1.1.7-rc1" CONFIG_OPTION_BACKEND_NONE=y CONFIG_VENDOR_MSI=y CONFIG_ONBOARD_VGA_IS_PRIMARY=y @@ -26,8 +26,8 @@ CONFIG_PCIEXP_LANE_ERR_STAT_CLEAR=y CONFIG_DRIVERS_EFI_VARIABLE_STORE=y CONFIG_DRIVERS_EFI_FW_INFO=y CONFIG_DRIVERS_EFI_MAIN_FW_GUID="119059ec-1a5e-4844-b2e6-6f573b257570" -CONFIG_DRIVERS_EFI_MAIN_FW_VERSION=0x01010580 -CONFIG_DRIVERS_EFI_MAIN_FW_LSV=0x01010580 +CONFIG_DRIVERS_EFI_MAIN_FW_VERSION=0x01010701 +CONFIG_DRIVERS_EFI_MAIN_FW_LSV=0x01010701 CONFIG_DRIVERS_EFI_UPDATE_CAPSULES=y CONFIG_TPM2=y CONFIG_BOOTMEDIA_LOCK_CONTROLLER=y diff --git a/configs/config.msi_ms7e06_ddr4 b/configs/config.msi_ms7e06_ddr4 index ba972d6713e..dd451b27919 100644 --- a/configs/config.msi_ms7e06_ddr4 +++ b/configs/config.msi_ms7e06_ddr4 @@ -1,4 +1,4 @@ -CONFIG_LOCALVERSION="v0.9.3" +CONFIG_LOCALVERSION="v0.9.5-rc1" CONFIG_OPTION_BACKEND_NONE=y CONFIG_VENDOR_MSI=y CONFIG_ONBOARD_VGA_IS_PRIMARY=y @@ -26,8 +26,8 @@ CONFIG_PCIEXP_LANE_ERR_STAT_CLEAR=y CONFIG_DRIVERS_EFI_VARIABLE_STORE=y CONFIG_DRIVERS_EFI_FW_INFO=y CONFIG_DRIVERS_EFI_MAIN_FW_GUID="91e6522e-25e1-4543-9b38-02e2aeafc9ed" -CONFIG_DRIVERS_EFI_MAIN_FW_VERSION=0x00090380 -CONFIG_DRIVERS_EFI_MAIN_FW_LSV=0x00090380 +CONFIG_DRIVERS_EFI_MAIN_FW_VERSION=0x00090501 +CONFIG_DRIVERS_EFI_MAIN_FW_LSV=0x00090501 CONFIG_DRIVERS_EFI_UPDATE_CAPSULES=y CONFIG_TPM2=y CONFIG_BOOTMEDIA_LOCK_CONTROLLER=y diff --git a/configs/config.msi_ms7e06_ddr5 b/configs/config.msi_ms7e06_ddr5 index c7a4deefc0f..4a773eef439 100644 --- a/configs/config.msi_ms7e06_ddr5 +++ b/configs/config.msi_ms7e06_ddr5 @@ -1,4 +1,4 @@ -CONFIG_LOCALVERSION="v0.9.3" +CONFIG_LOCALVERSION="v0.9.5-rc1" CONFIG_OPTION_BACKEND_NONE=y CONFIG_VENDOR_MSI=y CONFIG_ONBOARD_VGA_IS_PRIMARY=y @@ -26,8 +26,8 @@ CONFIG_PCIEXP_LANE_ERR_STAT_CLEAR=y CONFIG_DRIVERS_EFI_VARIABLE_STORE=y CONFIG_DRIVERS_EFI_FW_INFO=y CONFIG_DRIVERS_EFI_MAIN_FW_GUID="58301b3d-5b6d-4562-9ec1-fbce40f15253" -CONFIG_DRIVERS_EFI_MAIN_FW_VERSION=0x00090380 -CONFIG_DRIVERS_EFI_MAIN_FW_LSV=0x00090380 +CONFIG_DRIVERS_EFI_MAIN_FW_VERSION=0x00090501 +CONFIG_DRIVERS_EFI_MAIN_FW_LSV=0x00090501 CONFIG_DRIVERS_EFI_UPDATE_CAPSULES=y CONFIG_TPM2=y CONFIG_BOOTMEDIA_LOCK_CONTROLLER=y From d179e81e27ec7b66cc9b0ba3554df5897bad6f34 Mon Sep 17 00:00:00 2001 From: Sergii Dmytruk Date: Sun, 12 Apr 2026 21:00:55 +0300 Subject: [PATCH 2/7] payloads/edk2: optionally copy CapsuleRootKey.inc If CapsuleRootKey.inc exists and CONFIG_EDK2_CAPSULES_V2 is set, copy the file to EDK. This needs to be done as part of coreboot's build process because EDK's worktree doesn't exist right after cloning coreboot and there is no way to initialize it without building coreboot. This makes it impossible to provision EDK's key before the build without coreboot knowing about it at some level. Also reset DasharoPayloadPkg/CapsuleRootKey.inf in EDK if CONFIG_EDK2_CAPSULES_V2 is enabled, like it's already done for logos. Not adding the file to .gitignore so it's more visible to the user when present. Change-Id: I8b557c4ab239d61a5cef01928fda13b8417d54cb Upstream-Status: Inappropriate [Dasharo downstream] Signed-off-by: Sergii Dmytruk --- payloads/external/edk2/Makefile | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/payloads/external/edk2/Makefile b/payloads/external/edk2/Makefile index 3c39427d014..f1cea1be1ec 100644 --- a/payloads/external/edk2/Makefile +++ b/payloads/external/edk2/Makefile @@ -499,6 +499,7 @@ $(EDK2_PATH): $(WORKSPACE) git checkout -- MdeModulePkg/Logo/Logo.bmp > /dev/null 2>&1 || true; \ if [ "$(CONFIG_EDK2_CAPSULES_V2)" = y ]; then \ git checkout -- DasharoPayloadPkg/CapsuleSplashDxe/Logo.bmp > /dev/null 2>&1 || true; \ + git checkout -- DasharoPayloadPkg/CapsuleRootKey.inc > /dev/null 2>&1 || true; \ fi; \ if [ -e $(PAYLOAD_NAME)/ShimLayer/UniversalPayload.o ]; then \ rm $(PAYLOAD_NAME)/ShimLayer/UniversalPayload.o; \ @@ -537,6 +538,11 @@ logo: $(EDK2_PATH) esac \ fi +root_key: $(EDK2_PATH) + if [ "$(CONFIG_EDK2_CAPSULES_V2)" = y ] && [ -f CapsuleRootKey.inc ]; then \ + cp CapsuleRootKey.inc $(EDK2_PATH)/DasharoPayloadPkg/CapsuleRootKey.inc; \ + fi + gop_driver: $(EDK2_PATH) if [ -n "$(CONFIG_EDK2_GOP_DRIVER)" ]; then \ echo "Using GOP driver $(CONFIG_EDK2_GOP_FILE)"; \ @@ -600,7 +606,7 @@ print: -e 's/s /Build: Silent/' \ -e 's/t /Toolchain: /' -prep: $(EDK2_PATH) $(EDK2_PLATFORMS_PATH) clean checktools logo gop_driver lan_rom ipxe_rom +prep: $(EDK2_PATH) $(EDK2_PLATFORMS_PATH) clean checktools logo root_key gop_driver lan_rom ipxe_rom cd $(WORKSPACE); \ source $(EDK2_PATH)/edksetup.sh; \ unset CC; $(MAKE) -C $(EDK2_PATH)/BaseTools 2>&1; \ @@ -660,4 +666,4 @@ clean: distclean: rm -rf $(WORKSPACE) -.PHONY: $(EDK2_PATH) checktools logo $(PAYLOAD_NAME) UniversalPayload clean distclean +.PHONY: $(EDK2_PATH) checktools logo root_key $(PAYLOAD_NAME) UniversalPayload clean distclean From 67595c3ef801dc973931636a9eb1177bea5d8d7f Mon Sep 17 00:00:00 2001 From: Sergii Dmytruk Date: Thu, 16 Apr 2026 01:11:02 +0300 Subject: [PATCH 3/7] capsule.sh: remove a debug print Change-Id: Ia9462cc4997dd04a17bc43d41fd3f8a08d318341 Upstream-Status: Inappropriate [Dasharo downstream] Signed-off-by: Sergii Dmytruk --- capsule.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/capsule.sh b/capsule.sh index 14943639a28..0799969fd0f 100755 --- a/capsule.sh +++ b/capsule.sh @@ -285,7 +285,6 @@ EOF local opt_sub_cert=$sub_cert local opt_sign_cert=$sign_cert if [ "${CONFIG_EDK2_CAPSULES_V2:-n}${CONFIG_EDK2_CAPSULES_V2_TRANSITION:-n}" = yn ]; then - echo "nested capsules" # The inner capsule is always signed with the test key. Not signing it # at all doesn't work because FmpDxe doesn't accept unsigned payloads at # least due to Image->AuthInfo.Hdr.wRevision check in From 9f40c7b74e98aadf0cc4c46dee268532cf15fc24 Mon Sep 17 00:00:00 2001 From: Sergii Dmytruk Date: Fri, 17 Apr 2026 01:17:18 +0300 Subject: [PATCH 4/7] .github/workflows/checks.yml: consistently use actions/checkout@v4 There was one outlier in this file. Upstream-Status: Inappropriate [Dasharo downstream] Change-Id: I3334d8eccaf64c57fc37580dce3d057938795427 Signed-off-by: Sergii Dmytruk --- .github/workflows/checks.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 9142e82af46..2a8761e099e 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -7,7 +7,7 @@ jobs: check-upstream-status: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: # Checkout pull request HEAD commit instead of merge commit # See: https://github.com/actions/checkout#checkout-pull-request-head-commit-instead-of-merge-commit From e72877ebc25579a978d268ab52b829349c211a0b Mon Sep 17 00:00:00 2001 From: Sergii Dmytruk Date: Fri, 17 Apr 2026 01:21:13 +0300 Subject: [PATCH 5/7] .github/workflows/tag-check.yml: check capsule version/generation updates Check that `CONFIG_EDK2_CAPSULES_V2=y` is not added to `CONFIG_DRIVERS_EFI_UPDATE_CAPSULES=y` without `CONFIG_EDK2_CAPSULES_V2_TRANSITION=y`. Check that `CONFIG_EDK2_CAPSULES_V2_TRANSITION=y` doesn't live longer than one release cycle. Check that `CONFIG_EDK2_CAPSULES_V2_TRANSITION=y` is not added to `CONFIG_EDK2_CAPSULES_V2=y`. Check that `CONFIG_EDK2_CAPSULES_V2_TRANSITION=y` is not removed. Change-Id: I24a1fd41864983fff3f9dfa717a0e4a7505fecac Upstream-Status: Inappropriate [Dasharo downstream] Signed-off-by: Sergii Dmytruk --- .github/scripts/capsule-versioning-check.sh | 116 ++++++++++++++++++++ .github/workflows/tag-check.yml | 3 + 2 files changed, 119 insertions(+) create mode 100755 .github/scripts/capsule-versioning-check.sh diff --git a/.github/scripts/capsule-versioning-check.sh b/.github/scripts/capsule-versioning-check.sh new file mode 100755 index 00000000000..02e7694c37a --- /dev/null +++ b/.github/scripts/capsule-versioning-check.sh @@ -0,0 +1,116 @@ +#!/bin/bash + +# This script verifies that defconfig options related to capsule versions are +# properly updated between releases. It expects tag of the new release/RC as +# its only parameter. + +set -eu + +# note that this works only for up to one release back in history +function get_previous_release() { + local tag=$1 + + local tag_prefix + tag_prefix=$(echo "$tag" | grep -o '.*_v[0-9]') + if [ "$tag" = "$tag_prefix" ]; then + echo "error: failed to determine prefix of tag: '$tag'." 1>&2 + exit 1 + fi + tag_prefix=${tag_prefix::-1} + + local pos + if [[ $tag =~ .*-rc[0-9]+$ ]]; then + # last non-RC release is the predecessor + pos=1 + else + # the previous non-RC release is the predecessor + pos=2 + fi + + git tag --list "$tag_prefix*" --sort=-version:refname | + grep -v '.*-rc[0-9]\+$' | + sed -n "${pos}p" +} + +function uses_v1_capsules() { + local config=$1 + echo "$config" | grep -qFx "CONFIG_DRIVERS_EFI_UPDATE_CAPSULES=y" && + ! echo "$config" | grep -qFx "CONFIG_EDK2_CAPSULES_V2=y" +} + +function uses_v2_capsules() { + local config=$1 + echo "$config" | grep -qFx "CONFIG_EDK2_CAPSULES_V2=y" +} + +function transitions_to_v2() { + local config=$1 + echo "$config" | grep -qFx "CONFIG_EDK2_CAPSULES_V2_TRANSITION=y" +} + +if [ $# -ne 1 ]; then + echo "Usage: $(basename "$0") -h|--help|new-tag" + exit 1 +fi + +if [ $1 = "-h" ] || [ $1 = "--help" ]; then + echo "Usage: $(basename "$0") -h|--help|new-tag" + exit 0 +fi + +new_tag=$1 + +old_tag=$(get_previous_release "$new_tag") +if [ -z "$old_tag" ]; then + echo "warning: no previous release for '$new_tag', skipping checks." 1>&2 + exit 0 +fi +echo "info: determined '$old_tag' to be the predecessor of '$new_tag'." + +fail=0 + +# check all configs to not figure out mapping tag name to a config name, some +# false-positives are possible due to overlapping releases, but that should be +# rare and obvious to the user +mapfile -t configs < <(grep -l 'EFI_UPDATE_CAPSULE' configs/config.*) +for config in "${configs[@]}"; do + new_config=$(git show "$new_tag:$config") + old_config=$(git show "$old_tag:$config") + + # check that `CONFIG_EDK2_CAPSULES_V2=y` is not added to + # `CONFIG_DRIVERS_EFI_UPDATE_CAPSULES=y` without + # `CONFIG_EDK2_CAPSULES_V2_TRANSITION=y` + if uses_v1_capsules "$old_config" && + uses_v2_capsules "$new_config" && + ! transitions_to_v2 "$new_config"; then + echo "error: '$config' switches to V2 without CONFIG_EDK2_CAPSULES_V2_TRANSITION=y" 1>&2 + fail=1 + fi + + # check that `CONFIG_EDK2_CAPSULES_V2_TRANSITION=y` doesn't live longer than + # one release cycle + if transitions_to_v2 "$old_config" && + transitions_to_v2 "$new_config"; then + echo "error: '$config' didn't remove CONFIG_EDK2_CAPSULES_V2_TRANSITION=y after a release" 1>&2 + fail=1 + fi + + # check that `CONFIG_EDK2_CAPSULES_V2_TRANSITION=y` is not added to + # `CONFIG_EDK2_CAPSULES_V2=y` + if uses_v2_capsules "$old_config" && + uses_v2_capsules "$new_config" && + transitions_to_v2 "$new_config"; then + echo "error: '$config' adds CONFIG_EDK2_CAPSULES_V2_TRANSITION=y to V2" 1>&2 + fail=1 + fi + + # check that `CONFIG_EDK2_CAPSULES_V2_TRANSITION=y` is not removed causing a + # downgrade to V2 + if uses_v2_capsules "$old_config" && + uses_v1_capsules "$new_config"; then + echo "error: '$config' removes CONFIG_EDK2_CAPSULES_V2=y" 1>&2 + fail=1 + fi +done + +exit "$fail" diff --git a/.github/workflows/tag-check.yml b/.github/workflows/tag-check.yml index dbd772c1836..e5aa24c3c03 100644 --- a/.github/workflows/tag-check.yml +++ b/.github/workflows/tag-check.yml @@ -46,3 +46,6 @@ jobs: echo "::error::Tag '${tag}' is NOT signed. Create it with: git tag -s" exit 1 fi + + - name: Verify update capsules support in defconfigs + run: .github/scripts/capsule-versioning-check.sh "${GITHUB_REF_NAME}" From 20fea379113389d101b804147f7075a3b0ef0e4c Mon Sep 17 00:00:00 2001 From: Sergii Dmytruk Date: Sat, 25 Apr 2026 20:00:09 +0300 Subject: [PATCH 6/7] capsule.sh: extract 3 helpers They will reused by upcoming changes. Change-Id: Ie81e82f402e4c171f957a9b53b1e40dc559d19a4 Upstream-Status: Inappropriate [Dasharo downstream] Signed-off-by: Sergii Dmytruk --- capsule.sh | 48 ++++++++++++++++++++++++++++++------------------ 1 file changed, 30 insertions(+), 18 deletions(-) diff --git a/capsule.sh b/capsule.sh index 0799969fd0f..7a6d00c48a3 100755 --- a/capsule.sh +++ b/capsule.sh @@ -358,6 +358,31 @@ EOF echo "Created the capsule at '$cap_file'" } +function assert_file_exists() { + local path=$1 + + if [ ! -f "$path" ]; then + die "File '$path' not found" + fi +} + +function assert_file_is_a_capsule() { + local path=$1 + + local fmp_guid_bytes_hex=edd5cb6d2de8444cbda17194199ad92a + if [ "$(xxd -l 16 -ps "$path")" != "$fmp_guid_bytes_hex" ]; then + die "'$path' is not an FMP capsule file" + fi +} + +function assert_command_exists() { + local cmd=$1 + + if ! command -v "$cmd" >/dev/null 2>&1; then + die "'$cmd' not found in PATH" + fi +} + function create_cabinet_subcommand() { if [ $# -ne 1 ]; then die "Incorrect number of input parameters specified: $# (expected: 1)" @@ -370,18 +395,9 @@ function create_cabinet_subcommand() { die "No input capsule specified" fi - if [ ! -f "$capsule" ]; then - die "File $capsule not found" - fi - - if ! command -v fwupdtool >/dev/null 2>&1; then - die "fwupdtool not found in PATH" - fi - - local fmp_guid_bytes_hex=edd5cb6d2de8444cbda17194199ad92a - if [ "$(xxd -l 16 -ps "$capsule")" != "$fmp_guid_bytes_hex" ]; then - die "'$capsule' is not an FMP capsule file" - fi + assert_file_exists "$capsule" + assert_file_is_a_capsule "$capsule" + assert_command_exists fwupdtool source_coreboot_config require_capsule_support @@ -571,9 +587,7 @@ function upload_lvfs_subcommand() { cabinet="$1" fi - if [ ! -f "$cabinet" ]; then - die "File '$cabinet' not found" - fi + assert_file_exists "$cabinet" if [ -z "$email" ]; then die "LVFS email is not set. Put LVFS_EMAIL into '$creds_file' or pass -e" @@ -582,9 +596,7 @@ function upload_lvfs_subcommand() { die "LVFS token is not set. Put LVFS_TOKEN into '$creds_file' or pass -t" fi - if ! command -v curl >/dev/null 2>&1; then - die "curl not found in PATH" - fi + assert_command_exists curl url="${base_url%/}/lvfs/upload/token" From 1df7e898525f0dd1b2e954f48aa0cf0f3789a483 Mon Sep 17 00:00:00 2001 From: Sergii Dmytruk Date: Sat, 25 Apr 2026 20:12:34 +0300 Subject: [PATCH 7/7] capsule.sh: add resign subcommand MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Takes a capsule and signing keys, unpacks the capsule, then packs it back under a (likely) different name. Two functions were borrowed from a script in OSFV. Change-Id: I23157aaeedb4e1fdcfb10c5a0235acd571aa72b4 Upstream-Status: Inappropriate [Dasharo downstream] Co-authored-by: Filip Gołaś Signed-off-by: Sergii Dmytruk --- capsule.sh | 105 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 104 insertions(+), 1 deletion(-) diff --git a/capsule.sh b/capsule.sh index 7a6d00c48a3..629a7414824 100755 --- a/capsule.sh +++ b/capsule.sh @@ -46,6 +46,11 @@ function print_usage() { echo ' -o subroot-certificate-file' echo ' -s signing-certificate-file' echo ' -b (the flag adds battery check DXE into the capsule)' + echo ' resign resign an existing capsule with a different key' + echo ' -t root-certificate-file' + echo ' -o subroot-certificate-file' + echo ' -s signing-certificate-file' + echo ' positional arguments: input-capsule output-capsule' echo ' create_cabinet create a fwupd cabinet (.cab) from a capsule' echo ' positional argument: capsule-file' echo ' upload_lvfs upload a cabinet (.cab) to LVFS, options' @@ -383,6 +388,104 @@ function assert_command_exists() { fi } +function decode_capsule() { + local tmp_dir=$1 + local capsule=$2 + local -n result=$3 + + "${edk_tools}/GenerateCapsule" --decode "$capsule" \ + --output "$tmp_dir/decoded" + + local json_file="$tmp_dir/decoded.json" + result["fw_version"]=$(jq -r '.Payloads[0].FwVersion' "$json_file") + result["guid"]=$(jq -r '.Payloads[0].Guid' "$json_file") + result["lowest_supported_version"]=$(jq -r '.Payloads[0].LowestSupportedVersion' "$json_file") + result["payload"]=$(jq -r '.Payloads[0].Payload' "$json_file") + + local drivers + drivers=$(ls -v "$tmp_dir"/decoded.EmbeddedDriver* 2>/dev/null | while read -r f; do + printf ' { "Driver": "%s" },\n' "$f" + done | sed '$s/,$//') + result["drivers"]="$drivers" +} + +function encode_capsule() { + local tmp_dir=$1 + local cap_out=$2 + local -n json_data=$3 + + cat > "$tmp_dir/cap.json" <