#!/usr/bin/env bash ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" COMPOSE_FILE="$ROOT_DIR/local-services.compose.yml" ENV_FILE="${LIKEI_ENV_FILE:-$ROOT_DIR/.env.local}" DOCKER_APP_BIN_DIR="/Applications/Docker.app/Contents/Resources/bin" resolve_default_workspace_dir() { local candidate for candidate in "$ROOT_DIR/.." "$ROOT_DIR/../chatapp3" "$ROOT_DIR/../../chatapp3" "$ROOT_DIR/../.."; do if [ -d "$candidate/chatapp3-java" ] && [ -d "$candidate/chatapp3-golang" ]; then cd "$candidate" && pwd return 0 fi done cd "$ROOT_DIR/.." && pwd } DEFAULT_WORKSPACE_DIR="$(resolve_default_workspace_dir)" WORKSPACE_DIR="$DEFAULT_WORKSPACE_DIR" JAVA_REPO_DIR="$WORKSPACE_DIR/chatapp3-java" GOLANG_REPO_DIR="$WORKSPACE_DIR/chatapp3-golang" configure_local_repo_paths() { if [ -n "${LIKEI_WORKSPACE_DIR:-}" ]; then WORKSPACE_DIR="$(cd "$LIKEI_WORKSPACE_DIR" && pwd)" else WORKSPACE_DIR="$DEFAULT_WORKSPACE_DIR" fi JAVA_REPO_DIR="${LIKEI_LOCAL_JAVA_REPO_DIR:-$WORKSPACE_DIR/chatapp3-java}" GOLANG_REPO_DIR="${LIKEI_LOCAL_GOLANG_REPO_DIR:-$WORKSPACE_DIR/chatapp3-golang}" export WORKSPACE_DIR export JAVA_REPO_DIR export GOLANG_REPO_DIR export LIKEI_LOCAL_JAVA_REPO_DIR="$JAVA_REPO_DIR" export LIKEI_LOCAL_GOLANG_REPO_DIR="$GOLANG_REPO_DIR" } configure_local_repo_paths trim_spaces() { local value="${1:-}" value="${value#"${value%%[![:space:]]*}"}" value="${value%"${value##*[![:space:]]}"}" printf '%s' "$value" } strip_wrapping_quotes() { local value="${1:-}" if [ "${#value}" -ge 2 ]; then case "$value" in \"*\") printf '%s' "${value:1:${#value}-2}" return 0 ;; \'*\') printf '%s' "${value:1:${#value}-2}" return 0 ;; esac fi printf '%s' "$value" } is_truthy() { case "${1:-}" in 1|true|TRUE|True|yes|YES|Yes|y|Y|on|ON|On) return 0 ;; *) return 1 ;; esac } local_rocketmq_disabled() { is_truthy "${LIKEI_ROCKETMQ_LOCAL_DISABLE:-true}" } configure_local_runtime_defaults() { if [ -z "${LIKEI_LOCAL_OTHER_JAVA_OPTS_EXTRA+x}" ]; then if local_rocketmq_disabled; then export LIKEI_LOCAL_OTHER_JAVA_OPTS_EXTRA="-Dspring.autoconfigure.exclude=com.red.circle.component.mq.config.RocketMQAutoConfiguration" else export LIKEI_LOCAL_OTHER_JAVA_OPTS_EXTRA="" fi fi } load_local_env() { if [ -f "$ENV_FILE" ]; then while IFS= read -r raw_line || [ -n "$raw_line" ]; do local line key value line="$(trim_spaces "$raw_line")" [ -n "$line" ] || continue case "$line" in \#*) continue ;; export\ *) line="${line#export }" ;; esac case "$line" in *=*) key="${line%%=*}" value="${line#*=}" key="$(trim_spaces "$key")" value="$(trim_spaces "$value")" value="$(strip_wrapping_quotes "$value")" export "$key=$value" ;; esac done < "$ENV_FILE" fi export LIKEI_ENV_FILE="$ENV_FILE" export LIKEI_ENV_MODE="local" configure_local_repo_paths configure_local_runtime_defaults } ensure_docker_cli() { if command -v docker >/dev/null 2>&1; then return 0 fi if [ -d "$DOCKER_APP_BIN_DIR" ]; then export PATH="$DOCKER_APP_BIN_DIR:$PATH" fi command -v docker >/dev/null 2>&1 } wait_for_docker() { ensure_docker_cli || { echo "docker CLI not found" >&2 exit 1 } if docker info >/dev/null 2>&1; then return 0 fi if [ -d "/Applications/Docker.app" ]; then open -a Docker >/dev/null 2>&1 || true fi for _ in $(seq 1 120); do if docker info >/dev/null 2>&1; then return 0 fi sleep 2 done echo "docker daemon did not become ready in time" >&2 exit 1 } compose() { docker compose --env-file "$ENV_FILE" -f "$COMPOSE_FILE" "$@" } wait_http() { local url="$1" local retries="${2:-120}" for _ in $(seq 1 "$retries"); do if curl -fsS "$url" >/dev/null 2>&1; then return 0 fi sleep 2 done echo "service did not become healthy in time: $url" >&2 exit 1 } wait_tcp_port() { local host="$1" local port="$2" local retries="${3:-120}" for _ in $(seq 1 "$retries"); do if nc -z "$host" "$port" >/dev/null 2>&1; then return 0 fi sleep 2 done echo "tcp port did not become ready in time: $host:$port" >&2 exit 1 } wait_container_healthy() { local container_name="$1" local retries="${2:-120}" for _ in $(seq 1 "$retries"); do local state state="$(docker inspect -f '{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}' "$container_name" 2>/dev/null || true)" if [ "$state" = "healthy" ] || [ "$state" = "running" ]; then return 0 fi sleep 2 done echo "container did not become healthy in time: $container_name" >&2 exit 1 } wait_rocketmq_cluster() { local retries="${1:-120}" for _ in $(seq 1 "$retries"); do if docker exec chatapp3-local-rocketmq-broker sh mqadmin clusterList -n rocketmq-namesrv:9876 2>/dev/null | grep -q "DefaultCluster"; then return 0 fi sleep 2 done echo "rocketmq cluster did not become ready in time: DefaultCluster" >&2 exit 1 } wait_local_rocketmq() { if local_rocketmq_disabled; then return 0 fi require_env LIKEI_LOCAL_ROCKETMQ_NAMESRV_PORT require_env LIKEI_LOCAL_ROCKETMQ_PROXY_GRPC_PORT wait_container_healthy chatapp3-local-rocketmq-namesrv 120 wait_container_healthy chatapp3-local-rocketmq-broker 120 wait_container_healthy chatapp3-local-rocketmq-proxy 120 wait_tcp_port 127.0.0.1 "${LIKEI_LOCAL_ROCKETMQ_NAMESRV_PORT}" 120 wait_rocketmq_cluster 120 wait_tcp_port 127.0.0.1 "${LIKEI_LOCAL_ROCKETMQ_PROXY_GRPC_PORT}" 120 } require_env() { local key="$1" if [ -z "${!key:-}" ]; then echo "required environment variable is missing: $key" >&2 exit 1 fi } java_major_version() { if ! command -v java >/dev/null 2>&1; then return 1 fi local raw_version major raw_version="$(java -version 2>&1 | awk -F '"' '/version/ {print $2; exit}')" [ -n "$raw_version" ] || return 1 major="${raw_version%%.*}" if [ "$major" = "1" ]; then major="$(printf '%s\n' "$raw_version" | awk -F. '{print $2}')" fi printf '%s\n' "$major" } require_java_17() { local major major="$(java_major_version || true)" if [ -z "$major" ]; then echo "java not found; rebuilding local Java services requires JDK 17" >&2 exit 1 fi if [ "$major" != "17" ]; then echo "rebuilding local Java services requires JDK 17; current java major version is $major" >&2 exit 1 fi } is_java_service() { case "$1" in gateway|auth|external|other|live|order|wallet|console|visual-monitor|scheduling) return 0 ;; *) return 1 ;; esac } java_artifact_path_for_service() { case "$1" in gateway) printf '%s\n' "$JAVA_REPO_DIR/rc-gateway/target/rc-gateway-1.0.0-local.jar" ;; auth) printf '%s\n' "$JAVA_REPO_DIR/rc-auth/target/rc-auth-1.0.0-local.jar" ;; external) printf '%s\n' "$JAVA_REPO_DIR/rc-service/rc-service-external/external-start/target/rc-service-external-1.0.0-local.jar" ;; other) printf '%s\n' "$JAVA_REPO_DIR/rc-service/rc-service-other/other-start/target/rc-service-other-1.0.0-local.jar" ;; live) printf '%s\n' "$JAVA_REPO_DIR/rc-service/rc-service-live/live-start/target/rc-service-live-1.0.0-local.jar" ;; order) printf '%s\n' "$JAVA_REPO_DIR/rc-service/rc-service-order/order-start/target/rc-service-order-1.0.0-local.jar" ;; wallet) printf '%s\n' "$JAVA_REPO_DIR/rc-service/rc-service-wallet/wallet-start/target/rc-service-wallet-1.0.0-local.jar" ;; console) printf '%s\n' "$JAVA_REPO_DIR/rc-service/rc-service-console/console-start/target/rc-service-console-1.0.0-local.jar" ;; visual-monitor) printf '%s\n' "$JAVA_REPO_DIR/rc-visual/visual-monitor/target/visual-monitor-1.0.0-local.jar" ;; scheduling) printf '%s\n' "$JAVA_REPO_DIR/rc-scheduling/target/rc-scheduling-1.0.0-local.jar" ;; *) return 1 ;; esac } all_local_java_jars_present() { local service artifact_path for service in gateway auth external other live order wallet console visual-monitor scheduling; do artifact_path="$(java_artifact_path_for_service "$service")" || return 1 if [ ! -f "$artifact_path" ]; then echo "missing local jar: $artifact_path" >&2 return 1 fi done } service_port() { local env_key case "$1" in gateway) env_key="LIKEI_LOCAL_GATEWAY_PORT" ;; auth) env_key="LIKEI_LOCAL_AUTH_PORT" ;; wallet) env_key="LIKEI_LOCAL_WALLET_PORT" ;; other) env_key="LIKEI_LOCAL_OTHER_PORT" ;; live) env_key="LIKEI_LOCAL_LIVE_PORT" ;; order) env_key="LIKEI_LOCAL_ORDER_PORT" ;; console) env_key="LIKEI_LOCAL_CONSOLE_PORT" ;; scheduling) env_key="LIKEI_LOCAL_SCHEDULING_PORT" ;; golang) env_key="LIKEI_LOCAL_GOLANG_PORT" ;; external) env_key="LIKEI_LOCAL_EXTERNAL_PORT" ;; visual-monitor) env_key="LIKEI_LOCAL_VISUAL_MONITOR_PORT" ;; *) return 1 ;; esac require_env "$env_key" printf '%s\n' "${!env_key}" } wait_service_port() { local service="$1" local retries="${2:-120}" local port port="$(service_port "$service")" || { echo "unknown app service port: $service" >&2 exit 1 } wait_tcp_port 127.0.0.1 "$port" "$retries" } service_health_url() { local service="$1" case "$service" in console) printf 'http://127.0.0.1:%s/console/actuator/health\n' "$(service_port "$service")" ;; golang) printf 'http://127.0.0.1:%s/health\n' "$(service_port "$service")" ;; *) printf 'http://127.0.0.1:%s/actuator/health\n' "$(service_port "$service")" ;; esac } wait_service_health() { local service="$1" local retries="${2:-120}" local url url="$(service_health_url "$service")" || { echo "unknown app service health url: $service" >&2 exit 1 } wait_http "$url" "$retries" }