|
|
@@ -0,0 +1,289 @@
|
|
|
+#!/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 "$@"
|