UMGUM.COM (лучше) 

Функции контейнеров стендов ( Библиотека функций управления контейнерами тестовых стендов "Docker + Bash". )

4 мая 2019  (обновлено 4 декабря 2020)

Эта публикация скрыта. Она доступна только по прямой ссылке.

OS: "Linux Debian 9 (Stretch)", "Linux Ubuntu 18.04 LTS (Bionic Beaver)".
Apps: "Bash", "Docker" & etc.

В этой заметке описан один из этапов реализации поставленной в вышестоящей публикации задачи автоматизации процедур развёртывания тестовых стендов из docker-контейнеров.

Библиотека функций управления контейнерами тестовых стендов:

# vi /usr/local/etc/devops/lib/bunch.sh.snippet


#!/bin/bash
# This file contains the code snippet for the shell Bash v.4 (Bourne again shell).
# Файл содержит фрагмент кода для командного интерпретатора Bash v.4 (Bourne again shell).

# Функция подготовки конфигурации контейнеров тестового стенда
function bunch-preset {

  # Создаём директорию для журналов событий контейнеров
  mkdir -p /var/log/devops/bunch/${SITENAME}

  # Зачищаем конфигурацию, данные и журналы - каждый запуск "с чистого листа"
  rm -rf ${OPSROOT}/bunch/${SITENAME}
  rm -f "${LOG}"

  # # Создаём структуру временных директорий монтирования.

  # Передаём всю файловую иерархию тестового стенда пользователю и группе "www-data"
  # (за пределы контейнера и выданных ему файловых ресурсов всё равно выйти нельзя)
  mkdir -p ${OPSROOT}/bunch/${SITENAME}
  chown -R www-data:www-data ${OPSROOT}/bunch/${SITENAME}
  setfacl --mask --modify group::rwX ${OPSROOT}/bunch/${SITENAME}
  setfacl --mask --set default:group::rwX ${OPSROOT}/bunch/${SITENAME}
  setfacl --mask --modify user:www-data:rwX,group:www-data:rwX ${OPSROOT}/bunch/${SITENAME}
  setfacl --mask --set default:user:www-data:rwX,default:group:www-data:rwX ${OPSROOT}/bunch/${SITENAME}

  # Профилактически создаём директории, используемые в конфигурации
  mkdir -p /etc/ssl/nginx
  mkdir -p /etc/letsencrypt

  # Создаём директории конфигурационных файлов
  mkdir -p ${OPSROOT}/bunch/${SITENAME}/etc/memcached
  mkdir -p ${OPSROOT}/bunch/${SITENAME}/etc/mongodb
  mkdir -p ${OPSROOT}/bunch/${SITENAME}/etc/mysql
  mkdir -p ${OPSROOT}/bunch/${SITENAME}/etc/nginx
  mkdir -p ${OPSROOT}/bunch/${SITENAME}/etc/php

  # Копируем во временные директории монтирования глобальную конфигурацию
  cp -a /usr/local/etc/devops/conf/bunch/etc ${OPSROOT}/bunch/${SITENAME} 2>/dev/null || true

  # Пробуем скопировать во временные директории монтирования точечную конфигурацию
  cp -a /usr/local/etc/devops/conf/${SITENAME}/etc ${OPSROOT}/bunch/${SITENAME} 2>/dev/null || true

  # Перекрываем параметры доступа файлов настроек общепринятыми для тестовых стендов
  chown -R www-data:www-data ${OPSROOT}/bunch/${SITENAME}/etc

  # Передаём директории конфигураций некоторых приложений во владение их специфичных пользователей
  chown -R 999:999 ${OPSROOT}/bunch/${SITENAME}/etc/mysql
  chown -R 999:999 ${OPSROOT}/bunch/${SITENAME}/etc/mongodb

  # Создаём директории данных
  mkdir -p ${OPSROOT}/bunch/${SITENAME}/var/www
  mkdir -p ${OPSROOT}/bunch/${SITENAME}/var/www/log
  mkdir -p ${OPSROOT}/bunch/${SITENAME}/${SITEROOT}

  mkdir -p ${OPSROOT}/bunch/${SITENAME}/var/log

  # Создаём директории для локальных файловых сокетов
  # (переводим директории во владение пользователей, в контексте которых работают сервисы внутри контейнеров)
  mkdir -p ${OPSROOT}/bunch/${SITENAME}/var/run/memcached
  chown 11211 ${OPSROOT}/bunch/${SITENAME}/var/run/memcached
  mkdir -p ${OPSROOT}/bunch/${SITENAME}/var/run/mongodb
  chown 999 ${OPSROOT}/bunch/${SITENAME}/var/run/mongodb
  mkdir -p ${OPSROOT}/bunch/${SITENAME}/var/run/mysqld
  chown 999 ${OPSROOT}/bunch/${SITENAME}/var/run/mysqld
  mkdir -p ${OPSROOT}/bunch/${SITENAME}/var/run/php
  chown 33 ${OPSROOT}/bunch/${SITENAME}/var/run/php

  # Запрещаем посторонним доступ в структуру временных директорий монтирования
  chmod -R o-rw ${OPSROOT}/bunch/${SITENAME}
  setfacl --recursive --mask --set default:other:--X ${OPSROOT}/bunch/${SITENAME}

return ${?}
}

# Функция организации SFTP-доступа к данным внутри контейнеров тестового стенда
function bunch-sftp-enable {

  # Перебираем всех возможно объявленных SFTP-пользователей
  for SFTP_USER_ITEM in "${SFTP_USER[@]}" ; do

    # Проверяем наличие указанного пользователя в несущей ОС
    id ${SFTP_USER_ITEM} > /dev/null 2>&1
    if [ "${?}" -eq "0" ] ; then

      # Пробуем создать директорию для "chroot"-а
      [ -d "/var/opt/devops/chroot/${SFTP_USER_ITEM}" ] || mkdir -p /var/opt/devops/chroot/${SFTP_USER_ITEM}
      if [ "${?}" -eq "0" ] ; then

        # Профилактически приводим разрешения доступа иерархии "chroot"-а к требуемым OpenSSH-SFTP
        chown root /var/opt/devops && chmod go-w /var/opt/devops
        chown root /var/opt/devops/chroot && chmod go-w /var/opt/devops/chroot
        chown root:developer /var/opt/devops/chroot/${SFTP_USER_ITEM}
        chmod go-w /var/opt/devops/chroot/${SFTP_USER_ITEM}

        # Пробуем создать директорию, служащую опорной точкой для монтирования
        [ -d "/var/opt/devops/chroot/${SFTP_USER_ITEM}/${SITENAME}" ] || mkdir -p /var/opt/devops/chroot/${SFTP_USER_ITEM}/${SITENAME}
        if [ "${?}" -eq "0" ] ; then

          # Проверяем, не смонтирована ли уже файловая система
          if [ "$(mount | grep -c -i /var/opt/devops/chroot/${SFTP_USER_ITEM}/${SITENAME})" -eq "0" ] ; then

            # Монтируем зеркало (посредством FUSE) файловой системы тестируемого проекта в "chroot" пользователя
            chown ${SFTP_USER_ITEM}:www-data /var/opt/devops/chroot/${SFTP_USER_ITEM}/${SITENAME}
            bindfs --mirror-only=${SFTP_USER_ITEM} --perms=0770 --create-for-group=www-data ${OPSROOT}/bunch/${SITENAME} /var/opt/devops/chroot/${SFTP_USER_ITEM}/${SITENAME}
            if [ "${?}" -eq "0" ] ; then
              echo "$(cdate): Пользователю \"${SFTP_USER_ITEM}\" успешно предоставлен SFTP-доступ к файловой системе тестируемого проекта \"${SITENAME}\"." | tee -a "${LOG}"
            else
              FORTH=false
              echo "$(cdate): Сбой при монтировании файловой системы проекта \"${SITENAME}\" в точку \"/var/opt/devops/chroot/${SFTP_USER_ITEM}/${SITENAME}\"." | tee -a "${LOG}"
            fi
          else
            echo "$(cdate): Пользователь \"${SFTP_USER_ITEM}\" уже обладает SFTP-доступом к файловой системе тестируемого проекта \"${SITENAME}\"." | tee -a "${LOG}"
          fi
        else
          FORTH=false
          echo "$(cdate): Сбой при создании точки монтирования \"/var/opt/devops/chroot/${SFTP_USER_ITEM}/${SITENAME}\"." | tee -a "${LOG}"
        fi
      else
        FORTH=false
        echo "$(cdate): Сбой при создании chroot-директории \"/var/opt/devops/chroot/${SFTP_USER_ITEM}\"." | tee -a "${LOG}"
      fi
    else
      FORTH=false
      echo "$(cdate): Невозможно включить SFTP-доступ для пользователя \"${SFTP_USER_ITEM}\" - таковой отсутствует." | tee -a "${LOG}"
    fi
  done
  unset SFTP_USER_ITEM

return ${?}
}

# Функция выключения SFTP-доступа к данным внутри контейнеров тестового стенда
function bunch-sftp-disable {

  # Перебираем всех возможно объявленных SFTP-пользователей
  for SFTP_USER_ITEM in "${SFTP_USER[@]}" ; do

    # Проверяем наличие указанного пользователя в несущей ОС
    id ${SFTP_USER_ITEM} > /dev/null 2>&1
    if [ "${?}" -eq "0" ] ; then

      # Проверяем, смонтирована ли файловая система
      if [ "$(mount | grep -c -i /var/opt/devops/chroot/${SFTP_USER_ITEM}/${SITENAME})" -ne "0" ] ; then

        # Уничтожаем все процессы, взаимодействующие с высвобождаемыми файловыми ресурсами
        fuser -km "/var/opt/devops/chroot/${SFTP_USER_ITEM}/${SITENAME}"

        # Демонтируем из "croot" файловую систему тестируемого проекта
        umount -fl /var/opt/devops/chroot/${SFTP_USER_ITEM}/${SITENAME} > /dev/null 2>&1
        if [ "${?}" -eq "0" ]; then
          echo "$(cdate): Для пользователя \"${SFTP_USER_ITEM}\" успешно деактивирован SFTP-доступ к файловой системе тестируемого проекта \"${SITENAME}\"." | tee -a "${LOG}"
        else
          FORTH=false
          echo "$(cdate): Сбой при демонтировании файловой системы \"/var/opt/devops/chroot/${SFTP_USER_ITEM}/${SITENAME}\"." | tee -a "${LOG}"
        fi

        # Удаляем иерархию от точки монтирования (профилактическая зачистка)
        rm -rf "/var/opt/devops/chroot/${SFTP_USER_ITEM}/${SITENAME}"

      fi
    else
      FORTH=false
      echo "$(cdate): Невозможно отключить SFTP-доступ для пользователя \"${SFTP_USER_ITEM}\" - таковой отсутствует." | tee -a "${LOG}"
    fi
  done
  unset SFTP_USER_ITEM

return ${?}
}

# Функция загрузки данных от рабочего сервера к тестовому стенду
function bunch-scp-download {

  # Загружаем с исходного web-сервера данные во временные директории монтирования
  echo "$(cdate): Ожидаем загрузки (посредством SSH+TAR+GZip) файлов данных с исходного сервера \"${SCP_FQDN_SRC}\"..." | tee -a "${LOG}"
  front-nginx-log-show

  # Перебираем все возможно объявленные исходные директории
  for I in "${!SCP_DIR_SRC[@]}" ; do

    # Если целевая директория явно не задана, то принимаем её одинаковой с исходной
    [ -z "${SCP_DIR_TGT[$I]}" ] && { SCP_DIR_TGT[$I]=${SCP_DIR_SRC[$I]}; }

    # Пробуем создать целевую локальную директорию
    [ -d "${OPSROOT}/bunch/${SITENAME}/${SCP_DIR_TGT[$I]}" ] || { mkdir -p ${OPSROOT}/bunch/${SITENAME}/${SCP_DIR_TGT[$I]} && chown www-data:www-data ${OPSROOT}/bunch/${SITENAME}/${SCP_DIR_TGT[$I]}; }
    if [ "${?}" -eq "0" ] ; then

      # Вариант копирования с однопоточным сжатием GZip
      # sudo -u "${SCP_SSH_USER_SRC}" ssh -q -o StrictHostKeyChecking=no ${SCP_FQDN_SRC} "(GZIP=-7 cd ${SCP_DIR_SRC[$I]} && tar -zcf - ./)" | tar --warning=no-timestamp --warning=no-file-removed --totals -zxf - -C ${OPSROOT}/bunch/${SITENAME}/${SCP_DIR_TGT[$I]}

      # Вариант копирования с многопоточным сжатием PiGZ (требуется установка пакета "pigz")
      sudo -u "${SCP_SSH_USER_SRC}" ssh -q ${SCP_FQDN_SRC} "(cd ${SCP_DIR_SRC[$I]} && tar -cf - ./ | pigz -7 -p 3)" | tar --warning=no-timestamp --warning=no-file-removed --totals -zxf - -C ${OPSROOT}/bunch/${SITENAME}/${SCP_DIR_TGT[$I]}

      # Проверяем результат и делаем отметку в журнале событий
      if [ "${?}" -eq "0" ] ; then
        echo "$(cdate): Файлы данных успешно загружены в директорию \"${SCP_DIR_TGT[$I]}\" тестового стенда." | tee -a "${LOG}"

        # Самым простым способом обеспечиваем доступность данных пользователю по умолчанию (www-data)
        chown -R 33:33 ${OPSROOT}/bunch/${SITENAME}/${SCP_DIR_TGT[$I]}
        chmod -R ug+rw ${OPSROOT}/bunch/${SITENAME}/${SCP_DIR_TGT[$I]}
      else
        FORTH=false
        echo "$(cdate): Сбой при загрузке файлов данных в директорию \"${SCP_DIR_TGT[$I]}\" тестового стенда." | tee -a "${LOG}"
      fi
    else
      FORTH=false
      echo "$(cdate): Невозможно создать целевую директорию \"${OPSROOT}/bunch/${SITENAME}/${SCP_DIR_TGT[$I]}\"." | tee -a "${LOG}"
    fi
  done
  unset I

return ${?}
}

# Функция синхронизации файлов данных от рабочего сервера к тестовому стенду
function bunch-scp-sync {

  # Синхронизируем данные с исходного сервера в директории тестовой площадки
  echo "$(cdate): Ожидаем загрузки изменений (посредством SSH+RSync) файлов данных с исходного сервера \"${SCP_FQDN_SRC}\"..." | tee -a "${LOG}"
  front-nginx-log-show

  # Перебираем все возможно объявленные исходные директории
  for I in "${!SCP_DIR_SRC[@]}" ; do

    # Если целевая директория явно не задана, то принимаем её одинаковой с исходной
    [ -z "${SCP_DIR_TGT[$I]}" ] && { SCP_DIR_TGT[$I]=${SCP_DIR_SRC[$I]}; }

    # Пробуем создать целевую локальную директорию
    [ -d "${OPSROOT}/bunch/${SITENAME}/${SCP_DIR_TGT[$I]}" ] || { mkdir -p ${OPSROOT}/bunch/${SITENAME}/${SCP_DIR_TGT[$I]} && chown www-data:www-data ${OPSROOT}/bunch/${SITENAME}/${SCP_DIR_TGT[$I]}; }
    if [ "${?}" -eq "0" ] ; then

      # Вариант однопоточной синхронизации с сжатием GZip
      # (надёжнее и быстрее всего в RSync работает метод сравнения файлов посредством "MD5 checksum")
      rsync --stats --checksum --recursive --links --delete --compress --compress-level=7 -e "ssh -q -o StrictHostKeyChecking=no -i /home/${SCP_SSH_USER_SRC}/.ssh/id_rsa" ${SCP_SSH_USER_SRC}@${SCP_FQDN_SRC}:${SCP_DIR_SRC[$I]}/ ${OPSROOT}/bunch/${SITENAME}/${SCP_DIR_TGT[$I]}

      # Проверяем результат и делаем отметку в журнале событий
      if [ "${?}" -eq "0" ] ; then
        echo "$(cdate): Успешно синхронизированы файлы данных в директории \"${SCP_DIR_TGT[$I]}\" тестового стенда." | tee -a "${LOG}"

        # Самым простым способом обеспечиваем доступность данных пользователю по умолчанию (www-data)
        chown -R 33:33 ${OPSROOT}/bunch/${SITENAME}/${SCP_DIR_TGT[$I]}
        chmod -R ug+rw ${OPSROOT}/bunch/${SITENAME}/${SCP_DIR_TGT[$I]}
      else
        FORTH=false
        echo "$(cdate): Сбой при синхронизации файлов данных в директории \"${SCP_DIR_TGT[$I]}\" тестового стенда." | tee -a "${LOG}"
      fi
    else
      FORTH=false
      echo "$(cdate): Невозможно создать целевую директорию \"${OPSROOT}/bunch/${SITENAME}/${SCP_DIR_TGT[$I]}\"." | tee -a "${LOG}"
    fi
  done
  unset I

return ${?}
}

# Функция синхронизации файлов данных совместного использования от рабочего сервера к тестовому стенду
function bunch-scp-ext-sync {

  # Перебираем все возможно объявленные исходные директории совместного использования
  for I in "${!SCP_EXT_DIR_SRC[@]}" ; do

    # Если целевая директория явно не задана, то принимаем её одинаковой с исходной
    [ -z "${SCP_EXT_DIR_TGT[$I]}" ] && { SCP_EXT_DIR_TGT[$I]=${SCP_EXT_DIR_SRC[$I]}; }

    # Пробуем создать локальную директорию хранения файлов общего пользования
    [ -d "${OPSROOT}/share/${SCP_EXT_DIR_TGT[$I]}" ] || { mkdir -p ${OPSROOT}/share/${SCP_EXT_DIR_TGT[$I]} && chown www-data:www-data ${OPSROOT}/share/${SCP_EXT_DIR_TGT[$I]}; }
    if [ "${?}" -eq "0" ] ; then

      # Пробуем создать целевую локальную директорию
      [ -d "${OPSROOT}/bunch/${SITENAME}/${SCP_EXT_DIR_TGT[$I]}" ] || { mkdir -p ${OPSROOT}/bunch/${SITENAME}/${SCP_EXT_DIR_TGT[$I]} && chown www-data:www-data ${OPSROOT}/bunch/${SITENAME}/${SCP_EXT_DIR_TGT[$I]}; }
      if [ "${?}" -eq "0" ] ; then

        # Вариант однопоточной синхронизации с сжатием GZip
        # (надёжнее и быстрее всего в RSync работает метод сравнения файлов посредством "MD5 checksum")
        rsync --stats --checksum --recursive --links --delete --compress --compress-level=7 -e "ssh -q -o StrictHostKeyChecking=no -i /home/${SCP_SSH_USER_SRC}/.ssh/id_rsa" ${SCP_SSH_USER_SRC}@${SCP_FQDN_SRC}:${SCP_EXT_DIR_SRC[$I]}/ ${OPSROOT}/share/${SCP_EXT_DIR_TGT[$I]}

        # Проверяем результат и делаем отметку в журнале событий
        if [ "${?}" -eq "0" ] ; then
          echo "$(cdate): Успешно синхронизированы файлы данных общего пользования в директории хранения \"./share/${SCP_EXT_DIR_TGT[$I]}\"." | tee -a "${LOG}"

          # Самым простым способом обеспечиваем доступность данных пользователю по умолчанию (www-data)
          chown -R 33:33 ${OPSROOT}/share/${SCP_EXT_DIR_TGT[$I]}
          chmod -R ug+rw ${OPSROOT}/share/${SCP_EXT_DIR_TGT[$I]}

          # Связываем целевую директорию с директорией хранения файлов общего пользования
          #if [ "$(mount | grep -c -i ${OPSROOT}/bunch/${SITENAME}/${SCP_EXT_DIR_TGT[$I]})" -eq "0" ] ; then
          if [ "$(mount | grep -c -i $(echo ${OPSROOT}/bunch/${SITENAME}/${SCP_EXT_DIR_TGT[$I]} | sed 's#//#/#g'))" -eq "0" ] ; then
            mount --bind ${OPSROOT}/share/${SCP_EXT_DIR_TGT[$I]} ${OPSROOT}/bunch/${SITENAME}/${SCP_EXT_DIR_TGT[$I]}
            if [ "${?}" -eq "0" ] ; then
              echo "$(cdate): Файлы данных совместного использования успешно доставлены в целевую директорию \"${SCP_EXT_DIR_TGT[$I]}\" тестового стенда." | tee -a "${LOG}"
            else
              FORTH=false
              echo "$(cdate): Сбой при доставке файлов данных в целевую директорию \"${SCP_EXT_DIR_TGT[$I]}\" тестового стенда." | tee -a "${LOG}"
            fi
          fi
        else
          FORTH=false
          echo "$(cdate): Сбой при синхронизации файлов данных общего пользования в директории \"./share/${SCP_EXT_DIR_TGT[$I]}\"." | tee -a "${LOG}"
        fi
      else
        FORTH=false
        echo "$(cdate): Невозможно создать целевую локальную директорию \"${SCP_EXT_DIR_TGT[$I]}\"." | tee -a "${LOG}"
      fi
    else
      FORTH=false
      echo "$(cdate): Невозможно создать локальную директорию хранения файлов данных общего пользования \"./share/${SCP_EXT_DIR_TGT[$I]}\"." | tee -a "${LOG}"
    fi
  done
  unset I

return ${?}
}

# Функция загрузки данных из Git-репозитория для контейнеров тестового стенда
function bunch-git-download {

  # Пробуем создать временную директорию для загрузки данных
  rm -rf ${OPSROOT}/bunch/${SITENAME}/var/tmp/git
  mkdir -p ${OPSROOT}/bunch/${SITENAME}/var/tmp/git && chown "${GIT_SSH_USER_SRC}" ${OPSROOT}/bunch/${SITENAME}/var/tmp/git
  if [ "${?}" -eq "0" ] ; then

    # Загружаем с Git-репозитория данные во временные директории монтирования
    echo "$(cdate): Ожидаем загрузки (посредством SSH+Git) файлов из Git-репозитория \"${GIT_REPO_SRC}\"..." | tee -a "${LOG}"
    front-nginx-log-show
    # Загружаем только одну ветвь без истории (по состоянию на последний коммит)
    sudo -u "${GIT_SSH_USER_SRC}" XDG_CONFIG_HOME="${OPSROOT}/bunch/${SITENAME}/var/tmp/git/${GIT_BRANCH_SRC}" git clone --depth 1 --no-tags --branch ${GIT_BRANCH_SRC} "${GIT_REPO_SRC}" "${OPSROOT}/bunch/${SITENAME}/var/tmp/git/${GIT_BRANCH_SRC}"
    if [ "${?}" -eq "0" ] ; then

      # Копируем полученные из репозитория файлы в директорию данных с замещением имеющихся
      rm -rf "${OPSROOT}/bunch/${SITENAME}/var/tmp/git/${GIT_BRANCH_SRC}/.git"
      cp -rf "${OPSROOT}/bunch/${SITENAME}/var/tmp/git/${GIT_BRANCH_SRC}/"* "${OPSROOT}/bunch/${SITENAME}/${GIT_DIR_TGT}"

      # Проверяем результат и делаем отметку в журнале событий
      if [ "${?}" -eq "0" ] ; then
        echo "$(cdate): Файлы данных успешно загружены в директорию \"${GIT_DIR_TGT}\"." | tee -a "${LOG}"

        # Самым простым способом обеспечиваем доступность данных пользователю по умолчанию (www-data)
        chown -R 33:33 ${OPSROOT}/bunch/${SITENAME}/${GIT_DIR_TGT}
        chmod -R ug+rw ${OPSROOT}/bunch/${SITENAME}/${GIT_DIR_TGT}

        # Удаляем директорию с промежуточными данными
        rm -rf "${OPSROOT}/bunch/${SITENAME}/var/tmp/git/${GIT_BRANCH_SRC}"
      else
        FORTH=false
        echo "$(cdate): Сбой при загрузке файлов данных в \"${GIT_DIR_TGT}\"." | tee -a "${LOG}"
      fi
    else
      FORTH=false
      echo "$(cdate): Сбой при загрузке данных из Git-репозитория \"${GIT_REPO_SRC}\"." | tee -a "${LOG}"
    fi
  else
    FORTH=false
    echo "$(cdate): Невозможно создать целевую директорию \"${OPSROOT}/bunch/${SITENAME}/var/tmp/git\"." | tee -a "${LOG}"
  fi

return ${?}
}

# Функция загрузки данных из MySQL для контейнеров тестового стенда
function bunch-mysql-download {

  # Проверяем наличие обязательных переменных конфигурации
  if [ -z "${MYSQL_FQDN_SRC}" -o -z "${MYSQL_SSH_USER_SRC}" -o -z "${MYSQL_DB_SRC}" -o -z "${MYSQL_USER_SRC}" -o -z "${MYSQL_PASSWD_SRC}" ] ; then
    FORTH=false
    echo "$(cdate): Отсутствует необходимые для загрузки в контейнер \"bunch-${SITENAME}-mysql\" данных параметры подключения." | tee -a "${LOG}"
    return 1
  fi

  # Если целевые БД, имя пользователя и пароль явно не заданы, то принимаем их одинаковыми с исходными
  [ -z "${MYSQL_DB_TGT}" ] && { MYSQL_DB_TGT=${MYSQL_DB_SRC}; }
  [ -z "${MYSQL_USER_TGT}" ] && { MYSQL_USER_TGT=${MYSQL_USER_SRC}; }
  [ -z "${MYSQL_PASSWD_TGT}" ] && { MYSQL_PASSWD_TGT=${MYSQL_PASSWD_SRC}; }

  # Пытаемся обеспечить и проверить тем самым доступность удалённой исходной БД
  sudo -u "${MYSQL_SSH_USER_SRC}" ssh -q ${MYSQL_FQDN_SRC} "(echo -e \"[client]\nuser=${MYSQL_USER_SRC}\npassword=${MYSQL_PASSWD_SRC}\n\" > ./.mysql-${SITENAME}.cnf && chmod go-rwx ./.mysql-${SITENAME}.cnf && mysql --defaults-file=./.mysql-${SITENAME}.cnf -h localhost -u ${MYSQL_USER_SRC} ${MYSQL_DB_SRC} -e ';' 2>/dev/null)" 2>/dev/null
  if [ "${?}" -eq "0" ] ; then

    # Проверяем доступность локальной тестовой БД
    docker exec -i bunch-${SITENAME}-mysql mysql -h localhost -u ${MYSQL_USER_TGT} --password="${MYSQL_PASSWD_TGT}" ${MYSQL_DB_TGT} -e ";" 2>/dev/null
    if [ "${?}" -eq "0" ] ; then
      echo "$(cdate): Ожидаем загрузки (посредством SSH+SQL) данных с исходного MySQL-сервера \"${MYSQL_FQDN_SRC}\" и применения их на тестовой площадке..." | tee -a "${LOG}"
      front-nginx-log-show

      # Предварительно зачищаем применяемую базу данных, удаляя и пересоздавая её
      cat "${OPSROOT}/bunch/${SITENAME}/var/tmp/mysql/initdb.d/1.create-db-user.sql" | docker exec -i bunch-${SITENAME}-mysql mysql --defaults-file=/etc/mysql/${SITENAME}.cnf -h localhost -u root mysql

      # Одноэтапный простой SQL-дамп со сжатием GZip
      # sudo -u "${MYSQL_SSH_USER_SRC}" ssh -q -o StrictHostKeyChecking=no ${MYSQL_FQDN_SRC} "(mysqldump -h localhost -u ${MYSQL_USER_SRC} --password=${MYSQL_PASSWD_SRC} --no-tablespaces ${MYSQL_DB_SRC} | gzip -c -7)" | gzip -c -d | docker exec -i --env MYSQL_PWD="${MYSQL_ROOT_PASSWORD_TGT}" bunch-${SITENAME}-mysql mysql -h localhost -u root ${MYSQL_DB_TGT}

      # Одноэтапный слегка оптимизированный SQL-дамп
      # sudo -u "${MYSQL_SSH_USER_SRC}" ssh -q -o StrictHostKeyChecking=no ${MYSQL_FQDN_SRC} "mysqldump --no-tablespaces --add-drop-table --quick --disable-keys --extended-insert --single-transaction --skip-lock-tables --lock-tables=false -h localhost -u ${MYSQL_USER_SRC} --password=${MYSQL_PASSWD_SRC} ${MYSQL_DB_SRC}" | docker exec -i --env MYSQL_PWD="${MYSQL_ROOT_PASSWORD_TGT}" bunch-${SITENAME}-mysql mysql -h localhost -u root ${MYSQL_DB_TGT}

      # Трёхэтапный оптимизированный SQL-дамп с временным отключением индексов и сжатием GZip
      echo "SET GLOBAL AUTOCOMMIT=0; SET GLOBAL UNIQUE_CHECKS=0; SET GLOBAL FOREIGN_KEY_CHECKS=0;" | docker exec -i bunch-${SITENAME}-mysql mysql --defaults-file=/etc/mysql/${SITENAME}.cnf -h localhost -u root ${MYSQL_DB_TGT} \
      && sudo -u "${MYSQL_SSH_USER_SRC}" ssh -q ${MYSQL_FQDN_SRC} "(mysqldump --defaults-file=./.mysql-${SITENAME}.cnf --no-tablespaces --add-drop-database --add-drop-table --single-transaction --extended-insert --hex-blob --quick --disable-keys --skip-lock-tables --lock-tables=false -h localhost -u ${MYSQL_USER_SRC} ${MYSQL_DB_SRC} | gzip -c -5)" | gzip -c -d | docker exec -i bunch-${SITENAME}-mysql mysql --defaults-file=/etc/mysql/${SITENAME}.cnf -h localhost -u root ${MYSQL_DB_TGT} \
      && echo "SET GLOBAL AUTOCOMMIT=1; SET GLOBAL UNIQUE_CHECKS=1; SET GLOBAL FOREIGN_KEY_CHECKS=1; COMMIT;" | docker exec -i bunch-${SITENAME}-mysql mysql --defaults-file=/etc/mysql/${SITENAME}.cnf -h localhost -u root ${MYSQL_DB_TGT}

      # Проверяем результат и делаем отметку в журнале событий
      if [ "${?}" -eq "0" ] ; then
        echo "$(cdate): Данные MySQL успешно загружены в локальную тестовую БД \"${MYSQL_DB_TGT}\"." | tee -a "${LOG}"
      else
        FORTH=false
        echo "$(cdate): Сбой при загрузке данных в локальную тестовую БД \"${MYSQL_DB_TGT}\" MySQL." | tee -a "${LOG}"
      fi

    else
      FORTH=false
      echo "$(cdate): Невозможно подключение к локальной тестовой БД \"${MYSQL_DB_TGT}\"." | tee -a "${LOG}"
    fi

  else
    FORTH=false
    echo "$(cdate): Невозможно подключение к удалённой исходной БД \"${MYSQL_DB_SRC}\"." | tee -a "${LOG}"
  fi

  # При любом итоге пробуем зачистить следы деятельности
  sudo -u "${MYSQL_SSH_USER_SRC}" ssh -q ${MYSQL_FQDN_SRC} "(rm -f ./.mysql-${SITENAME}.cnf)" 2>/dev/null

return ${?}
}

# Функция загрузки данных из MongoDB для контейнеров тестового стенда
function bunch-mongodb-download {

  # Проверяем наличие обязательных переменных конфигурации
  if [ -z "${MONGODB_FQDN_SRC}" -o -z "${MONGODB_SSH_USER_SRC}" -o -z "${MONGODB_DB_SRC}" -o -z "${MONGODB_USER_SRC}" -o -z "${MONGODB_PASSWD_SRC}" ] ; then
    FORTH=false
    echo "$(cdate): Отсутствует необходимые для загрузки в контейнер \"bunch-${SITENAME}-mongodb\" данных параметры подключения." | tee -a "${LOG}"
    return 1
  fi

  # Если целевые БД, имя пользователя и пароль явно не заданы, то принимаем их одинаковыми с исходными
  [ -z "${MONGODB_DB_TGT}" ] && { MONGODB_DB_TGT=${MONGODB_DB_SRC}; }
  [ -z "${MONGODB_USER_TGT}" ] && { MONGODB_USER_TGT=${MONGODB_USER_SRC}; }
  [ -z "${MONGODB_PASSWD_TGT}" ] && { MONGODB_PASSWD_TGT=${MONGODB_PASSWD_SRC}; }

  # Проверяем доступность удалённой исходной БД
  sudo -u "${MONGODB_SSH_USER_SRC}" ssh -q ${MONGODB_FQDN_SRC} "mongo localhost/${MONGODB_DB_SRC} --username ${MONGODB_USER_SRC} --password \"${MONGODB_PASSWD_SRC}\" --eval \"db.getCollectionNames()\" > /dev/null 2>&1"
  if [ "${?}" -eq "0" ] ; then

    # Проверяем доступность локальной тестовой БД
    docker exec -i bunch-${SITENAME}-mongodb mongo localhost/admin --username root --password "${MONGODB_ROOT_PASSWORD_TGT}" --eval "db.adminCommand('listDatabases')" 2>/dev/null | grep -i "name" | grep -i -e "\"${MONGODB_DB_TGT}\"" > /dev/null 2>&1
    if [ "${?}" -eq "0" ] ; then
      echo "$(cdate): Ожидаем загрузки (посредством SSH+Cat) данных с исходного MongoDB-сервера \"${MONGODB_FQDN_SRC}\" и применения их на тестовой площадке..." | tee -a "${LOG}"
      front-nginx-log-show

      # Предварительно зачищаем применяемую базу данных, удаляя все её "коллекции данных"
      docker exec -i bunch-${SITENAME}-mongodb mongo localhost/${MONGODB_DB_TGT} --username ${MONGODB_USER_TGT} --password "${MONGODB_PASSWD_TGT}" --eval "db.getCollectionNames().forEach(function(c){db[c].drop()});" > /dev/null 2>&1

      # # К сожалению, у спарки "mongodump" и "mongorestore" имеются баги, практически исключающие их работу через unix-pipe - нельзя просто выкачивать данные и сразу заливать их в целевую БД - придётся вначале делать "дамп", загружать его файлом, а в контейнере раскрывать.

      # Выгрузка "дампа" локально с последующим копированием его на целевой сервер
      sudo -u "${MONGODB_SSH_USER_SRC}" ssh -q -o StrictHostKeyChecking=no ${MONGODB_FQDN_SRC} "( mkdir -p /var/tmp/mongodb && mongodump --host=localhost --username=\"${MONGODB_USER_SRC}\" --password=\"${MONGODB_PASSWD_SRC}\" --db=\"${MONGODB_DB_SRC}\" --gzip --archive=\"/var/tmp/mongodb/dump.${MONGODB_DB_SRC}.gz\" && cat \"/var/tmp/mongodb/dump.${MONGODB_DB_SRC}.gz\" && rm -f \"/var/tmp/mongodb/dump.${MONGODB_DB_SRC}.gz\" )" | cat > "${OPSROOT}/bunch/${SITENAME}/var/lib/mongodb/dump.${MONGODB_DB_SRC}.gz"

      # Применение полученного "дампа" в контейнере
      docker exec -i bunch-${SITENAME}-mongodb mongorestore -v --host=localhost --username=root --password="${MONGODB_ROOT_PASSWORD_TGT}" --nsInclude "${MONGODB_DB_SRC}.*" --gzip --archive="/var/lib/mongodb/dump.${MONGODB_DB_SRC}.gz" > /dev/null 2>&1
      rm -f "${OPSROOT}/bunch/${SITENAME}/var/lib/mongodb/dump.${MONGODB_DB_SRC}.gz"

    else
      FORTH=false
      echo "$(cdate): Невозможно подключение к локальной тестовой БД MongoDB \"${MONGODB_DB_TGT}\"." | tee -a "${LOG}"
    fi

  else
    FORTH=false
    echo "$(cdate): Невозможно подключение к удалённой исходной БД MongoDB \"${MONGODB_DB_SRC}\"." | tee -a "${LOG}"
  fi

return ${?}
}

# Функция запуска Shell-команд корректировки данных тестового стенда
function bunch-hook-exec {

  # Перебираем всех возможно объявленных SFTP-пользователей
  for HOOK_COMMAND_ITEM in "${HOOK_COMMAND[@]}" ; do

    # Элементарно пытаемся исполнить команду, сохранённую в переменной
    /bin/bash -c "${HOOK_COMMAND_ITEM}"
    if [ "${?}" -eq "0" ] ; then
      echo "$(cdate): Успешно исполнена hook-команда." | tee -a "${LOG}"
    else
      FORTH=false
      echo "$(cdate): Сбой при исполнении hook-команды." | tee -a "${LOG}"
    fi
  done

return ${?}
}

# Функция запуска контейнера Nginx для тестирования
function bunch-nginx-start {

  # Проверяем наличие обязательных переменных конфигурации
  if [ -z "${FQDN}" -o "${#FQDN[@]}" -eq "0" ] ; then
    FORTH=false
    echo "$(cdate): Отсутствует необходимая для запуска контейнера \"bunch-${SITENAME}-nginx\" конфигурация." | tee -a "${LOG}"
    return 1
  fi

  # Заранее создаём простейший HTML-файл, которым можно будет определить работоспособность тестового стенда
  [[ ! -f "${OPSROOT}/bunch/${SITENAME}/${SITEROOT}/index."* ]] && {
    echo "${SITENAME}" > ${OPSROOT}/bunch/${SITENAME}/${SITEROOT}/index.html
    chown www-data:www-data ${OPSROOT}/bunch/${SITENAME}/${SITEROOT}/index.html
  }

  # Перебираем все FQDN и каждому генерируем отдельный конфигурационный файл
  for I in "${!FQDN[@]}" ; do
    # Не генерируем конфигурационный файл, если в каком-то из имеющихся обнаружен текущий FQDN
    if [ ! -z "${FQDN[$I]}" ] && [[ ! `grep -ril -e "server_name.*\s*${FQDN[$I]}.*;" ${OPSROOT}/bunch/${SITENAME}/etc/nginx/conf.d 2>/dev/null` ]] ; then

      # Подставляем путь к файлу SSL-конфигурации, если таковой не задан индивидуально
      [ -z "${FQDN_SSL_CONF[$I]}" ] && { FQDN_SSL_CONF[$I]="ssl_wildcard.conf"; }

      # Подставляем путь к файлу ACL-конфигурации, если таковой не задан индивидуально
      [ -z "${FQDN_ACL_CONF[$I]}" ] && { FQDN_ACL_CONF[$I]="./acl_default.conf"; }

      # Пытаемся прочитать содержимое файла ACL-конфигурации
      if [ -f "${FQDN_ACL_CONF[$I]}" ] ; then
        FQDN_ACL_CONFIGURATION[$I]=$(cat "${FQDN_ACL_CONF[$I]}")
      elif [ -f "${OPSROOT}/bunch/${SITENAME}/etc/nginx/${FQDN_ACL_CONF[$I]}" ] ; then
        FQDN_ACL_CONFIGURATION[$I]=$(cat "${OPSROOT}/bunch/${SITENAME}/etc/nginx/${FQDN_ACL_CONF[$I]}")
      else
        FORTH=false
        echo "$(cdate): Сбой при наладке проксирования от фронтального web-прокси к контейнеру \"bunch-${SITENAME}-nginx\": не обнаружен файл ACL-конфигурации \"${FQDN_ACL_CONF[$I]}\"." | tee -a "${LOG}"
      fi

      # Создаём конфигурационный файл по следующему шаблону
      source /usr/local/etc/devops/lib/bunch-nginx-conf.sh.snippet
    fi
  done

  # Запускаем контейнер
  docker run --rm --name bunch-${SITENAME}-nginx \
    --network-alias bunch-${SITENAME}-nginx \
    --env TZ="`cat /etc/timezone`" \
    --env MODE_ENV="${MODE_ENV}" \
    -v /etc/ssl/nginx:/etc/ssl/nginx:ro \
    -v /etc/letsencrypt:/etc/letsencrypt:ro \
    -v ${OPSROOT}/bunch/${SITENAME}/etc/nginx:/etc/nginx:ro \
    -v ${OPSROOT}/bunch/${SITENAME}/var/run/php:/var/run/php:rw \
    -v ${OPSROOT}/bunch/${SITENAME}/var/www:/var/www:rw \
    --tmpfs /run --tmpfs /tmp \
    --network backnet-${SITENAME} \
    --detach \
    nginx:stable > /dev/null

  # Направляем вывод подсистемы журналирования контейнера в текстовый файл
  docker logs --follow bunch-${SITENAME}-nginx > /var/log/devops/bunch/${SITENAME}/output-nginx.log 2>&1 &

  # Проверяем успешность выполнения процедуры запуска
  if [ `docker ps | grep -c -i "bunch-${SITENAME}-nginx"` -ne 0 ] ; then
    echo "$(cdate): Контейнер \"bunch-${SITENAME}-nginx\" запущен." | tee -a "${LOG}"

    # Подключаем запущенный контейнер к транзитной связующей сети
    docker network connect vianet bunch-${SITENAME}-nginx
    if [ "${?}" -eq "0" ] ; then
      echo "$(cdate): Контейнер \"bunch-${SITENAME}-nginx\" включён в транзитную связующую сеть \"vianet\"." | tee -a "${LOG}"

      # Выясняем IP-адрес, автоматически выданный Nginx-контейнеру для транзитной связующей сети "vianet"
      VIAIPWOM=$(docker inspect bunch-${SITENAME}-nginx 2> /dev/null | jq -r '.[0].NetworkSettings.Networks["vianet"].IPAddress' | awk -F "/" '{print $1}')
      if [ ! -z "${VIAIPWOM}" -a "${VIAIPWOM}" != "null" ] ; then

        # Перебираем все FQDN и каждому генерируем отдельный конфигурационный файл для фронтального web-прокси
        for I in "${!FQDN[@]}" ; do
          if [ ! -z "${FQDN[$I]}" ] ; then

            # Подставляем путь к файлу SSL-конфигурации, если таковой не задан индивидуально
            [ -z "${FQDN_SSL_CONF[$I]}" ] && { FQDN_SSL_CONF[$I]="./ssl_wildcard.conf"; }

            # Пытаемся прочитать содержимое файла SSL-конфигурации
            if [ -f "${FQDN_SSL_CONF[$I]}" ] ; then
              FQDN_SSL_CONFIGURATION[$I]=$(cat "${FQDN_SSL_CONF[$I]}")
            elif [ -f "${OPSROOT}/bunch/${SITENAME}/etc/nginx/${FQDN_SSL_CONF[$I]}" ] ; then
              FQDN_SSL_CONFIGURATION[$I]=$(cat "${OPSROOT}/bunch/${SITENAME}/etc/nginx/${FQDN_SSL_CONF[$I]}")
            else
              FORTH=false
              echo "$(cdate): Сбой при наладке проксирования от фронтального web-прокси к контейнеру \"bunch-${SITENAME}-nginx\": не обнаружен файл SSL-конфигурации \"${FQDN_SSL_CONF[$I]}\"." | tee -a "${LOG}"
            fi

            # Создаём конфигурационный файл по следующему шаблону
            source /usr/local/etc/devops/lib/bunch-nginx-proxying.sh.snippet
          fi
        done

        # Указываем web-серверу фронтального контейнера принять обновлённую конфигурацию
        docker exec -i ${FRONTNAME} /bin/bash -c "nginx -s reload" > /dev/null 2>/dev/null
        if [ "${?}" -eq "0" ] ; then
          echo "$(cdate): Проксирование от фронтального web-прокси к контейнеру \"bunch-${SITENAME}-nginx\" успешно налажено." | tee -a "${LOG}"

          # Информируем об активации и доступности FQDN
          for FQDN_ITEM in "${FQDN[@]}" ; do
            echo "$(cdate): Подключения к тестовой площадке принимаются по адресу \"http[s]://${FQDN_ITEM}/\"." | tee -a "${LOG}"

            # Для полноты картины тестируем доступность извне доменного имени запускаемого тестового стенда
            FQDN_ITEM_IP=$(dig +short "${FQDN_ITEM}")
            if [ ! -z "${FQDN_ITEM_IP}" ] ; then
              if [ "$(ip address | grep -i -c ${FQDN_ITEM_IP})" -eq "0" ] ; then
                echo "$(cdate): IP-адрес сопоставленный с доменным именем \"${FQDN_ITEM}\" не ведёт на этот тестовый стенд!" | tee -a "${LOG}"
              fi
            else
              echo "$(cdate): Доменному имени \"${FQDN_ITEM}\" тестового стенда в DNS не сопоставлен IP-адрес!" | tee -a "${LOG}"
            fi
          done

        else
          FORTH=false
          echo "$(cdate): Сбой при наладке проксирования от фронтального web-прокси к контейнеру \"bunch-${SITENAME}-nginx\": невозможно применить конфигурацию на фронтальном web-прокси." | tee -a "${LOG}"
        fi
      else
        FORTH=false
        echo "$(cdate): Сбой при наладке проксирования от фронтального web-прокси к контейнеру \"bunch-${SITENAME}-nginx\": не получен внутренний IP-адрес контейнера в транзитной сети \"vianet\"." | tee -a "${LOG}"
      fi
    else
      FORTH=false
      echo "$(cdate): Сбой при включении контейнера \"bunch-${SITENAME}-nginx\" в транзитную сеть \"vianet\"." | tee -a "${LOG}"
    fi
  else
    FORTH=false
    echo "$(cdate): Сбой при запуске контейнера \"bunch-${SITENAME}-nginx\"." | tee -a "${LOG}"
  fi

return ${?}
}

# Функция остановки контейнера Nginx для тестирования
function bunch-nginx-stop {

  # Пробуем остановить контейнер
  docker stop bunch-${SITENAME}-nginx > /dev/null 2>&1
  if [ "$(docker ps | grep -c -i bunch-${SITENAME}-nginx)" -eq "0" ] ; then
    echo "$(cdate): Контейнер \"bunch-${SITENAME}-nginx\" остановлен." | tee -a "${LOG}"
  else
    FORTH=false
    echo "$(cdate): Сбой при остановке контейнера \"bunch-${SITENAME}-nginx\"." | tee -a "${LOG}"
  fi

return ${?}
}

# Функция запуска контейнера PHP-FPM для тестирования
function bunch-php-start {

  # Проверяем наличие обязательных переменных конфигурации
  if [ -z "${PHPFPM_VER}" -o ! -f ${OPSROOT}/bunch/${SITENAME}/etc/php/${PHPFPM_VER}/fpm/php.ini ] ; then
    FORTH=false
    echo "$(cdate): Отсутствует необходимая для запуска контейнера \"bunch-${SITENAME}-php\" конфигурация." | tee -a "${LOG}"
    return 1
  fi

  # Заранее создаём простейший PHP-скрипт, которым можно будет определить работоспособность тестового стенда
  [ ! -f "${OPSROOT}/bunch/${SITENAME}/${SITEROOT}/info.php" ] && {
    echo "<?php phpinfo(); ?>" > ${OPSROOT}/bunch/${SITENAME}/${SITEROOT}/info.php
    chown www-data:www-data ${OPSROOT}/bunch/${SITENAME}/${SITEROOT}/info.php
  }

  # Запускаем контейнер
  docker run --rm --name bunch-${SITENAME}-php \
    --network-alias bunch-${SITENAME}-php \
    --env TZ="`cat /etc/timezone`" \
    --env MODE_ENV="${MODE_ENV}" \
    -v ${OPSROOT}/bunch/${SITENAME}/etc/php:/etc/php:ro \
    -v ${OPSROOT}/bunch/${SITENAME}/etc/msmtprc:/etc/msmtprc:ro \
    -v ${OPSROOT}/bunch/${SITENAME}/var/run/php:/var/run/php:rw \
    -v ${OPSROOT}/bunch/${SITENAME}/var/run/memcached:/var/run/memcached:rw \
    -v ${OPSROOT}/bunch/${SITENAME}/var/run/mongodb:/var/run/mongodb:rw \
    -v ${OPSROOT}/bunch/${SITENAME}/var/run/mysqld:/var/run/mysqld:rw \
    -v ${OPSROOT}/bunch/${SITENAME}/var/www:/var/www:rw \
    --tmpfs /run --tmpfs /tmp \
    --tmpfs /var/lib/php/sessions \
    --network backnet-${SITENAME} \
    --detach \
    selfmade:php-${PHPFPM_VER}-fpm > /dev/null

  # Направляем вывод подсистемы журналирования контейнера в текстовый файл
  docker logs --follow bunch-${SITENAME}-php > /var/log/devops/bunch/${SITENAME}/output-php.log 2>&1 &

  # Проверяем успешность выполнения процедуры запуска
  if [ `docker ps | grep -c -i "bunch-${SITENAME}-php"` -ne 0 ] ; then
    echo "$(cdate): Контейнер \"bunch-${SITENAME}-php\" запущен." | tee -a "${LOG}"

    # Для совместимости с разнородными web-проектами дублируем файловые "сокеты" разноименными ссылками
    if [[ -S ${OPSROOT}/bunch/${SITENAME}/var/run/php/php-fpm.sock ]] ; then
      ln -P ${OPSROOT}/bunch/${SITENAME}/var/run/php/php-fpm.sock ${OPSROOT}/bunch/${SITENAME}/var/run/php/php${PHPFPM_VER}-fpm.sock 2>/dev/null
    fi

    # Пробуем применить возможно заданные для контейнера crontab-записи
    if [ "${#PHPFPM_CRON_COMMAND[@]}" -ge "1" ] ; then

      # Получаем действующий список задач и удаляем возможно уже имеющиеся crontab-записи для этого контейнера
      CRONTAB=$(crontab -u root -l)
      CRONTAB=`echo "${CRONTAB}" | sed -E "s/.*docker\s*exec.*bunch-${SITENAME}-php.*//gI"`

      # Перебираем возможно заданные crontab-записи и дополняем ими crontab-таблицу
      for I in "${!PHPFPM_CRON_COMMAND[@]}" ; do
        if [ ! -z "${PHPFPM_CRON_COMMAND[$I]}" -a ! -z "${PHPFPM_CRON_SCHEDULE[$I]}" ] ; then
          CRONTAB="${CRONTAB}\n${PHPFPM_CRON_SCHEDULE[$I]} docker exec -i --user www-data bunch-${SITENAME}-php /bin/bash -c \"${PHPFPM_CRON_COMMAND[$I]}\" >> ${OPSROOT}/bunch/${SITENAME}/var/log/crontab.log 2>&1 &"
        fi
      done

      # Сохраняем изменённый набор crontab-записей
      echo -e "${CRONTAB}" | crontab -u root -
      echo "$(cdate): Для контейнера \"bunch-${SITENAME}-php\" применены crontab-задачи." | tee -a "${LOG}"
    fi

  else
    FORTH=false
    echo "$(cdate): Сбой при запуске контейнера \"bunch-${SITENAME}-php\"." | tee -a "${LOG}"
  fi

return ${?}
}

# Функция остановки контейнера PHP-FPM для тестирования
function bunch-php-stop {

  # Пробуем остановить контейнер
  docker stop bunch-${SITENAME}-php > /dev/null 2>&1
  if [ "$(docker ps | grep -c -i bunch-${SITENAME}-php)" -eq "0" ] ; then
    echo "$(cdate): Контейнер \"bunch-${SITENAME}-php\" успешно остановлен." | tee -a "${LOG}"
  else
    FORTH=false
    echo "$(cdate): Сбой при остановке контейнера \"bunch-${SITENAME}-php\"." | tee -a "${LOG}"
  fi

return ${?}
}

# Функция запуска контейнера NodeJS для тестирования
function bunch-nodejs-start {

  # Проверяем наличие обязательных переменных конфигурации
  if [ -z "${NODEJS_VER}" ] ; then
    FORTH=false
    echo "$(cdate): Отсутствует необходимая для запуска контейнера \"bunch-${SITENAME}-nodejs\" конфигурация." | tee -a "${LOG}"
    return 1
  fi

  # Создаём необходимые директории и передаём их во владение пользователя, от имени которого запущены сервисы внутри контейнера
  [ -d "${OPSROOT}/bunch/${SITENAME}/${NODEJS_APP_DIR}" ] || mkdir -p "${OPSROOT}/bunch/${SITENAME}/${NODEJS_APP_DIR}"
  chown -R www-data:www-data  ${OPSROOT}/bunch/${SITENAME}/${NODEJS_APP_DIR}

  # Заранее создаём простейшее NodeJS-приложение, которым можно будет определить работоспособность тестового стенда
  [ ! -f "${OPSROOT}/bunch/${SITENAME}/${NODEJS_APP_DIR}/package.json" ] && {
    source /usr/local/etc/devops/lib/bunch-nodejs-example-app.sh.snippet
    chown -R www-data:www-data ${OPSROOT}/bunch/${SITENAME}/${NODEJS_APP_DIR}
  }

  # Запускаем контейнер
  docker run --rm --name bunch-${SITENAME}-nodejs \
    --network-alias bunch-${SITENAME}-nodejs \
    --env TZ="`cat /etc/timezone`" \
    --env MODE_ENV="${MODE_ENV}" \
    --env NODE_ENV="development" \
    --workdir ${NODEJS_APP_DIR} \
    -v ${OPSROOT}/bunch/${SITENAME}/var/run/memcached:/var/run/memcached:rw \
    -v ${OPSROOT}/bunch/${SITENAME}/var/run/mongodb:/var/run/mongodb:rw \
    -v ${OPSROOT}/bunch/${SITENAME}/var/run/mysqld:/var/run/mysqld:rw \
    -v ${OPSROOT}/bunch/${SITENAME}/var/www:/var/www:rw \
    --tmpfs /run --tmpfs /tmp \
    --network backnet-${SITENAME} \
    --user www-data \
    --detach \
    selfmade:nodejs-${NODEJS_VER} > /dev/null

  # Направляем вывод подсистемы журналирования контейнера в текстовый файл
  docker logs --follow bunch-${SITENAME}-nodejs > /var/log/devops/bunch/${SITENAME}/output-nodejs.log 2>&1 &

  # Проверяем успешность выполнения процедуры запуска
  if [ `docker ps | grep -c -i "bunch-${SITENAME}-nodejs"` -ne 0 ] ; then
    echo "$(cdate): Контейнер \"bunch-${SITENAME}-nodejs\" запущен." | tee -a "${LOG}"
  else
    FORTH=false
    echo "$(cdate): Сбой при запуске контейнера \"bunch-${SITENAME}-nodejs\"." | tee -a "${LOG}"
  fi

return ${?}
}

# Функция остановки контейнера NodeJS для тестирования
function bunch-nodejs-stop {

  # Пробуем остановить контейнер
  docker stop bunch-${SITENAME}-nodejs > /dev/null 2>&1
  if [ "$(docker ps | grep -c -i bunch-${SITENAME}-nodejs)" -eq "0" ] ; then
    echo "$(cdate): Контейнер \"bunch-${SITENAME}-nodejs\" остановлен." | tee -a "${LOG}"
  else
    FORTH=false
    echo "$(cdate): Сбой при остановке контейнера \"bunch-${SITENAME}-nodejs\"." | tee -a "${LOG}"
  fi

return ${?}
}

# Функция запуска контейнера Memcached для тестирования
function bunch-memcached-start {

  # Пробуем подготовить строку инициализации для Memcached
  while read LINE ; do
    VAR=`echo "${LINE}" | grep -E "^-" | tr -d '[:blank:]'`
    [ ! -z "${VAR}" ] && { MEMCACHED_VARS="${MEMCACHED_VARS} ${VAR}"; }
  done < ${OPSROOT}/bunch/${SITENAME}/etc/memcached/memcached.conf
  if [ "${?}" -ne "0" -o -z "${MEMCACHED_VARS}" ] ; then
    FORTH=false
    echo "$(cdate): Сбой при подготовке параметров инициализации для контейнера \"bunch-${SITENAME}-memcached\"." | tee -a "${LOG}"
    return 1
  fi

  # Официальный контейнер Memcached может создавать файловые сокеты только в "/tmp"
  # (подменяем общепринятые "/var/run" на "/tmp" с последующим перемонтированием снова в "/var/run")
  MEMCACHED_VARS=`echo "${MEMCACHED_VARS}" | sed -E 's#-s\s*(\/var\/run|\/run)\/memcached#-s \/tmp\/memcached#gI'`

  # Запускаем контейнер
  docker run --rm --name bunch-${SITENAME}-memcached \
    --network-alias bunch-${SITENAME}-memcached \
    --env TZ="`cat /etc/timezone`" \
    --env MODE_ENV="${MODE_ENV}" \
    -v ${OPSROOT}/bunch/${SITENAME}/var/run/memcached:/tmp/memcached:rw \
    --tmpfs /run --tmpfs /tmp \
    --network backnet-${SITENAME} \
    --detach \
    memcached:1.5 /bin/bash -c "mkdir -p /tmp/memcached && memcached ${MEMCACHED_VARS} logfile /dev/stdout" > /dev/null

  # Направляем вывод подсистемы журналирования контейнера в текстовый файл
  docker logs --follow bunch-${SITENAME}-memcached > /var/log/devops/bunch/${SITENAME}/output-memcached.log 2>&1 &

  # Проверяем успешность выполнения процедуры запуска
  if [ `docker ps | grep -c -i "bunch-${SITENAME}-memcached"` -ne 0 ] ; then
    echo "$(cdate): Контейнер \"bunch-${SITENAME}-memcached\" запущен." | tee -a "${LOG}"

  else
    FORTH=false
    echo "$(cdate): Сбой при запуске контейнера \"bunch-${SITENAME}-memcached\"." | tee -a "${LOG}"
  fi

return ${?}
}

# Функция остановки контейнера Memcached для тестирования
function bunch-memcached-stop {

  # Пробуем остановить контейнер
  docker stop bunch-${SITENAME}-memcached > /dev/null 2>&1
  if [ "$(docker ps | grep -c -i bunch-${SITENAME}-memcached)" -eq "0" ] ; then
    echo "$(cdate): Контейнер \"bunch-${SITENAME}-memcached\" остановлен." | tee -a "${LOG}"
  else
    FORTH=false
    echo "$(cdate): Сбой при остановке контейнера \"bunch-${SITENAME}-memcached\"." | tee -a "${LOG}"
  fi

return ${?}
}

# Функция запуска контейнера MongoDB для тестирования
function bunch-mongodb-start {

  # Проверяем наличие обязательных переменных конфигурации
  if [ -z "${MONGODB_ROOT_PASSWORD_TGT}" ] ;then
    FORTH=false
    echo "$(cdate): Без пароля суперпользователя СУБД запуск контейнера \"bunch-${SITENAME}-mongodb\" невозможен." | tee -a "${LOG}"
    return 1
  fi

  # Создаём необходимые директории и передаём их во владение пользователя, от имени которого запущены сервисы внутри контейнера
  [ -d "${OPSROOT}/bunch/${SITENAME}/var/lib/mongodb" ] || mkdir -p ${OPSROOT}/bunch/${SITENAME}/var/lib/mongodb
  [ -d "${OPSROOT}/bunch/${SITENAME}/var/tmp/mongodb/initdb.d" ] || mkdir -p ${OPSROOT}/bunch/${SITENAME}/var/tmp/mongodb/initdb.d
  chown -R 999:999 ${OPSROOT}/bunch/${SITENAME}/var/lib/mongodb
  chown -R 999:999 ${OPSROOT}/bunch/${SITENAME}/var/tmp/mongodb/initdb.d

  # Если целевые БД, имя пользователя и пароль явно не заданы, то принимаем их одинаковыми с исходными
  [ -z "${MONGODB_DB_TGT}" ] && { MONGODB_DB_TGT=${MONGODB_DB_SRC}; }
  [ -z "${MONGODB_USER_TGT}" ] && { MONGODB_USER_TGT=${MONGODB_USER_SRC}; }
  [ -z "${MONGODB_PASSWD_TGT}" ] && { MONGODB_PASSWD_TGT=${MONGODB_PASSWD_SRC}; }

  # При наличии параметров подключения к пользовательской БД пробуем создать Bash-скрипт для инициализации аккаунта и БД в MongoDB
  if [ ! -z "${MONGODB_DB_TGT}" -a ! -z "${MONGODB_USER_TGT}" -a ! -z "${MONGODB_PASSWD_TGT}" ] ; then
    # (Bash-скрипт впоследствии автоматически запускается внутри контейнера)
    cat << EOS | sed "s/^[ \t]*//" > ${OPSROOT}/bunch/${SITENAME}/var/tmp/mogodb/initdb.d/1.create-db-user.sh
      # Creating in MongoDB a database and user as its owner:
      mongo localhost/admin --username root --password "${MONGODB_ROOT_PASSWORD_TGT}" --eval "db=db.getSiblingDB('${MONGODB_DB_TGT}');db.createUser({user:'${MONGODB_USER_TGT}',pwd:'${MONGODB_PASSWD_TGT}',roles:[{role:'readWrite',db:'${MONGODB_DB_TGT}'}]});db.init.insert({sitename:'${MONGODB_DB_TGT}'})"
EOS
# (indicator strictly on beginning line)
    if [ "${?}" -ne "0" ] ; then
      FORTH=false
      echo "$(cdate): Сбой при подготовке init-скрипта для контейнера \"bunch-${SITENAME}-mongodb\"." | tee -a "${LOG}"
      return 1
    fi
    chown 999 ${OPSROOT}/bunch/${SITENAME}/var/tmp/mongodb/initdb.d/1.create-db-user.sh
  fi

  # Запускаем контейнер
  docker run --rm --name bunch-${SITENAME}-mongodb \
    --network-alias bunch-${SITENAME}-mongodb \
    --env TZ="`cat /etc/timezone`" \
    --env MODE_ENV="${MODE_ENV}" \
    --env MONGO_INITDB_ROOT_USERNAME="root" \
    --env MONGO_INITDB_ROOT_PASSWORD="${MONGODB_ROOT_PASSWORD_TGT}" \
    -v ${OPSROOT}/bunch/${SITENAME}/var/tmp/mongodb/initdb.d:/docker-entrypoint-initdb.d:ro \
    -v ${OPSROOT}/bunch/${SITENAME}/etc/mongodb:/etc/mongodb:ro \
    -v ${OPSROOT}/bunch/${SITENAME}/var/lib/mongodb:/var/lib/mongodb:rw \
    -v ${OPSROOT}/bunch/${SITENAME}/var/run/mongodb:/var/run/mongodb:rw \
    --tmpfs /run --tmpfs /tmp \
    --network backnet-${SITENAME} \
    --detach \
    mongo:${MONGODB_VER} --config /etc/mongodb/mongod.conf > /dev/null

  # Направляем вывод подсистемы журналирования контейнера в текстовый файл
  docker logs --follow bunch-${SITENAME}-mongodb > /var/log/devops/bunch/${SITENAME}/output-mongodb.log 2>&1 &

  # Проверяем успешность выполнения процедуры запуска
  if [ `docker ps | grep -c -i "bunch-${SITENAME}-mongodb"` -ne 0 ] ; then
    echo "$(cdate): Контейнер \"bunch-${SITENAME}-mongodb\" запущен." | tee -a "${LOG}"

    # Циклический тест в ожидании возможности подключения к MongoDB
    echo -n "$(cdate): Ожидаем доступности сервиса \"MongoDB\"... " | tee -a "${LOG}"
    front-nginx-log-show
    unset FORTH_MONGODB
    for try in {1..30} ; do
      [[ $(docker exec -i bunch-${SITENAME}-mongodb mongo localhost/admin --username root --password "${MONGODB_ROOT_PASSWORD_TGT}" --eval "db.adminCommand('listDatabases')" 2>/dev/null | grep -i "name" | grep -i -c -e "\"${MONGODB_DB_TGT}\"") -eq "1" ]] && {
        FORTH_MONGODB=true
        break
      } || { FORTH_MONGODB=false; echo -n "#"; sleep 1; }
    done ; echo | tee -a "${LOG}"
    if [ "${FORTH_MONGODB}" != "true" ] ; then
      FORTH=false
      echo "$(cdate): Невозможно подключиться к MongoDB в контейнере \"bunch-${SITENAME}-mongodb\"." | tee -a "${LOG}"
      return 1
    fi

    # Разрешаем чтение и запись в файловые сокеты специфичным пользователю "www-data" и группе "www-data"
    setfacl --recursive --modify user:www-data:rwX,group:www-data:rwX ${OPSROOT}/bunch/${SITENAME}/var/run/mongodb

  else
    FORTH=false
    echo "$(cdate): Сбой при запуске контейнера \"bunch-${SITENAME}-mongodb\"." | tee -a "${LOG}"
  fi

return ${?}
}

# Функция остановки контейнера MongoDB для тестирования
function bunch-mongodb-stop {

  # Пробуем остановить контейнер
  docker stop bunch-${SITENAME}-mongodb > /dev/null 2>&1
  if [ "$(docker ps | grep -c -i bunch-${SITENAME}-mongodb)" -eq "0" ] ; then
    echo "$(cdate): Контейнер \"bunch-${SITENAME}-mongodb\" остановлен." | tee -a "${LOG}"
  else
    FORTH=false
    echo "$(cdate): Сбой при остановке контейнера \"bunch-${SITENAME}-mongodb\"." | tee -a "${LOG}"
  fi

return ${?}
}

# Функция запуска контейнера MySQL для тестирования
function bunch-mysql-start {

  # Проверяем наличие обязательных переменных конфигурации
  if [ -z "${MYSQL_ROOT_PASSWORD_TGT}" ] ;then
    FORTH=false
    echo "$(cdate): Без пароля суперпользователя СУБД запуск контейнера \"bunch-${SITENAME}-mysql\" невозможен." | tee -a "${LOG}"
    return 1
  fi

  # Создаём необходимые директории и передаём их во владение пользователя, от имени которого запущены сервисы внутри контейнера
  [ -d "${OPSROOT}/bunch/${SITENAME}/var/lib/mysql" ] || mkdir -p ${OPSROOT}/bunch/${SITENAME}/var/lib/mysql
  [ -d "${OPSROOT}/bunch/${SITENAME}/var/tmp/mysql/initdb.d" ] || mkdir -p ${OPSROOT}/bunch/${SITENAME}/var/tmp/mysql/initdb.d
  chown -R 999:33 ${OPSROOT}/bunch/${SITENAME}/var/lib/mysql
  chown -R 999:33 ${OPSROOT}/bunch/${SITENAME}/var/tmp/mysql/initdb.d

  # Укладываем пароль суперпользователя в специальном файле конфигурации, обеспечивая возможность подключения без указания пароля
  echo -e "[client]\nuser=root\npassword=\"${MYSQL_ROOT_PASSWORD_TGT}\"\n" > "${OPSROOT}/bunch/${SITENAME}/etc/mysql/${SITENAME}.cnf"
  chown 999 "${OPSROOT}/bunch/${SITENAME}/etc/mysql/${SITENAME}.cnf"
  chmod go-rwx "${OPSROOT}/bunch/${SITENAME}/etc/mysql/${SITENAME}.cnf"

  # Если целевые БД, имя пользователя и пароль явно не заданы, то принимаем их одинаковыми с исходными
  [ -z "${MYSQL_DB_TGT}" ] && { MYSQL_DB_TGT=${MYSQL_DB_SRC}; }
  [ -z "${MYSQL_USER_TGT}" ] && { MYSQL_USER_TGT=${MYSQL_USER_SRC}; }
  [ -z "${MYSQL_PASSWD_TGT}" ] && { MYSQL_PASSWD_TGT=${MYSQL_PASSWD_SRC}; }

  # При наличии параметров подключения к пользовательской БД пробуем создать SQL-скрипт для инициализации аккаунта и БД в MySQL
  if [ ! -z "${MYSQL_DB_TGT}" -a ! -z "${MYSQL_USER_TGT}" -a ! -z "${MYSQL_PASSWD_TGT}" ] ; then
    # (SQL-скрипт впоследствии автоматически запускается внутри контейнера)
    cat << EOS | sed "s/^[ \t]*//" > ${OPSROOT}/bunch/${SITENAME}/var/tmp/mysql/initdb.d/1.create-db-user.sql
      --
      -- Creating in MySQL a database and user as its owner:
      --
      DROP DATABASE IF EXISTS ${MYSQL_DB_TGT};
      CREATE DATABASE ${MYSQL_DB_TGT};
      GRANT ALL PRIVILEGES ON ${MYSQL_DB_TGT}.* TO '${MYSQL_USER_TGT}'@'%' IDENTIFIED BY '${MYSQL_PASSWD_TGT}';
      FLUSH PRIVILEGES;
EOS
# (indicator strictly on beginning line)
    if [ "${?}" -ne "0" ] ; then
      FORTH=false
      echo "$(cdate): Сбой при подготовке init-скрипта для контейнера \"bunch-${SITENAME}-mysql\"." | tee -a "${LOG}"
      return 1
    fi
    chown 999 ${OPSROOT}/bunch/${SITENAME}/var/tmp/mysql/initdb.d/1.create-db-user.sql
  fi

  # Запускаем контейнер
  docker run --rm --name bunch-${SITENAME}-mysql \
    --network-alias bunch-${SITENAME}-mysql \
    --env TZ="`cat /etc/timezone`" \
    --env MODE_ENV="${MODE_ENV}" \
    --env MYSQL_ROOT_PASSWORD="${MYSQL_ROOT_PASSWORD_TGT}" \
    -v ${OPSROOT}/bunch/${SITENAME}/var/tmp/mysql/initdb.d:/docker-entrypoint-initdb.d:ro \
    -v ${OPSROOT}/bunch/${SITENAME}/etc/mysql:/etc/mysql:ro \
    -v ${OPSROOT}/bunch/${SITENAME}/var/lib/mysql:/var/lib/mysql:rw \
    -v ${OPSROOT}/bunch/${SITENAME}/var/run/mysqld:/var/run/mysqld:rw \
    --tmpfs /run --tmpfs /tmp \
    --network backnet-${SITENAME} \
    --detach \
    mysql:5.7 > /dev/null

  # Направляем вывод подсистемы журналирования контейнера в текстовый файл
  docker logs --follow bunch-${SITENAME}-mysql > /var/log/devops/bunch/${SITENAME}/output-mysql.log 2>&1 &

  # Проверяем успешность выполнения процедуры запуска
  if [ `docker ps | grep -c -i "bunch-${SITENAME}-mysql"` -ne 0 ] ; then
    echo "$(cdate): Контейнер \"bunch-${SITENAME}-mysql\" запущен." | tee -a "${LOG}"

    # Циклический тест в ожидании возможности подключения к MySQL
    # (на запуск и конфигурирование приложения внутри контейнера может понадобиться до одной-двух минут)
    echo -n "$(cdate): Ожидаем доступности сервиса \"MySQL\"... " | tee -a "${LOG}"
    front-nginx-log-show
    unset FORTH_MYSQL
    for try in {1..300} ; do
      [[ $(docker exec -i bunch-${SITENAME}-mysql mysql --defaults-file=/etc/mysql/${SITENAME}.cnf -h localhost -u root -e "show databases;" 2>/dev/null | grep -v -i "database" | grep -i "mysql") ]] && {
        FORTH_MYSQL=true
        break
      } || { FORTH_MYSQL=false; echo -n "#"; sleep 1; }
    done ; echo | tee -a "${LOG}"
    if [ "${FORTH_MYSQL}" != "true" ] ; then
      FORTH=false
      echo "$(cdate): Невозможно подключиться к MySQL в контейнере \"bunch-${SITENAME}-mysql\"." | tee -a "${LOG}"
      return 1
    fi

    # Разрешаем чтение и запись в файловые сокеты специфичным пользователю "www-data" и группе "www-data"
    setfacl --recursive --modify user:www-data:rwX,group:www-data:rwX ${OPSROOT}/bunch/${SITENAME}/var/run/mysqld

  else
    FORTH=false
    echo "$(cdate): Сбой при запуске контейнера \"bunch-${SITENAME}-mysql\"." | tee -a "${LOG}"
  fi

return ${?}
}

# Функция остановки контейнера MySQL для тестирования
function bunch-mysql-stop {

  # Пробуем остановить контейнер
  docker stop bunch-${SITENAME}-mysql > /dev/null 2>&1
  if [ "$(docker ps | grep -c -i bunch-${SITENAME}-mysql)" -eq "0" ] ; then
    echo "$(cdate): Контейнер \"bunch-${SITENAME}-mysql\" остановлен." | tee -a "${LOG}"
  else
    FORTH=false
    echo "$(cdate): Сбой при остановке контейнера \"bunch-${SITENAME}-mysql\"." | tee -a "${LOG}"
  fi

return ${?}
}

# Функция запуска контейнера "phpMyAdmin" для тестирования
function bunch-pma-start {

  # Запускаем контейнер
  docker run --rm --name bunch-${SITENAME}-pma \
    --network-alias bunch-${SITENAME}-pma \
    --env TZ="`cat /etc/timezone`" \
    --env MODE_ENV="${MODE_ENV}" \
    --env PMA_HOST="bunch-${SITENAME}-mysql" \
    --env PMA_PORT=3306 \
    -v ${OPSROOT}/bunch/${SITENAME}/var/run/mysqld:/var/run/mysqld:rw \
    --tmpfs /run --tmpfs /tmp \
    --network backnet-${SITENAME} \
    --detach \
    phpmyadmin/phpmyadmin:latest > /dev/null

  # Направляем вывод подсистемы журналирования контейнера в текстовый файл
  docker logs --follow bunch-${SITENAME}-pma > /var/log/devops/bunch/${SITENAME}/output-pma.log 2>&1 &

  # Проверяем успешность выполнения процедуры запуска
  if [ `docker ps | grep -c -i "bunch-${SITENAME}-pma"` -ne 0 ] ; then
    echo "$(cdate): Контейнер \"bunch-${SITENAME}-pma\" запущен." | tee -a "${LOG}"
  else
    FORTH=false
    echo "$(cdate): Сбой при запуске контейнера \"bunch-${SITENAME}-pma\"." | tee -a "${LOG}"
  fi

return ${?}
}

# Функция остановки контейнера "phpMyAdmin" для тестирования
function bunch-pma-stop {

  # Пробуем остановить контейнер
  docker stop bunch-${SITENAME}-pma > /dev/null 2>&1
  if [ "$(docker ps | grep -c -i bunch-${SITENAME}-pma)" -eq "0" ] ; then
    echo "$(cdate): Контейнер \"bunch-${SITENAME}-pma\" остановлен." | tee -a "${LOG}"
  else
    FORTH=false
    echo "$(cdate): Сбой при остановке контейнера \"bunch-${SITENAME}-pma\"." | tee -a "${LOG}"
  fi

return ${?}
}


Заметки и комментарии к публикации:


Оставьте свой комментарий ( выразите мнение относительно публикации, поделитесь дополнительными сведениями или укажите на ошибку )