start-db.sh 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  1. #!/usr/bin/env bash
  2. set -euo pipefail
  3. if ! docker info >/dev/null 2>&1; then
  4. echo "❌ Docker daemon is not running."
  5. echo "Please start Docker first:"
  6. echo " sudo systemctl start docker"
  7. exit 1
  8. fi
  9. NETWORK="box-network"
  10. MONGO_CONTAINER="box-mongodb"
  11. MONGO_INIT_CONTAINER="box-mongodb-init"
  12. REDIS_CONTAINER="box-redis"
  13. RABBIT_CONTAINER="box-rabbitmq"
  14. MYSQL_CONTAINER="box-mysql"
  15. MONGO_VOLUME="box_mongo_data"
  16. REDIS_VOLUME="box_redis_data"
  17. RABBIT_VOLUME="box_rabbitmq_data"
  18. MYSQL_VOLUME="box_mysql_data"
  19. MONGO_USER="boxadmin"
  20. MONGO_PASS="boxpass"
  21. MONGO_RS="rs0"
  22. MONGO_DB="box_admin"
  23. MYSQL_ROOT_PASS="rootpass"
  24. MYSQL_DB="box_admin"
  25. KEYFILE_HOST_PATH="docker/mongo/mongo-keyfile"
  26. KEYFILE_CONTAINER_PATH="/etc/mongo-keyfile/mongo-keyfile"
  27. INIT_JS_HOST_PATH="docker/mongo/init-repl.js"
  28. INIT_JS_CONTAINER_PATH="/init-repl.js"
  29. die() {
  30. echo "ERROR: $*" >&2
  31. exit 1
  32. }
  33. ensure_network() {
  34. if ! docker network inspect "$NETWORK" >/dev/null 2>&1; then
  35. docker network create "$NETWORK" >/dev/null
  36. fi
  37. }
  38. ensure_volume() {
  39. local v="$1"
  40. if ! docker volume inspect "$v" >/dev/null 2>&1; then
  41. docker volume create "$v" >/dev/null
  42. fi
  43. }
  44. rm_if_exists() {
  45. local c="$1"
  46. if docker ps -a --format '{{.Names}}' | grep -qx "$c"; then
  47. docker rm -f "$c" >/dev/null || true
  48. fi
  49. }
  50. container_running() {
  51. local c="$1"
  52. docker ps --format '{{.Names}}' | grep -qx "$c"
  53. }
  54. require_file() {
  55. local p="$1"
  56. [ -f "$p" ] || die "missing file: $p"
  57. }
  58. fix_keyfile_perms() {
  59. # Mongo requires keyfile permissions to be restricted.
  60. # In containers, mongod typically runs as uid 999 ("mongodb").
  61. # If host keyfile is 600 but owned by root, uid 999 can't read it.
  62. require_file "$KEYFILE_HOST_PATH"
  63. echo "== ensuring mongo keyfile perms (uid 999 can read, not world-readable) =="
  64. # Try to set ownership to 999:999 when sudo is available (best reliability)
  65. if command -v sudo >/dev/null 2>&1; then
  66. sudo chown 999:999 "$KEYFILE_HOST_PATH" || true
  67. sudo chmod 400 "$KEYFILE_HOST_PATH" || true
  68. else
  69. # fallback: at least restrict perms; ownership may still be an issue
  70. chmod 400 "$KEYFILE_HOST_PATH" || true
  71. fi
  72. echo "keyfile perms:"
  73. ls -l "$KEYFILE_HOST_PATH" || true
  74. }
  75. wait_for_container_or_fail_fast() {
  76. local c="$1"
  77. local seconds="${2:-10}"
  78. for _ in $(seq 1 "$seconds"); do
  79. if container_running "$c"; then
  80. return 0
  81. fi
  82. sleep 1
  83. done
  84. echo "== container '$c' is not running. last logs: =="
  85. docker logs "$c" --tail=200 || true
  86. return 1
  87. }
  88. wait_mongo_ping() {
  89. echo "== waiting for mongo ping =="
  90. for i in $(seq 1 90); do
  91. if docker exec "$MONGO_CONTAINER" mongosh --quiet \
  92. "mongodb://${MONGO_USER}:${MONGO_PASS}@127.0.0.1:27017/admin?authSource=admin" \
  93. --eval 'quit(db.adminCommand({ ping: 1 }).ok ? 0 : 1)' >/dev/null 2>&1; then
  94. echo "mongo ping ok"
  95. return 0
  96. fi
  97. # Fail fast if container died
  98. if ! container_running "$MONGO_CONTAINER"; then
  99. echo "== mongo container stopped. last logs: =="
  100. docker logs "$MONGO_CONTAINER" --tail=200 || true
  101. return 1
  102. fi
  103. sleep 1
  104. done
  105. echo "mongo did not become ready (ping) in time"
  106. echo "== last mongo logs: =="
  107. docker logs "$MONGO_CONTAINER" --tail=200 || true
  108. return 1
  109. }
  110. wait_mongo_rs_ok() {
  111. echo "== waiting for mongo replica set (rs.status().ok == 1) =="
  112. for i in $(seq 1 90); do
  113. if docker exec "$MONGO_CONTAINER" mongosh --quiet \
  114. "mongodb://${MONGO_USER}:${MONGO_PASS}@127.0.0.1:27017/admin?authSource=admin" \
  115. --eval 'try{quit(rs.status().ok===1?0:1)}catch(e){quit(1)}' >/dev/null 2>&1; then
  116. echo "mongo rs ok"
  117. return 0
  118. fi
  119. sleep 1
  120. done
  121. echo "mongo replica set not ok in time"
  122. docker exec "$MONGO_CONTAINER" mongosh --quiet \
  123. "mongodb://${MONGO_USER}:${MONGO_PASS}@127.0.0.1:27017/admin?authSource=admin" \
  124. --eval 'try{printjson(rs.status())}catch(e){print(e)}' || true
  125. return 1
  126. }
  127. wait_mysql_ping() {
  128. echo "== waiting for mysql ping =="
  129. for i in $(seq 1 90); do
  130. if docker exec "$MYSQL_CONTAINER" mysqladmin ping -h 127.0.0.1 -p"${MYSQL_ROOT_PASS}" --silent >/dev/null 2>&1; then
  131. echo "mysql ping ok"
  132. return 0
  133. fi
  134. if ! container_running "$MYSQL_CONTAINER"; then
  135. echo "== mysql container stopped. last logs: =="
  136. docker logs "$MYSQL_CONTAINER" --tail=200 || true
  137. return 1
  138. fi
  139. sleep 1
  140. done
  141. echo "mysql did not become ready in time"
  142. docker logs "$MYSQL_CONTAINER" --tail=200 || true
  143. return 1
  144. }
  145. start_redis() {
  146. echo "== starting redis =="
  147. rm_if_exists "$REDIS_CONTAINER"
  148. docker run -d \
  149. --name "$REDIS_CONTAINER" \
  150. --restart unless-stopped \
  151. --network "$NETWORK" \
  152. -p 6379:6379 \
  153. -v "${REDIS_VOLUME}:/data" \
  154. redis:7 >/dev/null
  155. }
  156. start_rabbitmq() {
  157. echo "== starting rabbitmq =="
  158. rm_if_exists "$RABBIT_CONTAINER"
  159. docker run -d \
  160. --name "$RABBIT_CONTAINER" \
  161. --restart unless-stopped \
  162. --network "$NETWORK" \
  163. -p 5672:5672 \
  164. -p 15672:15672 \
  165. -e RABBITMQ_DEFAULT_USER=boxrabbit \
  166. -e RABBITMQ_DEFAULT_PASS=BoxRabbit2025 \
  167. -e RABBITMQ_DEFAULT_VHOST=/ \
  168. -v "${RABBIT_VOLUME}:/var/lib/rabbitmq" \
  169. rabbitmq:3.12-management >/dev/null
  170. }
  171. start_mongodb() {
  172. echo "== starting mongodb (replSet + auth + keyFile) =="
  173. require_file "$KEYFILE_HOST_PATH"
  174. require_file "$INIT_JS_HOST_PATH"
  175. fix_keyfile_perms
  176. rm_if_exists "$MONGO_CONTAINER"
  177. docker run -d \
  178. --name "$MONGO_CONTAINER" \
  179. --restart unless-stopped \
  180. --network "$NETWORK" \
  181. -p 27017:27017 \
  182. -e MONGO_INITDB_ROOT_USERNAME="$MONGO_USER" \
  183. -e MONGO_INITDB_ROOT_PASSWORD="$MONGO_PASS" \
  184. -e MONGO_INITDB_DATABASE="$MONGO_DB" \
  185. -v "${MONGO_VOLUME}:/data/db" \
  186. -v "$(pwd)/${KEYFILE_HOST_PATH}:${KEYFILE_CONTAINER_PATH}:ro" \
  187. mongo:7 \
  188. mongod --replSet "$MONGO_RS" --bind_ip_all --keyFile "$KEYFILE_CONTAINER_PATH" >/dev/null
  189. wait_for_container_or_fail_fast "$MONGO_CONTAINER" 8
  190. wait_mongo_ping
  191. }
  192. init_mongodb_rs() {
  193. echo "== initializing mongodb replica set (one-shot) =="
  194. # Run init script (should be idempotent)
  195. rm_if_exists "$MONGO_INIT_CONTAINER"
  196. docker run --rm \
  197. --name "$MONGO_INIT_CONTAINER" \
  198. --network "$NETWORK" \
  199. -v "$(pwd)/${INIT_JS_HOST_PATH}:${INIT_JS_CONTAINER_PATH}:ro" \
  200. mongo:7 \
  201. mongosh "mongodb://${MONGO_USER}:${MONGO_PASS}@${MONGO_CONTAINER}:27017/admin?authSource=admin" \
  202. --quiet "$INIT_JS_CONTAINER_PATH"
  203. wait_mongo_rs_ok
  204. }
  205. start_mysql() {
  206. echo "== starting mysql =="
  207. rm_if_exists "$MYSQL_CONTAINER"
  208. docker run -d \
  209. --name "$MYSQL_CONTAINER" \
  210. --restart unless-stopped \
  211. --network "$NETWORK" \
  212. -p 3306:3306 \
  213. -e MYSQL_ROOT_PASSWORD="$MYSQL_ROOT_PASS" \
  214. -e MYSQL_DATABASE="$MYSQL_DB" \
  215. -v "${MYSQL_VOLUME}:/var/lib/mysql" \
  216. mysql:8 >/dev/null
  217. wait_for_container_or_fail_fast "$MYSQL_CONTAINER" 10
  218. wait_mysql_ping
  219. }
  220. print_status() {
  221. echo
  222. echo "== infra status =="
  223. docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" \
  224. | grep -E 'box-(redis|rabbitmq|mongodb|mysql)' || true
  225. }
  226. main() {
  227. echo "== ensuring network + volumes =="
  228. ensure_network
  229. ensure_volume "$MONGO_VOLUME"
  230. ensure_volume "$REDIS_VOLUME"
  231. ensure_volume "$RABBIT_VOLUME"
  232. ensure_volume "$MYSQL_VOLUME"
  233. start_redis
  234. start_rabbitmq
  235. start_mongodb
  236. init_mongodb_rs
  237. start_mysql
  238. print_status
  239. echo
  240. echo "DONE. Now run:"
  241. echo " docker compose --env-file .env.docker up -d --build"
  242. }
  243. main "$@"