#!/usr/bin/env bash set -euo pipefail script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" repo_root="$(cd "$script_dir/../.." && pwd)" version="${LOCAL_BUNDLE_VERSION:-${1:-}}" if [[ -z "$version" ]]; then if git -C "$repo_root" rev-parse --short HEAD >/dev/null 2>&1; then version="$(git -C "$repo_root" rev-parse --short HEAD)" else version="$(date +%Y%m%d%H%M%S)" fi fi bundle_name="pcp-local-bundle-$version" build_dir="$script_dir/build" dist_dir="$build_dir/$bundle_name" docker_contexts_dir="$build_dir/docker-contexts" archive_path="$build_dir/$bundle_name.tar.gz" service_names=( spring-cloud-config-server pcp-srpring-boot-admin-server pcp-satellite-catalog-service pcp-ballistics-service pcp-request-service pcp-route-processing-service slots-service pcp-complex-mission-service pcp-mission-planing-service pcp-coverage-scheme-service pcp-dynamic-plan-service pcp-stations-service pcp-ui-service pcp-tgu-service ) gradle_max_workers="${GRADLE_MAX_WORKERS:-1}" gradle_jvmargs="${GRADLE_JVMARGS:--Xmx2g -Dfile.encoding=UTF-8}" kotlin_daemon_jvmargs="${KOTLIN_DAEMON_JVMARGS:--Xmx3g -XX:MaxMetaspaceSize=768m}" gradle_offline="${GRADLE_OFFLINE:-true}" runtime_image="${LOCAL_BUNDLE_RUNTIME_IMAGE:-bellsoft/liberica-openjre-alpine:21.0.5}" include_infra_images="${INCLUDE_INFRA_IMAGES:-false}" gradle_common_args=( "--no-parallel" "--max-workers=$gradle_max_workers" "-Dorg.gradle.jvmargs=$gradle_jvmargs" "-Pkotlin.daemon.jvmargs=$kotlin_daemon_jvmargs" ) if [[ "$gradle_offline" == "true" ]]; then gradle_common_args+=("--offline") fi infra_images=( "${POSTGRES_IMAGE:-postgis/postgis:16-3.4}" "${KAFKA_IMAGE:-apache/kafka:3.7.2}" ) sanitize_image_name() { printf '%s' "$1" | sed 's#[/:@]#-#g' } copy_single_boot_jar() { local service_name="$1" local context_dir="$2" local jar_count local jar_file jar_count="$( find "$repo_root/services/$service_name/build/libs" -maxdepth 1 -type f -name '*.jar' ! -name '*-plain.jar' | wc -l )" if [[ "$jar_count" != "1" ]]; then echo "Expected exactly one bootJar for $service_name, found $jar_count:" >&2 find "$repo_root/services/$service_name/build/libs" -maxdepth 1 -type f -name '*.jar' -print >&2 || true exit 1 fi jar_file="$( find "$repo_root/services/$service_name/build/libs" -maxdepth 1 -type f -name '*.jar' ! -name '*-plain.jar' | head -n 1 )" mkdir -p "$context_dir/build/libs" cp "$repo_root/services/$service_name/Dockerfile" "$context_dir/Dockerfile" cp "$jar_file" "$context_dir/build/libs/app.jar" } copy_config_repo() { local target_dir="$1" local source_file mkdir -p "$target_dir" ( cd "$repo_root/config-repo" find . -type f \ ! -path './build/*' \ ! -path './.gradle/*' \ ! -name '*.log' \ ! -name '*.tmp' \ -print0 ) | while IFS= read -r -d '' source_file; do mkdir -p "$target_dir/$(dirname "$source_file")" cp "$repo_root/config-repo/$source_file" "$target_dir/$source_file" done } require_command() { local command_name="$1" if ! command -v "$command_name" >/dev/null 2>&1; then echo "$command_name is required." >&2 exit 1 fi } require_command docker require_command tar echo "Building bootJar artifacts..." echo "Gradle max workers: $gradle_max_workers" echo "Gradle JVM args: $gradle_jvmargs" echo "Kotlin daemon JVM args: $kotlin_daemon_jvmargs" echo "Gradle offline mode: $gradle_offline" echo "Local bundle runtime image: $runtime_image" for service_name in "${service_names[@]}"; do echo "Building bootJar for $service_name..." "$repo_root/gradlew" --project-dir "$repo_root" "${gradle_common_args[@]}" ":services:$service_name:bootJar" done rm -rf "$dist_dir" "$docker_contexts_dir" "$archive_path" mkdir -p "$dist_dir/images" "$docker_contexts_dir" echo "Building service Docker images..." service_images=() for service_name in "${service_names[@]}"; do image_name="pcp/$service_name:$version" context_dir="$docker_contexts_dir/$service_name" copy_single_boot_jar "$service_name" "$context_dir" docker build \ --build-arg "RUNTIME_IMAGE=$runtime_image" \ --file "$context_dir/Dockerfile" \ --tag "$image_name" \ "$context_dir" service_images+=("$image_name") done echo "Saving service Docker images into one archive..." docker save --output "$dist_dir/images/pcp-services.tar" "${service_images[@]}" if [[ "$include_infra_images" == "true" ]]; then echo "Pulling and saving infrastructure images..." for image_name in "${infra_images[@]}"; do image_file="$(sanitize_image_name "$image_name").tar" docker pull "$image_name" docker save --output "$dist_dir/images/$image_file" "$image_name" done else echo "Skipping public infrastructure images. They will be pulled by docker compose on the target machine." printf '%s\n' "${infra_images[@]}" > "$dist_dir/images/public-images.txt" fi echo "Preparing bundle files..." cp "$script_dir/docker-compose.yml" "$dist_dir/docker-compose.yml" cp "$script_dir/.env.example" "$dist_dir/.env.example" cp "$script_dir/.env.example" "$dist_dir/.env" sed -i "s/^PCP_IMAGE_TAG=.*/PCP_IMAGE_TAG=$version/" "$dist_dir/.env" cp "$script_dir/README-local.md" "$dist_dir/README-local.md" cp -R "$script_dir/scripts" "$dist_dir/scripts" copy_config_repo "$dist_dir/config-repo" find "$dist_dir/scripts" -type f -name '*.sh' -exec chmod +x {} + echo "Creating archive..." tar -C "$build_dir" -czf "$archive_path" "$bundle_name" echo "Local bundle created: $archive_path" echo "Bundle version: $version" echo "To use: tar -xzf $archive_path && cd $bundle_name"