#!/usr/bin/env bash ############################################################################### # [폐쇄망 안에서 실행] 스택을 내린다. # # ./bin/down.sh # compose 로 띄운 것을 내린다 (docker) # ./bin/down.sh compose podman # podman-compose # ./bin/down.sh run # up-docker-run.sh 로 띄운 것을 내린다 # ./bin/down.sh run podman # up-podman-run.sh # # --volumes 데이터 볼륨까지 지운다. ※ DB·업로드 문서·MinIO 객체가 전부 사라진다. # --dry-run 아무것도 지우지 않고 지워질 대상만 출력한다. ############################################################################### . "$(dirname -- "${BASH_SOURCE[0]}")/lib.sh" MODE=compose WIPE=false for arg in "$@"; do case "${arg}" in compose|run) MODE="${arg}" ;; docker|podman) ENGINE_ARG="${arg}" ;; --volumes) WIPE=true ;; --dry-run|-n) DRY_RUN=true ;; -h|--help) sed -n '2,16p' "$0"; exit 0 ;; *) die "알 수 없는 인자: ${arg}" ;; esac done resolve_engine "${ENGINE_ARG}" PATCH_OVERLAY=() [ -f "${DEPLOY_DIR}/compose.patches.yml" ] && PATCH_OVERLAY=(--overlay "${DEPLOY_DIR}/compose.patches.yml") [ -f "${DEPLOY_DIR}/compose.ports.yml" ] && PATCH_OVERLAY+=(--overlay "${DEPLOY_DIR}/compose.ports.yml") [ -f "${ENV_FILE}" ] && load_env PROJECT="${COMPOSE_PROJECT_NAME:-pi-continuum}" if [ "${WIPE}" = true ] && [ "${DRY_RUN}" != true ]; then warn "--volumes: ${PROJECT} 의 데이터 볼륨(DB·MinIO·업로드 문서·로그)을 모두 지운다." printf '정말 지우려면 %s 를 입력한다: ' "${PROJECT}" read -r answer [ "${answer}" = "${PROJECT}" ] || die "입력이 달라 중단한다." fi if [ "${MODE}" = compose ]; then # 비활성화한 뒤에도 Compose가 이전 선택형 컨테이너를 종료 대상으로 포함한다. export COMPOSE_PROFILES="${COMPOSE_PROFILES:+${COMPOSE_PROFILES},}litellm" COMPOSE_FILE="${DEPLOY_DIR}/compose.yml" PATCH_FILE=() [ -f "${DEPLOY_DIR}/compose.patches.yml" ] && PATCH_FILE=(-f "${DEPLOY_DIR}/compose.patches.yml") [ -f "${DEPLOY_DIR}/compose.ports.yml" ] && PATCH_FILE+=(-f "${DEPLOY_DIR}/compose.ports.yml") if [ "${ENGINE_KIND}" = podman ]; then if command -v podman-compose >/dev/null 2>&1; then COMPOSE=(podman-compose -f "${COMPOSE_FILE}" ${PATCH_FILE[@]+"${PATCH_FILE[@]}"}) else COMPOSE=(podman compose -f "${COMPOSE_FILE}" ${PATCH_FILE[@]+"${PATCH_FILE[@]}"}); fi export NGINX_UPSTREAM="${NGINX_UPSTREAM:-podman}" else COMPOSE=(docker compose -f "${COMPOSE_FILE}" ${PATCH_FILE[@]+"${PATCH_FILE[@]}"}) export NGINX_UPSTREAM="${NGINX_UPSTREAM:-docker}" fi log "compose down (${ENGINE_KIND})" down_args=(down --remove-orphans) [ "${WIPE}" = true ] && down_args=(down --volumes --remove-orphans) if [ "${DRY_RUN}" = true ]; then dry "${COMPOSE[*]} ${down_args[*]}" else ( cd "${DEPLOY_DIR}" && "${COMPOSE[@]}" "${down_args[@]}" ) fi else # 토폴로지는 compose 파일이 정본이다. up-run.sh 와 같은 계획을 읽어 그대로 되돌린다. if command -v python3 >/dev/null 2>&1 \ && PLAN="$(python3 "${DEPLOY_DIR}/bin/_compose_to_plan.py" "${DEPLOY_DIR}/compose.yml" "${DEPLOY_DIR}" --engine "${ENGINE_KIND}" ${PATCH_OVERLAY[@]+"${PATCH_OVERLAY[@]}"} 2>/dev/null)"; then mapfile -t NAMES < <(printf '%s' "${PLAN}" | python3 -c ' import json, sys plan = json.load(sys.stdin) # 기동의 역순으로 내린다. for s in reversed(plan["order"]): print(plan["services"][s]["name"]) ') mapfile -t NETS < <(printf '%s' "${PLAN}" | python3 -c ' import json, sys print("\n".join(n["name"] for n in json.load(sys.stdin)["networks"]))') mapfile -t VOLS < <(printf '%s' "${PLAN}" | python3 -c ' import json, sys print("\n".join(json.load(sys.stdin)["volumes"]))') else warn "compose 파일을 읽지 못해 이름 규칙으로만 정리한다." NAMES=(); NETS=("${PROJECT}_default"); VOLS=() for svc in nginx alpha-processing scheduler worker-log worker-high worker-default \ web bootstrap minio-init minio redis postgres; do NAMES+=("${PROJECT}-${svc}") done fi # 설정을 false로 바꾼 뒤 down해도 기존 선택형 컨테이너를 빠뜨리지 않는다. if [[ ! " ${NAMES[*]} " == *" ${PROJECT}-litellm "* ]]; then NAMES+=("${PROJECT}-litellm") fi # 현재 .env 의 수를 이미 낮췄더라도, 이전 기동에서 남은 복제 컨테이너를 찾는다. while IFS= read -r existing; do for base in "${NAMES[@]}"; do case "${existing}" in "${base}"-*) suffix="${existing#"${base}"-}" case "${suffix}" in ''|*[!0-9]*) continue ;; esac NAMES+=("${existing}") break ;; esac done done < <(engine ps -a --format '{{.Names}}') log "컨테이너 제거" # RQ 워커는 warm shutdown 이라 실행 중 잡을 끝낼 때까지 기다린다. 즉시 죽이려면 rm -f 대신 kill. for name in "${NAMES[@]}"; do engine container inspect "${name}" >/dev/null 2>&1 || continue if [ "${DRY_RUN}" = true ]; then dry "rm ${name}"; else log " rm ${name}"; engine rm -f "${name}" >/dev/null; fi done log "네트워크 제거" for net in "${NETS[@]}"; do engine network inspect "${net}" >/dev/null 2>&1 || continue if [ "${DRY_RUN}" = true ]; then dry "network rm ${net}"; continue; fi log " rm ${net}" engine network rm "${net}" >/dev/null 2>&1 || warn " ${net} 제거 실패 (남은 컨테이너 확인)" done if [ "${WIPE}" = true ]; then log "볼륨 제거" for name in "${VOLS[@]}"; do engine volume inspect "${name}" >/dev/null 2>&1 || continue if [ "${DRY_RUN}" = true ]; then dry "volume rm ${name}"; continue; fi log " rm ${name}" engine volume rm "${name}" >/dev/null done log "로그는 호스트에 그대로 둔다: ${LOG_DIR:-./logs}" fi fi log "완료"