Procházet zdrojové kódy

feat(scripts): add Docker management scripts for cleanup, health checks, and lifecycle management

Dave před 1 měsícem
rodič
revize
a4bd7f02fe
5 změnil soubory, kde provedl 404 přidání a 4 odebrání
  1. 4 4
      .gitignore
  2. 56 0
      scripts/cleanup-docker.sh
  3. 20 0
      scripts/smoke.sh
  4. 289 0
      scripts/start-db.sh
  5. 35 0
      scripts/stop-db.sh

+ 4 - 4
.gitignore

@@ -62,8 +62,8 @@ src/tmp/*
 # .env.app.dev
 # .env.mgnt
 # .env.mgnt.dev
-scripts/cleanup-docker.sh
-scripts/smoke.sh
-scripts/start-db.sh
-scripts/stop-db.sh
+# scripts/cleanup-docker.sh
+# scripts/smoke.sh
+# scripts/start-db.sh
+# scripts/stop-db.sh
 docker/mongo/mongo-keyfile

+ 56 - 0
scripts/cleanup-docker.sh

@@ -0,0 +1,56 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+# Usage:
+#   ./scripts/cleanup-docker.sh            # safe cleanup (dangling images, stopped containers, build cache)
+#   ./scripts/cleanup-docker.sh --all      # also prune unused images
+#   ./scripts/cleanup-docker.sh --volumes  # also prune unused volumes (DANGEROUS)
+#   ./scripts/cleanup-docker.sh --nuke-box # remove known box containers + named volumes (VERY DANGEROUS)
+
+ALL=0
+VOLUMES=0
+NUKE_BOX=0
+
+for arg in "${@:-}"; do
+  case "$arg" in
+    --all) ALL=1 ;;
+    --volumes) VOLUMES=1 ;;
+    --nuke-box) NUKE_BOX=1 ;;
+    *) echo "Unknown arg: $arg" >&2; exit 2 ;;
+  esac
+done
+
+echo "== docker cleanup starting =="
+
+echo "== pruning stopped containers =="
+docker container prune -f >/dev/null || true
+
+echo "== pruning build cache =="
+docker builder prune -f >/dev/null || true
+
+echo "== pruning dangling images =="
+docker image prune -f >/dev/null || true
+
+if [ "$ALL" -eq 1 ]; then
+  echo "== pruning unused images (--all) =="
+  docker image prune -af >/dev/null || true
+fi
+
+if [ "$VOLUMES" -eq 1 ]; then
+  echo "== pruning unused volumes (--volumes) =="
+  docker volume prune -f >/dev/null || true
+fi
+
+if [ "$NUKE_BOX" -eq 1 ]; then
+  echo "== removing known box containers (--nuke-box) =="
+  docker rm -f box-mgnt-api box-app-api box-stats-api box-mysql box-mongodb box-rabbitmq box-redis 2>/dev/null || true
+
+  echo "== removing known box volumes (--nuke-box) =="
+  docker volume rm box_mongo_data box_redis_data box_rabbitmq_data box_mysql_data 2>/dev/null || true
+
+  echo "== removing box network (--nuke-box) =="
+  docker network rm box-network 2>/dev/null || true
+fi
+
+echo "== docker cleanup done =="
+docker system df || true

+ 20 - 0
scripts/smoke.sh

@@ -0,0 +1,20 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+echo "== docker compose ps =="
+docker compose ps
+
+echo
+echo "== Mongo replica set ok? (rs.status().ok) =="
+docker exec box-mongodb mongosh --quiet --eval 'try{print(rs.status().ok)}catch(e){print("ERR");quit(1)}'
+
+echo
+echo "== Redis ping =="
+docker exec box-redis redis-cli ping
+
+echo
+echo "== RabbitMQ ping =="
+docker exec box-rabbitmq rabbitmq-diagnostics -q ping
+
+echo
+echo "== DONE =="

+ 289 - 0
scripts/start-db.sh

@@ -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 "$@"

+ 35 - 0
scripts/stop-db.sh

@@ -0,0 +1,35 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+# Must match start-db.sh names
+NETWORK="box-network"
+
+MONGO_CONTAINER="box-mongodb"
+REDIS_CONTAINER="box-redis"
+RABBIT_CONTAINER="box-rabbitmq"
+MYSQL_CONTAINER="box-mysql"
+
+stop_container_if_exists() {
+  local c="$1"
+  if docker ps -a --format '{{.Names}}' | grep -qx "$c"; then
+    echo "== stopping/removing $c =="
+    docker rm -f "$c" >/dev/null || true
+  else
+    echo "== skipping $c (not found) =="
+  fi
+}
+
+main() {
+  stop_container_if_exists "$MYSQL_CONTAINER"
+  stop_container_if_exists "$MONGO_CONTAINER"
+  stop_container_if_exists "$RABBIT_CONTAINER"
+  stop_container_if_exists "$REDIS_CONTAINER"
+
+  # Keep network by default (so compose + db scripts stay consistent)
+  echo "== done =="
+  echo "Note: network '$NETWORK' was kept."
+  echo "If you want to remove it, run:"
+  echo "  docker network rm $NETWORK"
+}
+
+main "$@"