#!/usr/bin/env bash set -euo pipefail script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" repo_root="$(cd "$script_dir/../.." && pwd)" env_file="$script_dir/.env" env_example_file="$script_dir/.env.example" docker_contexts_dir="$script_dir/build/local-docker-contexts" 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 ) usage() { cat <<'EOF' Usage: ./deploy/local-bundle/run-service.sh SERVICE [command] Commands: restart Build SERVICE bootJar, rebuild its Docker image and recreate only this compose service. build Build SERVICE bootJar and Docker image, without restarting container. stop Stop SERVICE container. logs Follow SERVICE logs. Examples: ./deploy/local-bundle/run-service.sh pcp-request-service ./deploy/local-bundle/run-service.sh pcp-request-service build EOF } ensure_env_file() { if [[ ! -f "$env_file" && -f "$env_example_file" ]]; then cp "$env_example_file" "$env_file" echo "Created $env_file from .env.example" fi } ensure_source_config_repo_dir() { if grep -q '^CONFIG_REPO_DIR=' "$env_file"; then sed -i "s#^CONFIG_REPO_DIR=.*#CONFIG_REPO_DIR=$repo_root/config-repo#" "$env_file" else printf '\nCONFIG_REPO_DIR=%s/config-repo\n' "$repo_root" >> "$env_file" fi } load_env() { set -a # shellcheck disable=SC1090 source "$env_file" set +a } 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 } compose() { if docker compose version >/dev/null 2>&1; then docker compose --project-directory "$script_dir" -f "$script_dir/docker-compose.yml" "$@" elif command -v docker-compose >/dev/null 2>&1; then docker-compose --project-directory "$script_dir" -f "$script_dir/docker-compose.yml" "$@" else echo "docker compose or docker-compose is required." >&2 exit 1 fi } is_known_service() { local service_name="$1" local known_service_name for known_service_name in "${service_names[@]}"; do if [[ "$known_service_name" == "$service_name" ]]; then return 0 fi done return 1 } 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" } build_service_image() { local service_name="$1" local gradle_max_workers="${GRADLE_MAX_WORKERS:-1}" local gradle_jvmargs="${GRADLE_JVMARGS:--Xmx2g -Dfile.encoding=UTF-8}" local kotlin_daemon_jvmargs="${KOTLIN_DAEMON_JVMARGS:--Xmx3g -XX:MaxMetaspaceSize=768m}" local gradle_offline="${GRADLE_OFFLINE:-true}" local runtime_image="${LOCAL_BUNDLE_RUNTIME_IMAGE:-bellsoft/liberica-openjre-alpine:21.0.5}" local image_tag="${PCP_IMAGE_TAG:-local}" local image_name="pcp/$service_name:$image_tag" local context_dir="$docker_contexts_dir/$service_name" local 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 echo "Building bootJar for $service_name..." "$repo_root/gradlew" --project-dir "$repo_root" "${gradle_common_args[@]}" ":services:$service_name:bootJar" rm -rf "$context_dir" copy_single_boot_jar "$service_name" "$context_dir" echo "Building Docker image $image_name..." docker build \ --build-arg "RUNTIME_IMAGE=$runtime_image" \ --file "$context_dir/Dockerfile" \ --tag "$image_name" \ "$context_dir" } http_get() { local url="$1" if command -v curl >/dev/null 2>&1; then curl -fsS "$url" >/dev/null elif command -v wget >/dev/null 2>&1; then wget -q -O /dev/null "$url" else return 2 fi } wait_for_config_server() { local attempt local url="http://localhost:${CONFIG_SERVER_PORT:-38888}/actuator/health" for attempt in {1..90}; do if http_get "$url"; then echo "spring-cloud-config-server is ready: $url" return 0 fi sleep 2 done echo "spring-cloud-config-server is not ready after 180 seconds: $url" >&2 exit 1 } restart_service() { local service_name="$1" build_service_image "$service_name" if [[ "$service_name" == "spring-cloud-config-server" ]]; then compose up -d --no-deps --force-recreate "$service_name" wait_for_config_server return 0 fi compose up -d spring-cloud-config-server wait_for_config_server compose up -d --no-deps --force-recreate "$service_name" } service_name="${1:-}" command="${2:-restart}" if [[ -z "$service_name" || "$service_name" == "help" || "$service_name" == "-h" || "$service_name" == "--help" ]]; then usage exit 0 fi if ! is_known_service "$service_name"; then echo "Unknown service: $service_name" >&2 echo "Known services:" >&2 printf ' %s\n' "${service_names[@]}" >&2 exit 1 fi ensure_env_file ensure_source_config_repo_dir load_env require_command docker case "$command" in restart) restart_service "$service_name" ;; build) build_service_image "$service_name" ;; stop) compose stop "$service_name" ;; logs) compose logs -f "$service_name" ;; help|-h|--help) usage ;; *) usage >&2 exit 1 ;; esac