feat(install): --use-local-images so a bundle can carry an unreleased build
management_compose.yaml pins the published Command Center image and the builder pulls it unconditionally, so a bundle always carries the *released* admin application. That makes an admin-side change impossible to test on an air-gapped target until it ships in an image — including the Easy Setup offline fixes in this branch. With --use-local-images, an image already in the daemon is bundled as-is. Anything not present locally is still pulled, so the flag cannot quietly produce a bundle with a gap in it. It is opt-in because it deliberately does not verify that a local image resembles the tag it claims. Bundles built this way record USED_LOCAL_IMAGES=1 in the manifest and carry a "not for distribution" note in README.txt. The manifest is read key-by-key by the installer, so the added key needs no format bump. The docker wrapper forwards the flag through its existing catch-all, and `docker image inspect` inside the build container resolves against the host daemon via the mounted socket, so host-built images are visible. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
8c91436aa4
commit
d5d2301679
|
|
@ -112,6 +112,7 @@ handing you a bundle that breaks in the field.
|
|||
| `--extra-image-archive FILE` | Include an existing `docker save` archive |
|
||||
| `--with-apps LIST` | Bundle Supply Depot app images (`default`, `all`, or a list) |
|
||||
| `--list-apps` | Print the installable app names and exit |
|
||||
| `--use-local-images` | Bundle images already in the local Docker daemon instead of pulling them |
|
||||
| `--content-dir DIR` | Include pre-staged NOMAD storage content |
|
||||
| `--archive` | Also produce a `.tar.gz` of the finished bundle |
|
||||
|
||||
|
|
@ -141,6 +142,29 @@ removes them and excludes them from `SHA256SUMS`, and the installer ignores them
|
|||
when loading image archives. Without that, a `._core-images.tar` sidecar would
|
||||
both break checksum verification and be handed to `docker load`.
|
||||
|
||||
### Testing an unreleased Command Center
|
||||
|
||||
`management_compose.yaml` pins the published
|
||||
`ghcr.io/crosstalk-solutions/project-nomad:latest`, and the builder pulls it. A
|
||||
bundle therefore carries the *released* Command Center, not whatever is in your
|
||||
checkout — so a change to the admin application cannot be tested air-gapped
|
||||
until it ships in an image.
|
||||
|
||||
`--use-local-images` closes that loop. Build the image from your checkout, tag it
|
||||
as the reference the compose file uses, then build the bundle:
|
||||
|
||||
```bash
|
||||
docker build -t ghcr.io/crosstalk-solutions/project-nomad:latest .
|
||||
./install/build_offline_bundle_docker.sh --target ubuntu:26.04 --use-local-images
|
||||
```
|
||||
|
||||
Any image already present in the daemon is taken as-is; anything missing is still
|
||||
pulled, so the flag can never quietly produce a bundle with a gap in it.
|
||||
|
||||
Bundles built this way record `USED_LOCAL_IMAGES=1` in the manifest and carry a
|
||||
note in `README.txt`, because they may contain unreleased code and should not be
|
||||
distributed.
|
||||
|
||||
### Bundles are specific
|
||||
|
||||
A bundle is valid for **one operating system, one version, and one
|
||||
|
|
@ -250,6 +274,7 @@ TARGET_OS=ubuntu
|
|||
TARGET_VERSION=26.04
|
||||
TARGET_ARCH=amd64
|
||||
WITH_NVIDIA_TOOLKIT=1
|
||||
USED_LOCAL_IMAGES=0
|
||||
CREATED_AT_UTC=2026-08-14T16:00:00Z
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ EXTRA_IMAGE_ARCHIVE=''
|
|||
CONTENT_DIR=''
|
||||
WITH_APPS=''
|
||||
LIST_APPS='0'
|
||||
USE_LOCAL_IMAGES='0'
|
||||
|
||||
# Supply Depot apps whose images are worth carrying by default: broadly useful,
|
||||
# and modest in size compared with the AI/education stack.
|
||||
|
|
@ -107,6 +108,12 @@ Options:
|
|||
useful starter set, or "all". Adds significant
|
||||
size — see --list-apps.
|
||||
--list-apps Print the installable app names and exit
|
||||
--use-local-images Skip the registry pull for any image already
|
||||
present in the local Docker daemon. Lets a bundle
|
||||
carry an image built from this checkout (e.g. an
|
||||
unreleased Command Center) instead of the
|
||||
published tag. Images that are NOT local are
|
||||
still pulled as usual.
|
||||
--without-nvidia-toolkit Omit the NVIDIA Container Toolkit packages
|
||||
--extra-image-list FILE Also pull and bundle the image references in FILE
|
||||
--extra-image-archive FILE Copy an existing docker-save archive into the
|
||||
|
|
@ -174,6 +181,9 @@ parse_args() {
|
|||
CONTENT_DIR="$2"
|
||||
shift
|
||||
;;
|
||||
--use-local-images)
|
||||
USE_LOCAL_IMAGES='1'
|
||||
;;
|
||||
--archive)
|
||||
CREATE_ARCHIVE='1'
|
||||
;;
|
||||
|
|
@ -415,6 +425,31 @@ CONTAINER_SCRIPT
|
|||
ok "Local APT repository resolves offline."
|
||||
}
|
||||
|
||||
# Make an image available locally so `docker save` can write it into the bundle.
|
||||
#
|
||||
# Normally that means pulling the published tag. With --use-local-images, an
|
||||
# image already in the daemon is taken as-is: the point is to bundle a Command
|
||||
# Center built from this checkout rather than the released
|
||||
# ghcr.io/crosstalk-solutions/project-nomad:latest that management_compose.yaml
|
||||
# pins, so unreleased changes can be tested on an air-gapped target. Images that
|
||||
# are not present locally are still pulled, so the flag never silently produces
|
||||
# a bundle with something missing.
|
||||
#
|
||||
# Deliberately narrow: it does not verify the local image resembles the tag it
|
||||
# claims. A bundle built this way carries whatever you built, which is the whole
|
||||
# point — and the reason it is opt-in rather than the default.
|
||||
acquire_image() {
|
||||
local image="$1"
|
||||
|
||||
if [[ "${USE_LOCAL_IMAGES}" == '1' ]] && docker image inspect "${image}" >/dev/null 2>&1; then
|
||||
log "Using local ${image} (not pulling)."
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "Pulling ${image}..."
|
||||
docker pull --platform "linux/${TARGET_ARCH}" "${image}"
|
||||
}
|
||||
|
||||
discover_and_save_images() {
|
||||
local compose_file="${REPO_ROOT}/install/management_compose.yaml"
|
||||
local image_list="${BUNDLE_DIR}/images/core-images.txt"
|
||||
|
|
@ -440,8 +475,7 @@ discover_and_save_images() {
|
|||
[[ ${#images[@]} -gt 0 ]] || die "No images were discovered from ${compose_file}."
|
||||
|
||||
for image in "${images[@]}"; do
|
||||
log "Pulling ${image}..."
|
||||
docker pull --platform "linux/${TARGET_ARCH}" "${image}" ||
|
||||
acquire_image "${image}" ||
|
||||
die "Failed to pull ${image}. The build machine needs internet access and registry availability."
|
||||
done
|
||||
|
||||
|
|
@ -550,9 +584,7 @@ bundle_app_images() {
|
|||
|
||||
log "Bundling ${#images[@]} Supply Depot app image(s)..."
|
||||
for image in "${images[@]}"; do
|
||||
log "Pulling ${image}"
|
||||
docker pull --platform "linux/${TARGET_ARCH}" "${image}" ||
|
||||
die "Failed to pull ${image}."
|
||||
acquire_image "${image}" || die "Failed to pull ${image}."
|
||||
done
|
||||
|
||||
printf '%s\n' "${images[@]}" > "${BUNDLE_DIR}/images/app-images.txt"
|
||||
|
|
@ -637,6 +669,7 @@ TARGET_OS=${TARGET_OS}
|
|||
TARGET_VERSION=${TARGET_VERSION}
|
||||
TARGET_ARCH=${TARGET_ARCH}
|
||||
WITH_NVIDIA_TOOLKIT=${WITH_NVIDIA}
|
||||
USED_LOCAL_IMAGES=${USE_LOCAL_IMAGES}
|
||||
CREATED_AT_UTC=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
||||
EOF
|
||||
|
||||
|
|
@ -646,7 +679,11 @@ Project NOMAD Offline Artifact Bundle
|
|||
|
||||
NOMAD commit : ${NOMAD_COMMIT}
|
||||
Target : ${TARGET_OS} ${TARGET_VERSION} (${TARGET_ARCH})
|
||||
|
||||
$(if [[ "${USE_LOCAL_IMAGES}" == '1' ]]; then
|
||||
printf '\nNOTE: Built with --use-local-images. One or more images came from the\n'
|
||||
printf 'build machine rather than a registry, so this bundle may carry\n'
|
||||
printf 'unreleased code. Not for distribution.\n'
|
||||
fi)
|
||||
Install on a matching, disconnected target:
|
||||
|
||||
sudo bash ./install_nomad.sh --artifacts .
|
||||
|
|
|
|||
Loading…
Reference in New Issue