#!/usr/bin/env bash set -euo pipefail if ! docker info >/dev/null 2>&1; then echo "❌ Docker daemon is not running." echo "Please start Docker first:" echo " sudo systemctl start docker" exit 1 fi NETWORK="box-network" MONGO_CONTAINER="box-mongodb" MONGO_INIT_CONTAINER="box-mongodb-init" REDIS_CONTAINER="box-redis" RABBIT_CONTAINER="box-rabbitmq" MYSQL_CONTAINER="box-mysql" MONGO_VOLUME="box_mongo_data" REDIS_VOLUME="box_redis_data" RABBIT_VOLUME="box_rabbitmq_data" MYSQL_VOLUME="box_mysql_data" MONGO_USER="boxadmin" MONGO_PASS="boxpass" MONGO_RS="rs0" MONGO_DB="box_admin" MYSQL_ROOT_PASS="rootpass" MYSQL_DB="box_admin" KEYFILE_HOST_PATH="docker/mongo/mongo-keyfile" KEYFILE_CONTAINER_PATH="/etc/mongo-keyfile/mongo-keyfile" INIT_JS_HOST_PATH="docker/mongo/init-repl.js" INIT_JS_CONTAINER_PATH="/init-repl.js" die() { echo "ERROR: $*" >&2 exit 1 } ensure_network() { if ! docker network inspect "$NETWORK" >/dev/null 2>&1; then docker network create "$NETWORK" >/dev/null fi } ensure_volume() { local v="$1" if ! docker volume inspect "$v" >/dev/null 2>&1; then docker volume create "$v" >/dev/null fi } rm_if_exists() { local c="$1" if docker ps -a --format '{{.Names}}' | grep -qx "$c"; then docker rm -f "$c" >/dev/null || true fi } container_running() { local c="$1" docker ps --format '{{.Names}}' | grep -qx "$c" } require_file() { local p="$1" [ -f "$p" ] || die "missing file: $p" } fix_keyfile_perms() { # Mongo requires keyfile permissions to be restricted. # In containers, mongod typically runs as uid 999 ("mongodb"). # If host keyfile is 600 but owned by root, uid 999 can't read it. require_file "$KEYFILE_HOST_PATH" echo "== ensuring mongo keyfile perms (uid 999 can read, not world-readable) ==" # Try to set ownership to 999:999 when sudo is available (best reliability) if command -v sudo >/dev/null 2>&1; then sudo chown 999:999 "$KEYFILE_HOST_PATH" || true sudo chmod 400 "$KEYFILE_HOST_PATH" || true else # fallback: at least restrict perms; ownership may still be an issue chmod 400 "$KEYFILE_HOST_PATH" || true fi echo "keyfile perms:" ls -l "$KEYFILE_HOST_PATH" || true } wait_for_container_or_fail_fast() { local c="$1" local seconds="${2:-10}" for _ in $(seq 1 "$seconds"); do if container_running "$c"; then return 0 fi sleep 1 done echo "== container '$c' is not running. last logs: ==" docker logs "$c" --tail=200 || true return 1 } wait_mongo_ping() { echo "== waiting for mongo ping ==" for i in $(seq 1 90); do if docker exec "$MONGO_CONTAINER" mongosh --quiet \ "mongodb://${MONGO_USER}:${MONGO_PASS}@127.0.0.1:27017/admin?authSource=admin" \ --eval 'quit(db.adminCommand({ ping: 1 }).ok ? 0 : 1)' >/dev/null 2>&1; then echo "mongo ping ok" return 0 fi # Fail fast if container died if ! container_running "$MONGO_CONTAINER"; then echo "== mongo container stopped. last logs: ==" docker logs "$MONGO_CONTAINER" --tail=200 || true return 1 fi sleep 1 done echo "mongo did not become ready (ping) in time" echo "== last mongo logs: ==" docker logs "$MONGO_CONTAINER" --tail=200 || true return 1 } wait_mongo_rs_ok() { echo "== waiting for mongo replica set (rs.status().ok == 1) ==" for i in $(seq 1 90); do if docker exec "$MONGO_CONTAINER" mongosh --quiet \ "mongodb://${MONGO_USER}:${MONGO_PASS}@127.0.0.1:27017/admin?authSource=admin" \ --eval 'try{quit(rs.status().ok===1?0:1)}catch(e){quit(1)}' >/dev/null 2>&1; then echo "mongo rs ok" return 0 fi sleep 1 done echo "mongo replica set not ok in time" docker exec "$MONGO_CONTAINER" mongosh --quiet \ "mongodb://${MONGO_USER}:${MONGO_PASS}@127.0.0.1:27017/admin?authSource=admin" \ --eval 'try{printjson(rs.status())}catch(e){print(e)}' || true return 1 } wait_mysql_ping() { echo "== waiting for mysql ping ==" for i in $(seq 1 90); do if docker exec "$MYSQL_CONTAINER" mysqladmin ping -h 127.0.0.1 -p"${MYSQL_ROOT_PASS}" --silent >/dev/null 2>&1; then echo "mysql ping ok" return 0 fi if ! container_running "$MYSQL_CONTAINER"; then echo "== mysql container stopped. last logs: ==" docker logs "$MYSQL_CONTAINER" --tail=200 || true return 1 fi sleep 1 done echo "mysql did not become ready in time" docker logs "$MYSQL_CONTAINER" --tail=200 || true return 1 } start_redis() { echo "== starting redis ==" rm_if_exists "$REDIS_CONTAINER" docker run -d \ --name "$REDIS_CONTAINER" \ --restart unless-stopped \ --network "$NETWORK" \ -p 6379:6379 \ -v "${REDIS_VOLUME}:/data" \ redis:7 >/dev/null } start_rabbitmq() { echo "== starting rabbitmq ==" rm_if_exists "$RABBIT_CONTAINER" docker run -d \ --name "$RABBIT_CONTAINER" \ --restart unless-stopped \ --network "$NETWORK" \ -p 5672:5672 \ -p 15672:15672 \ -e RABBITMQ_DEFAULT_USER=boxrabbit \ -e RABBITMQ_DEFAULT_PASS=BoxRabbit2025 \ -e RABBITMQ_DEFAULT_VHOST=/ \ -v "${RABBIT_VOLUME}:/var/lib/rabbitmq" \ rabbitmq:3.12-management >/dev/null } start_mongodb() { echo "== starting mongodb (replSet + auth + keyFile) ==" require_file "$KEYFILE_HOST_PATH" require_file "$INIT_JS_HOST_PATH" fix_keyfile_perms rm_if_exists "$MONGO_CONTAINER" docker run -d \ --name "$MONGO_CONTAINER" \ --restart unless-stopped \ --network "$NETWORK" \ -p 27017:27017 \ -e MONGO_INITDB_ROOT_USERNAME="$MONGO_USER" \ -e MONGO_INITDB_ROOT_PASSWORD="$MONGO_PASS" \ -e MONGO_INITDB_DATABASE="$MONGO_DB" \ -v "${MONGO_VOLUME}:/data/db" \ -v "$(pwd)/${KEYFILE_HOST_PATH}:${KEYFILE_CONTAINER_PATH}:ro" \ mongo:7 \ mongod --replSet "$MONGO_RS" --bind_ip_all --keyFile "$KEYFILE_CONTAINER_PATH" >/dev/null wait_for_container_or_fail_fast "$MONGO_CONTAINER" 8 wait_mongo_ping } init_mongodb_rs() { echo "== initializing mongodb replica set (one-shot) ==" # Run init script (should be idempotent) rm_if_exists "$MONGO_INIT_CONTAINER" docker run --rm \ --name "$MONGO_INIT_CONTAINER" \ --network "$NETWORK" \ -v "$(pwd)/${INIT_JS_HOST_PATH}:${INIT_JS_CONTAINER_PATH}:ro" \ mongo:7 \ mongosh "mongodb://${MONGO_USER}:${MONGO_PASS}@${MONGO_CONTAINER}:27017/admin?authSource=admin" \ --quiet "$INIT_JS_CONTAINER_PATH" wait_mongo_rs_ok } start_mysql() { echo "== starting mysql ==" rm_if_exists "$MYSQL_CONTAINER" docker run -d \ --name "$MYSQL_CONTAINER" \ --restart unless-stopped \ --network "$NETWORK" \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD="$MYSQL_ROOT_PASS" \ -e MYSQL_DATABASE="$MYSQL_DB" \ -v "${MYSQL_VOLUME}:/var/lib/mysql" \ mysql:8 >/dev/null wait_for_container_or_fail_fast "$MYSQL_CONTAINER" 10 wait_mysql_ping } print_status() { echo echo "== infra status ==" docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" \ | grep -E 'box-(redis|rabbitmq|mongodb|mysql)' || true } main() { echo "== ensuring network + volumes ==" ensure_network ensure_volume "$MONGO_VOLUME" ensure_volume "$REDIS_VOLUME" ensure_volume "$RABBIT_VOLUME" ensure_volume "$MYSQL_VOLUME" start_redis start_rabbitmq start_mongodb init_mongodb_rs start_mysql print_status echo echo "DONE. Now run:" echo " docker compose --env-file .env.docker up -d --build" } main "$@"