hyapp-server/scripts/apply-local-rocketmq-topics.sh

127 lines
4.6 KiB
Bash
Executable File

#!/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}"