#!/usr/bin/env bash # Package console for offline/airgap deployment # Usage: ./package-offline-bundle.sh [image] [output-dir] # Example: ./package-offline-bundle.sh registry.stella-ops.org/stellaops/console:2025.10.0 ./offline-bundle set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "${SCRIPT_DIR}/../../.." && pwd)" IMAGE="${1:-registry.stella-ops.org/stellaops/console:latest}" OUTPUT_DIR="${2:-${SCRIPT_DIR}/../artifacts/console/offline-bundle}" BUNDLE_NAME="console-offline-$(date +%Y%m%dT%H%M%S)" # Freeze timestamps export SOURCE_DATE_EPOCH=${SOURCE_DATE_EPOCH:-1704067200} echo "==> Creating offline bundle for: ${IMAGE}" mkdir -p "${OUTPUT_DIR}" # Save image as tarball IMAGE_TAR="${OUTPUT_DIR}/${BUNDLE_NAME}.tar" echo "==> Saving image to ${IMAGE_TAR}..." docker save "${IMAGE}" -o "${IMAGE_TAR}" # Calculate checksums echo "==> Generating checksums..." cd "${OUTPUT_DIR}" sha256sum "${BUNDLE_NAME}.tar" > "${BUNDLE_NAME}.tar.sha256" # Copy Helm values echo "==> Including Helm values overlay..." cp "${REPO_ROOT}/deploy/helm/stellaops/values-console.yaml" "${OUTPUT_DIR}/" # Copy Dockerfile for reference cp "${REPO_ROOT}/ops/devops/docker/Dockerfile.console" "${OUTPUT_DIR}/" # Generate SBOMs if syft available if command -v syft &>/dev/null; then echo "==> Generating SBOMs..." syft "${IMAGE}" -o spdx-json > "${OUTPUT_DIR}/${BUNDLE_NAME}.spdx.json" syft "${IMAGE}" -o cyclonedx-json > "${OUTPUT_DIR}/${BUNDLE_NAME}.cdx.json" fi # Create manifest cat > "${OUTPUT_DIR}/manifest.json" < "${OUTPUT_DIR}/load.sh" <<'LOAD' #!/usr/bin/env bash # Load console image into local Docker daemon set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" MANIFEST="${SCRIPT_DIR}/manifest.json" if [[ ! -f "${MANIFEST}" ]]; then echo "ERROR: manifest.json not found" >&2 exit 1 fi TARBALL=$(jq -r '.imageTarball' "${MANIFEST}") CHECKSUM_FILE=$(jq -r '.checksumFile' "${MANIFEST}") echo "==> Verifying checksum..." cd "${SCRIPT_DIR}" sha256sum -c "${CHECKSUM_FILE}" echo "==> Loading image..." docker load -i "${TARBALL}" IMAGE=$(jq -r '.image' "${MANIFEST}") echo "==> Image loaded: ${IMAGE}" LOAD chmod +x "${OUTPUT_DIR}/load.sh" # Create README cat > "${OUTPUT_DIR}/README.md" < Offline bundle created at: ${OUTPUT_DIR}" echo "==> Contents:" ls -la "${OUTPUT_DIR}"