Agent Skill · NVIDIA NIM

jetson-promote-image

Use to promote overlay files and built artifacts into the staged BSP image. Do NOT use to flash or build. Triggers: promote bsp image.

Provider: NVIDIA NIM Path in repo: skills/jetson-promote-image/SKILL.md

Skill body

Promote BSP Image

Purpose

Stage every Customize-* and Build output into bsp_image so it is ready for /jetson-flash-image. This is the promote leg of Deploy — it copies files, never flashes and never builds.

Prerequisites

Overview

This is the promote leg of Deploy — see ../../context/bsp-customization-workflow.md for the pipeline view. The two channels this skill walks are:

Channel Source Carrier Owner
Overlay tracker <source.root_path>/Linux_for_Tegra/ (git repo at HEAD) Customize-* outputs that don’t require a build (e.g. nvfancontrol.conf, nvpmodel.conf, BPMP DTB hand-edits) Customize customize-* skills commit here
Build manifest <source.root_path>/.build-manifest.yaml Rebuilt kernel Image, in-tree .ko, OOT .ko, NVIDIA DTBs Build jetson-build-source writes here

The skill computes the union of files to copy and writes each into <bsp_image.root_path>/Linux_for_Tegra/ with diff-aware skip-if-identical logic. When the copy pass touches the kernel Image or anything under rootfs/lib/modules/, it also rebuilds the initramfs via NVIDIA’s tools/l4t_update_initrd.sh so the freshly promoted kernel + modules ship in the initrd the bootloader actually loads. After it returns, bsp_image carries every Customize and Build output. The skill does not flash and does not modify the workspace.

When to invoke

Procedure

Resolve active target + paths

Resolve the active profile per the contract in ../../context/target-platform-contract.md.

Refuse and route in these cases:

Condition Refuse with
No active profile, or active: NA Route to /jetson-set-target or /jetson-init-target.
Profile lacks bsp_image: Route to /jetson-init-image.
<bsp_image.root_path>/Linux_for_Tegra/ missing Route to /jetson-init-image.
<source.root_path>/Linux_for_Tegra/ missing or not a git repo Route to /jetson-init-source.

Resolve paths:

Bind shell variables for the rest of the procedure:

LFT_SRC="<source.root_path>/Linux_for_Tegra"   # overlay tracker
LFT_DST="<bsp_image.root_path>/Linux_for_Tegra"
MANIFEST="<source.root_path>/.build-manifest.yaml"   # build outputs

Validate the two channels

The skill needs at least one channel populated. Refuse if the overlay tracker has uncommitted changes (status --porcelain non-empty), if $MANIFEST exists but doesn’t parse as YAML, or if both channels are empty. Records OVERLAY_HAS_COMMITS / OVERLAY_HEAD and MANIFEST_PRESENT for downstream steps.

See references/copy-pass-snippets.md for the shell snippet and refuse messages.

Verify build-source freshness

Refuse if .build-state.yaml shows any kernel-side repo in Source/bsp_sources/ dirty since the last /jetson-build-source — otherwise the copy pass would silently ship stale artifacts. Detection rules + shell snippet in references/build-source-freshness-gate.md. Records BUILD_FRESH=1.

Pre-promote collision check (overlay only)

When the overlay tracks a remote, refuse if upstream has commits not yet pulled. Skip gracefully when no remote is configured (the default git init empty tracker from jetson-init-source). Manifest channel has no git remote concept — this check is overlay-only. Records COLLISION_CHECK for the Summary.

See references/copy-pass-snippets.md for the shell snippet.

Enumerate sources (both channels)

Channel A — overlay: git ls-files against $LFT_SRC is the source of truth (transparent to symlink mounts when source.repos.Linux_for_Tegra was overridden, excludes untracked / .gitignored files). Each entry maps src = $LFT_SRC/<rel>dst = $LFT_DST/<rel>.

Channel B — manifest: parse artifacts[].{src,dst} from $MANIFEST. Refuse if any src is missing on disk (build was interrupted, or manifest stale — re-run /jetson-build-source). The manifest schema is written by jetson-build-source v0.2.0.

See references/copy-pass-snippets.md for both shell snippets and the manifest YAML schema.

Diff-aware copy into bsp_image

Iterate the union of overlay files and manifest entries. For each dst: if byte-identical, skip; otherwise cp -p (with sudo for rootfs/* destinations, where the sample rootfs was extracted as root). Tag INITRD_DIRTY=1 on any rootfs/lib/modules/* or kernel/Image write — the “Refresh initramfs” step gates on this flag. Counts / FIRST / LAST are recorded for the Summary.

Fail-fast: if any cp fails, surface the failed path and stop. bsp_image may be left partially updated — re-running after fixing the cause resumes via the diff-aware skip. Channel order is overlay first, then manifest: on a dst collision the manifest wins (freshly built artifact beats the older overlay copy).

See references/copy-pass-snippets.md for the copy_one() function and the two driving loops.

Mirror kernel Image into rootfs (when kernel changed)

The kernel Image lives in two paths inside bsp_image: <LFT_DST>/kernel/Image (read by the flash tool) and <LFT_DST>/rootfs/boot/Image (the rootfs-side copy, visible as /boot/Image from inside the rootfs chroot the refresh tool will run in). The build manifest only carries the kernel/Image dst, so this step mirrors kernel/Imagerootfs/boot/Image (diff-aware, no-op when already in sync) so the chrooted refresh tool resolves the kernel against the freshly promoted binary, not the stale rootfs copy. The mirror also sets INITRD_DIRTY=1 so a kernel-only promote (no rootfs/lib/modules/* writes) still triggers the refresh.

See references/kernel-image-and-initramfs.md for the shell snippet, the failure mode this prevents, and the INITRD_DIRTY corner case.

Refresh initramfs (when kernel or modules changed)

Run tools/l4t_update_initrd.sh from <LFT_DST>/ whenever INITRD_DIRTY=1 (set by the diff-aware copy or the mirror step above). The tool chroots into rootfs/, runs NVIDIA’s nv-update-initrd, and writes both <LFT_DST>/bootloader/l4t_initrd.img (used by the flash tool) and <LFT_DST>/rootfs/boot/initrd (/boot/initrd on the DUT). Idempotent; ~30 s. Skip when INITRD_DIRTY=0 (overlay-only edits). DUT-side workarounds (update-initramfs -u + manual cp) are out of scope — fix the gap here so flash ships a coherent image.

See references/kernel-image-and-initramfs.md for the shell snippet, refuse paths, the “module shadowing” and “vermagic skew” failure modes the rebuild closes, and why bootloader/initrd (a different file) is left alone.

Summary

Report:

Limitations

Troubleshooting

Error Cause Solution
Overlay has uncommitted changes at <LFT_SRC> Customize-* edits not committed before promote Run git -C $LFT_SRC commit (or stash), then re-run.
origin has N unpulled commits on <upstream> Remote overlay diverged from local git -C $LFT_SRC pull, resolve conflicts, then re-run.
Both overlay and manifest are empty — nothing to promote No Customize-* commits and no Build manifest Run a customize-* skill or /jetson-build-source first.
Kernel-side source(s) changed since last /jetson-build-source Freshness gate detected unprocessed customize-* edits under Source/bsp_sources/ Commit pending edits, run /jetson-build-source, re-run promote.
Manifest entry references missing build output: <src> bsp_sources/ build outputs wiped or stale manifest Re-run /jetson-build-source to regenerate.
Build manifest at <MANIFEST> is not valid YAML Manifest hand-edited or partially written Re-run /jetson-build-source to rewrite the manifest.
cp: permission denied under rootfs/ Missing sudo privilege on the host Run on an account that can sudo cp; re-run resumes via diff-aware copy.
Profile lacks bsp_image: / source: Workspace not bootstrapped Run /jetson-init-image and/or /jetson-init-source.
tool not found at <LFT_DST>/tools/l4t_update_initrd.sh tools/ was pruned, or bsp_image extracted from a non-NVIDIA tarball Re-run /jetson-init-image to repopulate.
l4t_update_initrd.sh exited non-zero Insufficient sudo, broken rootfs (missing lib/modules/<ver>/modules.dep), or out-of-space /tmp Run depmod -a -b <LFT_DST>/rootfs <ver> against the rootfs first; verify /tmp headroom; rerun promote.
DUT boots with stale kernel / modules after promote, modules fail to load with disagrees about version of symbol …, or initramfs ships pre-customize modules even after the refresh ran The mirror / refresh gate didn’t fire (manual hand-edit under <LFT_DST> outside the skill), or rootfs/boot/Image drifted from kernel/Image so the chrooted refresh built against the stale kernel Force the gate by sudo touch <LFT_DST>/kernel/Image + re-run promote, or run the two steps manually: sudo cp -p <LFT_DST>/kernel/Image <LFT_DST>/rootfs/boot/Image && cd <LFT_DST> && sudo ./tools/l4t_update_initrd.sh. Then re-flash. See references/kernel-image-and-initramfs.md.

Spec status

Locked in for v0.2.0:

Still deferred:

References

Skill frontmatter

version: 0.0.1 license: Apache-2.0 metadata: {"data-classification" => "public", "author" => "Jetson Team", "team" => "pts", "tags" => ["bsp", "promote", "deploy"], "domain" => "meta"}