#!/usr/bin/env bash set -euo pipefail # Local services start RocketMQ consumers before any business event is produced, # so relying on producer-side auto topic creation makes a fresh broker fail at # startup. Keep the fixed owner-outbox topics explicit and idempotent here. SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" cd "${PROJECT_ROOT}" "${SCRIPT_DIR}/prepare-local-rocketmq-config.sh" >/dev/null export ROCKETMQ_BROKER_CONFIG="${PROJECT_ROOT}/tmp/rocketmq/broker.local.conf" "${SCRIPT_DIR}/resolve-compose-container-conflicts.sh" rocketmq-namesrv rocketmq-broker running_services="$(docker compose ps --services --status running 2>/dev/null || true)" namesrv_was_running=0 broker_was_running=0 if grep -qx "rocketmq-namesrv" <<<"${running_services}"; then namesrv_was_running=1 else docker compose up -d rocketmq-namesrv >/dev/null fi if grep -qx "rocketmq-broker" <<<"${running_services}"; then broker_was_running=1 fi # `make run` is invoked repeatedly during local development. Recreating the # broker on every run forces RocketMQ through store recovery, broker # registration, and route propagation even when the same container is already # healthy. Keep the existing broker hot by default, and reserve a forced # recreate for explicit config refreshes via ROCKETMQ_FORCE_RECREATE=1. if [[ "${ROCKETMQ_FORCE_RECREATE:-0}" == "1" ]]; then docker compose up -d --force-recreate rocketmq-broker >/dev/null broker_was_running=0 elif [[ "${broker_was_running}" != "1" ]]; then docker compose up -d rocketmq-broker >/dev/null fi MQADMIN="/home/rocketmq/rocketmq-5.3.1/bin/mqadmin" NAMESRV="rocketmq-namesrv:9876" CLUSTER="hyapp-local" TOPIC_STAMP="${PROJECT_ROOT}/tmp/rocketmq/topics.applied" TOPICS=( "hyapp_wallet_outbox" "hyapp_wallet_realtime_outbox" "hyapp_room_outbox" "hyapp_room_rocket_launch" "hyapp_user_outbox" "hyapp_message_action_outbox" "hyapp_game_outbox" ) topic_fingerprint() { if command -v shasum >/dev/null 2>&1; then printf '%s\n' "${TOPICS[@]}" | shasum -a 256 | awk '{print $1}' return fi printf '%s\n' "${TOPICS[@]}" | sha256sum | awk '{print $1}' } write_topic_stamp() { local broker_container_id="$1" local fingerprint="$2" # The stamp is only a local startup accelerator. It is scoped to the Docker # container id plus the exact topic list, so replacing the broker or adding a # new owner-outbox topic automatically falls back to mqadmin verification. { printf 'broker_container_id=%s\n' "${broker_container_id}" printf 'topics_sha256=%s\n' "${fingerprint}" for topic in "${TOPICS[@]}"; do printf 'topic=%s\n' "${topic}" done } >"${TOPIC_STAMP}" } broker_container_id="$(docker compose ps -q rocketmq-broker)" topics_sha256="$(topic_fingerprint)" if [[ "${namesrv_was_running}" == "1" && "${broker_was_running}" == "1" && -n "${broker_container_id}" && -f "${TOPIC_STAMP}" ]]; then if grep -Fxq "broker_container_id=${broker_container_id}" "${TOPIC_STAMP}" && grep -Fxq "topics_sha256=${topics_sha256}" "${TOPIC_STAMP}"; then printf 'rocketmq topics already applied\n' exit 0 fi fi # The broker process can accept a container start before it has registered with # NameServer. Wait for the cluster view so topic checks and updateTopic target # the real broker instead of racing the route table. cluster_view="" for _ in {1..60}; do if cluster_view="$(docker compose exec -T rocketmq-broker "${MQADMIN}" clusterList -n "${NAMESRV}" 2>/dev/null)" && grep -q "${CLUSTER}" <<<"${cluster_view}"; then break fi sleep 1 done if ! grep -q "${CLUSTER}" <<<"${cluster_view}"; then printf 'rocketmq broker did not register in cluster %s before topic init\n' "${CLUSTER}" >&2 exit 1 fi # Topic creation is idempotent, but each updateTopic call shells into the # broker container and blocks local startup. Query once and only create missing # topics; a warm `make run` then pays one cheap route read instead of seven # management writes. topic_list="$(docker compose exec -T rocketmq-broker "${MQADMIN}" topicList -n "${NAMESRV}" 2>/dev/null || true)" missing_topics=() for topic in "${TOPICS[@]}"; do if grep -Fxq "${topic}" <<<"${topic_list}"; then continue fi missing_topics+=("${topic}") done if [[ ${#missing_topics[@]} -eq 0 ]]; then write_topic_stamp "${broker_container_id}" "${topics_sha256}" printf 'rocketmq topics already applied\n' exit 0 fi for topic in "${missing_topics[@]}"; do printf 'applying rocketmq topic: %s\n' "${topic}" docker compose exec -T rocketmq-broker "${MQADMIN}" updateTopic -n "${NAMESRV}" -c "${CLUSTER}" -t "${topic}" -r 8 -w 8 >/dev/null done write_topic_stamp "${broker_container_id}" "${topics_sha256}"