#!/usr/bin/env bash set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" # Defaults WITH_LIVEKIT=false VERSION="" usage() { cat < Building air-gapped bundle v${VERSION}" echo " LiveKit: ${WITH_LIVEKIT}" # ---- Image lists ---- # Core Helm images (used by loop-enterprise-stack) HELM_IMAGES=( "registry.loop.ru/loop/server:${VERSION}" "appropriate/curl:latest" ) # Core Docker Compose images COMPOSE_IMAGES=( "registry.loop.ru/loop/server:${VERSION}" "nginx:latest" "postgres:15-alpine3.21" "minio/minio:RELEASE.2022-08-26T19-53-15Z" "minio/mc:RELEASE.2022-08-28T20-08-11Z" ) # LiveKit images (optional) LIVEKIT_IMAGES=() echo "==> Image lists prepared" # ---- Prerequisites check ---- for cmd in docker helm tar gzip; do if ! command -v "$cmd" &>/dev/null; then echo "ERROR: '$cmd' is required but not found in PATH" exit 1 fi done # ---- Vendor Helm dependencies ---- echo "==> Vendoring Helm dependencies for loop-enterprise-stack..." helm dependency update "$REPO_ROOT/loop-enterprise-stack/" echo "==> Extracting Bitnami image references via helm template..." BITNAMI_IMAGES=$(helm template test "$REPO_ROOT/loop-enterprise-stack/" 2>/dev/null \ | grep 'image:' \ | sed -E 's/.*image:[[:space:]]*"?([^"[:space:]]+)"?.*/\1/' \ | sort -u \ | grep -v 'registry.loop.ru' \ | grep -v 'appropriate/curl') while IFS= read -r img; do [[ -n "$img" ]] && HELM_IMAGES+=("$img") done <<< "$BITNAMI_IMAGES" if [[ "$WITH_LIVEKIT" == "true" ]]; then echo "==> Vendoring Helm dependencies for loop-livekit-server..." helm dependency update "$REPO_ROOT/loop-livekit-server/" echo "==> Extracting LiveKit image references via helm template..." LK_IMAGES=$(helm template test "$REPO_ROOT/loop-livekit-server/" 2>/dev/null \ | grep 'image:' \ | sed -E 's/.*image:[[:space:]]*"?([^"[:space:]]+)"?.*/\1/' \ | sort -u) while IFS= read -r img; do [[ -n "$img" ]] && LIVEKIT_IMAGES+=("$img") done <<< "$LK_IMAGES" fi echo "==> Helm images to bundle:" printf ' %s\n' "${HELM_IMAGES[@]}" if [[ "$WITH_LIVEKIT" == "true" ]]; then echo "==> LiveKit images to bundle:" printf ' %s\n' "${LIVEKIT_IMAGES[@]}" fi echo "==> Compose images to bundle:" printf ' %s\n' "${COMPOSE_IMAGES[@]}" # ---- Prepare bundle directory ---- rm -rf "$BUNDLE_DIR" mkdir -p "$BUNDLE_DIR/images" mkdir -p "$BUNDLE_DIR/helm" mkdir -p "$BUNDLE_DIR/docker-compose" if [[ "$WITH_LIVEKIT" == "true" ]]; then mkdir -p "$BUNDLE_DIR/images/livekit" fi # ---- Pull and save Helm images ---- echo "==> Pulling and saving Helm images..." for img in "${HELM_IMAGES[@]}"; do echo " Pulling: $img" docker pull "$img" safe_name=$(echo "$img" | tr '/:' '_') echo " Saving: $img -> images/${safe_name}.tar" docker save "$img" -o "$BUNDLE_DIR/images/${safe_name}.tar" done # ---- Pull and save Compose-only images (skip duplicates) ---- echo "==> Pulling and saving Compose-only images..." for img in "${COMPOSE_IMAGES[@]}"; do safe_name=$(echo "$img" | tr '/:' '_') if [[ -f "$BUNDLE_DIR/images/${safe_name}.tar" ]]; then echo " Skipping (already saved): $img" continue fi echo " Pulling: $img" docker pull "$img" echo " Saving: $img -> images/${safe_name}.tar" docker save "$img" -o "$BUNDLE_DIR/images/${safe_name}.tar" done # ---- Pull and save LiveKit images ---- if [[ "$WITH_LIVEKIT" == "true" ]] && [[ ${#LIVEKIT_IMAGES[@]} -gt 0 ]]; then echo "==> Pulling and saving LiveKit images..." for img in "${LIVEKIT_IMAGES[@]}"; do echo " Pulling: $img" docker pull "$img" safe_name=$(echo "$img" | tr '/:' '_') echo " Saving: $img -> images/livekit/${safe_name}.tar" docker save "$img" -o "$BUNDLE_DIR/images/livekit/${safe_name}.tar" done fi # ---- Copy Helm charts (with vendored dependencies) ---- echo "==> Copying Helm charts..." cp -r "$REPO_ROOT/loop-enterprise-stack" "$BUNDLE_DIR/helm/loop-enterprise-stack" rm -rf "$BUNDLE_DIR/helm/loop-enterprise-stack/.git" if [[ "$WITH_LIVEKIT" == "true" ]]; then cp -r "$REPO_ROOT/loop-livekit-server" "$BUNDLE_DIR/helm/loop-livekit-server" rm -rf "$BUNDLE_DIR/helm/loop-livekit-server/.git" fi # ---- Copy docker-compose files ---- echo "==> Copying docker-compose files..." cp -r "$REPO_ROOT/docker-compose/"* "$BUNDLE_DIR/docker-compose/" # ---- Copy air-gapped values overrides ---- echo "==> Copying air-gapped values overrides..." if [[ -f "$SCRIPT_DIR/values-airgap-stack.yaml" ]]; then cp "$SCRIPT_DIR/values-airgap-stack.yaml" "$BUNDLE_DIR/helm/loop-enterprise-stack/values-airgap.yaml" fi if [[ -f "$SCRIPT_DIR/docker-compose-airgap.override.yml" ]]; then cp "$SCRIPT_DIR/docker-compose-airgap.override.yml" "$BUNDLE_DIR/docker-compose/docker-compose-airgap.override.yml" fi if [[ "$WITH_LIVEKIT" == "true" ]] && [[ -f "$SCRIPT_DIR/values-airgap-livekit.yaml" ]]; then cp "$SCRIPT_DIR/values-airgap-livekit.yaml" "$BUNDLE_DIR/helm/loop-livekit-server/values-airgap.yaml" fi # ---- Copy install script ---- echo "==> Copying install script..." if [[ -f "$SCRIPT_DIR/install.sh" ]]; then cp "$SCRIPT_DIR/install.sh" "$BUNDLE_DIR/install.sh" chmod +x "$BUNDLE_DIR/install.sh" fi # ---- Copy README ---- if [[ -f "$SCRIPT_DIR/README-airgap.md" ]]; then cp "$SCRIPT_DIR/README-airgap.md" "$BUNDLE_DIR/README.md" fi # ---- Package bundle ---- echo "==> Packaging bundle..." tar -czf "$SCRIPT_DIR/${BUNDLE_NAME}.tar.gz" -C "$SCRIPT_DIR" "$BUNDLE_NAME" rm -rf "$BUNDLE_DIR" BUNDLE_SIZE=$(du -sh "$SCRIPT_DIR/${BUNDLE_NAME}.tar.gz" | awk '{print $1}') echo "" echo "==> Bundle created: ${BUNDLE_NAME}.tar.gz (${BUNDLE_SIZE})" echo " Transfer this file to the air-gapped environment and run install.sh"