UMGUM.COM 

Автоматизация ( Автоматизация создания площадок сайтов web сервиса на базе "Nginx", PHP-FPM, "NodeJS", "MySQL", "MongoDB", "Memcached", "mSMTP", "inCron" и "Supervisord". )

21 ноября 2017  (обновлено 11 июня 2019)

OS: "Linux Debian 8/9", "Linux Ubuntu 16/18 LTS".
Applications: "Nginx", PHP-FPM, "NodeJS", "MySQL", "MongoDB", "Memcached", "mSMTP", "inCron", "Supervisord" и "Bash".

Задача: автоматизировать рутинные процедуры создания новых "сводных площадок" групп сайтов и сайтов как таковых, поэтапно описанные в предыдущей основной инструкции по развёртыванию web-сервиса.

Последовательность действий по созданию площадки web-сайта такова:

1. Создание "сводной площадки" группы сайтов.
2. Создание площадки web-сайта.
3. Создание (опционально) БД "MySQL".
4. Создание (опционально) БД "MongoDB".
5. Создание (опционально) аккаунта web-разработчика.

Я считаю, что вспомогательные скрипты должны писаться так, чтобы их запуск не вредил уже имеющейся конфигурации, которую возможно создали или доработали вручную. Практика годичной эксплуатации показала, что в данном случае мне это удалось - приводимые здесь скрипты лишь дополняют недостающее и профилактически поправляют права доступа к файлам, если структура web-площадки уже сформирована.


В последнее время я приспособился хранить свои инструменты в отдельной от системного окружения директории "/usr/local/etc" - поступим так и сейчас - создаём директорию для Bash-скриптов создания структур web-сайтов и шаблонов конфигурационных файлов для таковых:

# mkdir -p /usr/local/etc/webman
# chown -R root:root /usr/local/etc/webman
# chmod -R o-rwx /usr/local/etc/webman

Каждую операцию в скриптах я постарался подробно прокомментировать и в дополнительных сопутствующих пояснениях не вижу необходимости.

Скрипт создания "сводной площадки" группы сайтов.

# vi /usr/local/etc/webman/addwebgroup.sh && chmod ug+x /usr/local/etc/webman/addwebgroup.sh

#!/bin/bash

PHPCONF="/etc/php/7.2"
WEBROOT="/var/www"
GROUP=${1}
LOG="$(dirname ${0})/add.log"
DATE=$(date +"%Y-%m-%d %H:%M:%S")

# Проверяем наличие ожидаемых приложений и утилит
[ -x "$(command -v php-fpm7.2)" ] && [ -x "$(command -v supervisorctl)" ] && [ -x "$(command -v incrond)" ] && [ -x "$(command -v visudo)" ] && [ -x "$(command -v setfacl)" ] || { echo "Не обнаружен необходимый для работы набор приложений и утилит. Операция создания группы сайтов прервана."; exit 1; }

# Проверяем корректность вводимых данных и выводим подсказку в случае необходимости
# (если "группа сайтов" уже имеется - ничего страшного - профилактически перезаписываем и обновляем права доступа структуры директорий)
#
[ ! "${1}" ] && { echo "Ожидаемый формат: ${0} \"group\""; echo "Не указано название \"группы сайтов\". Операция создания \"группы сайтов\" прервана."; exit 1; }

# Пресекаем использование зарезервированных ресурсов
if [[ "${GROUP}" = "cgi-bin" || "${GROUP}" = "letsencrypt" ]] ; then
  echo "В качестве имени \"группы сайтов\" указано название зарезервированного ресурса. Операция создания \"группы сайтов\" прервана."; exit 1;
fi

echo >> "${LOG}"
echo "${DATE}:" >> "${LOG}"

# В профилактических целях пытаемся воссоздать корневую директорию ресурсов
mkdir -p ${WEBROOT}
chown root:root ${WEBROOT}
chmod go-w ${WEBROOT}
chmod go+rx ${WEBROOT}
#
setfacl --no-mask --set default:user::rwX,default:group::rwX,default:other:--- ${WEBROOT}
setfacl --no-mask --modify group::rX,other:rX ${WEBROOT}

# Создаём файловую структуру "группы сайтов"
mkdir -p ${WEBROOT}/${GROUP}
#
setfacl --no-mask --modify user::rX,group::rX ${WEBROOT}/${GROUP}
setfacl --no-mask --modify user:www-data:X ${WEBROOT}/${GROUP}
#
mkdir -p ${WEBROOT}/${GROUP}/conf
mkdir -p ${WEBROOT}/${GROUP}/home
mkdir -p ${WEBROOT}/${GROUP}/log
mkdir -p ${WEBROOT}/${GROUP}/mnt
mkdir -p ${WEBROOT}/${GROUP}/tmp

# Проверяем успешность создания опорной файловой структуры
if [ -d "${WEBROOT}/${GROUP}" ] ; then
  echo "Успешно создана опорная файловая структура для группы сайтов \"${GROUP}\"." | tee -a "${LOG}"
else
  echo "В процессе создания файловой структуры случился сбой. Операция добавления группы сайтов \"${GROUP}\" прервана." | tee -a "${LOG}"
  exit 1
fi

# Добавляем опорного пользователя "группы сайтов"
groupadd --force www-${GROUP} > /dev/null 2>&1
useradd --shell /bin/false --no-create-home --home-dir ${WEBROOT}/${GROUP}/home --gid www-${GROUP} www-${GROUP} > /dev/null 2>&1

# Переводим файловую структуру "группы сайтов" во владение опорного пользователя
chown -R www-${GROUP}:www-${GROUP} ${WEBROOT}/${GROUP}

# Удовлетворяем требования OpenSSH-сервера (поддержка "chroot" в SFTP)
chown root:www-${GROUP} ${WEBROOT}/${GROUP}
chmod g+rx ${WEBROOT}/${GROUP}

# Добавляем пользователя, от имени которого будут осуществляться операции с Git-репозитариями
useradd --shell /usr/bin/git-shell --create-home --home-dir /home/git-${GROUP} --gid www-${GROUP} git-${GROUP} > /dev/null 2>&1

# Подготовим место для SSH-ключей пользователя операций Git-репозитариев
mkdir -p /home/git-${GROUP}/.ssh
touch /home/git-${GROUP}/.ssh/authorized_keys
chown -R git-${GROUP}:www-${GROUP} /home/git-${GROUP}/.ssh
chmod -R go-rwx /home/git-${GROUP}/.ssh

# Проверяем в "sshd_config" наличие правил для "группы сайтов" и добавляем их при необходимости
if [[ "$(grep -i -c -e "match\b\sgroup\b\swww-${GROUP}\b" "/etc/ssh/sshd_config")" -eq "0" ]] ; then
  source "$(dirname ${0})/sftp.sh.snippet"
  [ "${?}" -ne "0" ] && { echo "Template file SSHd is missing or wrong! Операция добавления группы сайтов \"${GROUP}\" прервана." | tee -a "${LOG}"; exit 1; }

  # Тестируем конфигурацию и перезапускаем сервис
  sshd -t
  if [ "${?}" -eq "0" ] ; then
    /etc/init.d/ssh reload
    echo "В конфигурационный файл SSHd успешно внесены настройки для группы сайтов \"${GROUP}\"." | tee -a "${LOG}"
  else
    echo "На этапе внесения изменений в файл конфигурации SSHd \"/etc/ssh/sshd_config\" случился сбой. Операция добавления группы сайтов \"${GROUP}\" прервана." | tee -a "${LOG}"
    exit 1
  fi
fi

# Проверяем наличие настроек PHP-FPM для "группы сайтов" и добавляем их при необходимости
if [ ! -f ${PHPCONF}/fpm/pool.d/${GROUP}.conf ] ; then
  mkdir -p ${PHPCONF}/fpm/pool.d
  source "$(dirname ${0})/php-fpm.sh.snippet"
  [ "${?}" -ne "0" ] && { echo "Template file PHP-FPM is missing or wrong! Операция добавления группы сайтов \"${GROUP}\" прервана." | tee -a "${LOG}"; exit 1; }

  # Тестируем конфигурацию и перезапускаем сервис
  php-fpm7.2 -t --fpm-config ${PHPCONF}/fpm/pool.d/${GROUP}.conf
  if [ "${?}" -eq "0" ] ; then
    /etc/init.d/php7.2-fpm reload
    echo "В конфигурацию "пулов" PHP-FPM успешно внесены настройки для группы сайтов \"${GROUP}\"." | tee -a "${LOG}"
  else
    echo "На этапе внесения изменений в файл конфигурации PHP-FPM \"${PHPCONF}/fpm/pool.d/${GROUP}.conf\" случился сбой. Операция добавления группы сайтов \"${GROUP}\" прервана." | tee -a "${LOG}"
    exit 1
  fi
else
  echo "Обнаруженный имеющийся файл конфигурации PHP-FPM \"${PHPCONF}/fpm/pool.d/${GROUP}.conf\" оставлен без изменений." | tee -a "${LOG}"
fi

# Проверяем наличие настроек "Supervisor + Memcached" для "группы сайтов" и добавляем их при необходимости
if [ ! -f /etc/supervisor/conf.d/memcached-${GROUP}.conf ] ; then
  mkdir -p /etc/supervisor/conf.d
  source "$(dirname ${0})/memcached.sh.snippet"
  [ "${?}" -ne "0" ] && { echo "Template file Supervisor-Memcached is missing or wrong! Операция добавления группы сайтов \"${GROUP}\" прервана." | tee -a "${LOG}"; exit 1; }

  # Применяем изменения конфигурации
  supervisorctl reread > /dev/null 2>&1 && supervisorctl update > /dev/null 2>&1
  if [ "${?}" -eq "0" ] ; then
    echo "В конфигурацию \"Supervisor-Memcached\" успешно внесены настройки для группы сайтов \"${GROUP}\"." | tee -a "${LOG}"
  else
    echo "На этапе внесения изменений в файл конфигурации \"/etc/supervisor/conf.d/memcached-${GROUP}.conf\" для \"Supervisor-Memcached\" случился сбой. Операция добавления группы сайтов \"${GROUP}\" прервана." | tee -a "${LOG}"
    exit 1
  fi
else
  echo "Обнаруженный имеющийся файл конфигурации Supervisor-Memcached \"/etc/supervisor/conf.d/memcached-${GROUP}.conf\" оставлен без изменений." | tee -a "${LOG}"
fi

# Если поддерживается экспорт "кронтабов" посредством "inCron", то добавляем в отслеживание новую таблицу
if [ -d "/etc/incron.d" ] ; then
  touch "/etc/incron.d/web-crontab-export"
  if [[ "$(grep -i -c -e "${WEBROOT}/${GROUP}/conf\b" "/etc/incron.d/web-crontab-export")" -eq "0" ]] ; then
    source "$(dirname ${0})/incron.sh.snippet" > /dev/null 2>&1
    [ "${?}" -ne "0" ] && { echo "Template file inCron is missing or wrong!" | tee -a "${LOG}"; exit 1; }

    # Создаём файл "кронтаба" в файловой структуре "группы сайтов"
    crontab -u www-${GROUP} -l > ${WEBROOT}/${GROUP}/conf/crontab 2> /dev/null
    chown www-${GROUP}:www-${GROUP} ${WEBROOT}/${GROUP}/conf/crontab
    chmod g+w ${WEBROOT}/${GROUP}/conf/crontab
    chmod o-rwx ${WEBROOT}/${GROUP}/conf/crontab
    echo "В конфигурационный файл inCron внесены настройки автоматического экспорта \"${WEBROOT}/${GROUP}/conf/crontab\" для группы сайтов \"${GROUP}\"." | tee -a "${LOG}"
  else
    echo "Обнаруженная имеющаяся конфигурация "inCron" оставлена без изменений." | tee -a "${LOG}"
  fi
fi

# Разрешаем web-серверу переключаться в контекст пользователя, от имени которого будут осуществляться операции с Git-репозитариями
touch /etc/sudoers.d/web-git-deploy-users
if [[ $(grep -i -c -e "^www-data\b\s.*git-${GROUP}:www-${GROUP}.*NOPASSWD:.*/usr/bin/git\b" "/etc/sudoers.d/web-git-deploy-users") -eq "0" ]] ; then

  # В профилактических целях корректируем ограничения доступа к файлу конфигурации
  chown root:root /etc/sudoers.d/web-git-deploy-users
  chmod go-rwx /etc/sudoers.d/web-git-deploy-users

  # Вносим добавлением к возможно уже имеющимся разрешительное правило
  echo "www-data ALL=(git-${GROUP}:www-${GROUP}) NOPASSWD: /usr/bin/git" >> /etc/sudoers.d/web-git-deploy-users

  # Тестируем конфигурацию
  visudo -cf /etc/sudoers.d/web-git-deploy-users
  if [ "${?}" -eq "0" ] ; then
    echo "В конфигурационный файл SUDO успешно внесены разрешительные правила \"автоматизации деплоя\" для группы сайтов \"${GROUP}\"." | tee -a "${LOG}"
  else
    echo "На этапе внесения изменений в файл конфигурации SUDO \"/etc/sudoers.d/web-git-deploy-users\" случился сбой. Операция добавления группы сайтов \"${GROUP}\" прервана." | tee -a "${LOG}"; exit 1;
  fi
else
  echo "Обнаруженная имеющаяся конфигурация \"автоматизации деплоя\" в SUDO оставлена без изменений." | tee -a "${LOG}"
fi

exit ${?}

Используемый в скрипте создания "сводной площадки" шаблон блока конфигурации для SSHd-сервера:

# vi /usr/local/etc/webman/sftp.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).

cat << EOF >> /etc/ssh/sshd_config

# Запираем пользователей "группы сайтов" в пределах директории их web-ресурсов
Match Group www-${GROUP} User *
  AllowTcpForwarding no
  ChrootDirectory ${WEBROOT}/${GROUP}
  ForceCommand internal-sftp -u 0007
EOF

Используемый в скрипте создания "сводной площадки" шаблон блока конфигурации для сервиса PHP-FPM:

# vi /usr/local/etc/webman/php-fpm.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).

cat << EOF > ${PHPCONF}/fpm/pool.d/${GROUP}.conf
; Блок описания отдельного инстанса PHP-FPM
[${GROUP}]

; Указываем запускать инстанс от имени основного пользователя - владельца сайта
user = www-${GROUP}
group = www-${GROUP}

; Задаём точку приёма FCGI-запросов от шлюза (Nginx в нашем случае)
listen = /var/run/php/fpm-${GROUP}.sock

; Явно задаём владельца точки входа FCGI-запросов и разрешения для доступа к ней web-сервера
listen.owner = www-data
listen.group = www-data
listen.mode = 0600

; Режим запуска инстанса
pm = dynamic
pm.start_servers = 20
pm.max_children = 512
pm.min_spare_servers = 5
pm.max_spare_servers = 20
pm.max_requests = 4096

; Очень полезный журнал запросов, блокирующих ресурсы среднестатистически длительное время
slowlog = ${WEBROOT}/${GROUP}/log/php-fcgi.slow.log
; Порог реакции на время (три секунды) исполнения PHP-скрипта, после которого событие запишется в журнал "slowlog"
request_slowlog_timeout = 3s

; Блок переопределения в среде исполнения PHP-FPM глобальных параметров PHP-интерпретатора
php_admin_value[sys_temp_dir] = ${WEBROOT}/${GROUP}/tmp
php_admin_value[post_max_size] = 128M
php_admin_value[upload_max_filesize] = 128M
php_admin_flag[allow_url_fopen] = On
EOF

Используемый в скрипте создания "сводной площадки" шаблон блока конфигурации для комбинации сервисов "Supervisord" и "Memcached":

# vi /usr/local/etc/webman/memcached.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).

cat << EOF > /etc/supervisor/conf.d/memcached-${GROUP}.conf
# Блок описания отдельного инстанса Supervisor-Memcached
[program:memcached-${GROUP}]

user=www-${GROUP}
command=/usr/bin/memcached -m 128 -c 1024 -t 4 -s /var/run/memcached/memcached-${GROUP}.sock -a 770 -v
stdout_logfile=/var/log/supervisor/memcached-${GROUP}.log
redirect_stderr=true
autostart=true
autorestart=true
stopsignal=KILL
numprocs=1
EOF

Используемый в скрипте создания "сводной площадки" шаблон блока конфигурации для комбинации сервисов "inCron" и "Crontab":

# vi /usr/local/etc/webman/incron.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).

cat << EOF >> /etc/incron.d/web-crontab-export

${WEBROOT}/${GROUP}/conf IN_CREATE /etc/init.d/incron reload &
${WEBROOT}/${GROUP}/conf/crontab IN_MODIFY,IN_CLOSE_WRITE,IN_MOVE crontab -u www-${GROUP} \$@
EOF

Скрипт создания площадки web-сайта.

# vi /usr/local/etc/webman/addwebsite.sh && chmod ug+x /usr/local/etc/webman/addwebsite.sh

#!/bin/bash

NGINXCONF="/etc/nginx"
WEBROOT="/var/www"
GROUP=${1}
SITE=${2}
LOG="$(dirname ${0})/add.log"
DATE=$(date +"%Y-%m-%d %H:%M:%S")

# Проверяем наличие ожидаемых приложений и утилит
[ -x "$(command -v nginx)" ] && [ -x "$(command -v setfacl)" ] || { echo "Не обнаружен необходимый для работы набор приложений и утилит. Операция создания сайта прервана."; exit 1; }

# Проверяем корректность вводимых данных и выводим подсказку в случае необходимости
# (если сайт уже имеется - ничего страшного - профилактически перезаписываем структуру директорий)
#
[ ! "${1}" ] && { echo "Ожидаемый формат: ${0} \"group\" \"domain.name\""; echo "Не указано название \"группы сайтов\". Операция создания сайта прервана."; exit 1; }
[ ! -d "${WEBROOT}/${GROUP}" ] && { echo "Указанная \"группа сайтов\" отсутствует. Операция создания сайта прервана."; exit 1; }
#
[ ! "${2}" ] && { echo "Ожидаемый формат: ${0} \"${GROUP}\" \"domain.name\""; echo "Не указано имя сайта. Операция создания сайта прервана."; exit 1; }
[ $(echo "${SITE}" | grep -i -c -e "^www\.") -ne "0" ] && { echo "Имя сайта необходимо указывать без префикса \"www.\". Операция создания сайта прервана."; exit 1; }

# Проверяем наличие сайта в других "группах сайтов" и прерываем работу при обнаружении такового в другом месте
[ $(find ${WEBROOT} -maxdepth 2 -type d -name "${SITE}" ! -path "${WEBROOT}/${GROUP}*" | wc -l) -ne "0" ] && { echo "Одноимённый сайт обнаружен в другой \"группе сайтов\". Операция создания сайта прервана."; exit 1; }

echo >> "${LOG}"
echo "${DATE}:" >> "${LOG}"

# Создаём файловую структуру для сайта
mkdir -p ${WEBROOT}/${GROUP}/${SITE}
mkdir -p ${WEBROOT}/${GROUP}/${SITE}/nodejs
mkdir -p ${WEBROOT}/${GROUP}/${SITE}/www

# Запрещаем изменение корневой структуры ресурса
setfacl --no-mask --modify user::rX,group::rX,other:--- ${WEBROOT}/${GROUP}/${SITE}

# Явно разрешаем доступ на чтение web-серверу (к существующим и создаваемым в будущем ресурсам)
setfacl --no-mask --modify user:www-data:X ${WEBROOT}/${GROUP}/${SITE}
setfacl --recursive --no-mask --modify user:www-data:rX ${WEBROOT}/${GROUP}/${SITE}/www
setfacl --recursive --no-mask --modify default:user:www-data:rX ${WEBROOT}/${GROUP}/${SITE}/www

# Создаём страницу-заглушку
if [ $(find "${WEBROOT}/${GROUP}/${SITE}/www" -maxdepth 1 -type f -iname "index.*" | wc -l) -eq 0 ] ; then
  echo "${SITE}" > "${WEBROOT}/${GROUP}/${SITE}/www/index.html"
fi

# Переводим файловую структуру сайта во владение опорного пользователя
chown -R www-${GROUP}:www-${GROUP} ${WEBROOT}/${GROUP}/${SITE}
chmod -R ug+rw ${WEBROOT}/${GROUP}/${SITE}
setfacl --recursive --modify user::rwX,group::rwX,other:--- ${WEBROOT}/${GROUP}/${SITE}

# Проверяем успешность создания опорной файловой структуры
if [ -d "${WEBROOT}/${GROUP}/${SITE}/www" ] ; then
  echo "Успешно создана опорная файловая структура для сайта \"${SITE}\"." | tee -a "${LOG}"
else
  echo "В процессе создания файловой структуры случился сбой. Операция добавления сайта \"${SITE}\" прервана." | tee -a "${LOG}"
  exit 1
fi

# Проверяем наличие настроек Nginx для сайта и добавляем их при необходимости
if [ ! -f ${NGINXCONF}/sites-available/${SITE}.conf ] ; then
  mkdir -p ${NGINXCONF}/sites-available
  mkdir -p ${NGINXCONF}/sites-enabled
  source "$(dirname ${0})/nginx.sh.snippet"
  [ "${?}" -ne "0" ] && { echo "Template file Nginx is missing or wrong! Операция добавления сайта прервана." | tee -a "${LOG}"; exit 1; }
  ln -s ${NGINXCONF}/sites-available/${SITE}.conf ${NGINXCONF}/sites-enabled/${SITE}.conf

  # Тестируем конфигурацию и перезапускаем сервис
  nginx -t
  if [ "${?}" -eq "0" ] ; then
    /etc/init.d/nginx reload
    echo "Успешно внесены изменения в конфигурацию сайтов Nginx." | tee -a "${LOG}"
  else
    echo "На этапе внесения изменений в файл конфигурации Nginx \"${NGINXCONF}/sites-available/${SITE}.conf\" случился сбой. Операция добавления сайта прервана." | tee -a "${LOG}"
    exit 1
  fi
else
  echo "Обнаружен имеющийся файл конфигурации Nginx \"${NGINXCONF}/sites-available/${SITE}.conf\" - оставлен без изменений." | tee -a "${LOG}"
fi

# Профилактически обновляем установки разрешений на доступ к файлам журналов событий
chown www-${GROUP}:www-${GROUP} /var/www/${GROUP}/log/*
chmod g+rw /var/www/${GROUP}/log/*

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

exit ${?}

Используемый в скрипте создания площадки web-сайта шаблон блока конфигурации для web-сервера "Nginx":

# vi /usr/local/etc/webman/nginx.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).

cat << EOF > ${NGINXCONF}/sites-available/${SITE}.conf
# Блок отлова HTTP-обращений и перенаправления их на HTTPS
server {
  listen 80;
  server_name www.${SITE} ${SITE};
  location / {
    rewrite ^ https://${SITE}\$request_uri permanent;
  }
}

# Блок отлова обращений по префиксу "www." и перенаправления их на "Canonical URL"
server {
  listen 443 ssl http2;
  server_name www.${SITE};
  include /etc/nginx/ssl_wildcard.conf;
  location / {
    rewrite ^ https://${SITE}\$request_uri permanent;
  }
}

# Описание параметров web-сайта как такового
server {

  # Указываем сетевые порты и протоколы для приёма клиентских подключений
  listen 443 ssl http2;

  # Перечисляем обслуживаемые FQDN сайта
  server_name ${SITE};

  access_log /var/www/${GROUP}/log/nginx-${SITE}-access.log;
  error_log /var/www/${GROUP}/log/nginx-${SITE}-error.log;

  # Принудительно переводим сайт на работу только через SSL
  ssl on;
  include /etc/nginx/ssl_wildcard.conf;

  # Выключаем невостребованную перекодировку контента
  charset off;

  # Задаём переменную с многократно используемым параметром PHP-FPM
  set \$php_pass unix:/var/run/php/php-fpm-${GROUP}.sock;

  root /var/www/${GROUP}/${SITE}/www;
  index index.html index.htm index.phtml index.php;

  # Подставляем свои страницы обработки ошибок
  error_page 403 /403.html;
  error_page 404 /404.html;
  error_page 500 502 503 504 /50x.html;

  # ACL фильтрующий клиентские IP
  #allow 10.0.0.0/8;
  #allow 172.16.0.0/12;
  #allow 192.168.0.0/16;
  #deny all;

  # Глобальный обработчик запросов
  location / {
    # Обработчик событий отсутствия запрашиваемого файла
    try_files \$uri \$uri/ =404;
  }

  # Блокируем доступ к типовым "закрытым" ресурсам
  location ~* (/\.ht|/\.hg|/\.git|/\.svn|/\.subversion|/\.inc|/\.sys|/\.local|/\.env|/\.enabled|/\.config|/\.profile) {
    deny all;
    log_not_found off;
    access_log off;
  }

  # Блокируем доступ к ресурсам, которые часто забывают спрятать
  location ~* (/conf|/cnf|/inc|/log/|/tmp/|/temp/|/runtime|/back|/bkp|/bak|/old|/test|/protected|/base|/database|/exchange|/phpshell|/shell|/cli|/bin|\.sql|\.py|\.perl|\.tpl|\.sh|\.bash|\.dist|\.orig|\.back|\.bak|\.conf|phpinfo.php) {
    deny all;
    log_not_found off;
  }

  # Блокируем доступ к классически неправильно и опасно именованным файлам (вроде ".php.1")
  location ~* \.(phtml|php)(?!(\?|\/|\$)) {
    deny all;
    log_not_found off;
  }

  # Обработчик прямых обращений к PHP-скриптам
  location ~* \.(phtml|php)\$ {
    try_files \$uri =404;
    include /etc/nginx/fastcgi_params;
    fastcgi_pass \$php_pass;
    fastcgi_param SCRIPT_FILENAME \$document_root\$fastcgi_script_name;
  }

  # Не реагируем на неинтересные события загрузок
  location = /(favicon.ico|robots.txt|sitemap.xml) {
    log_not_found off;
    access_log off;
  }

  # Напрямую отдаём "статические" данные, предлагая браузеру сохранить их в своём "кеше", и не фиксируем эти события
  location ~* ^.+\.(jpg|jpeg|gif|png|svg|js|css|mp3|ogg|mpe?g|avi|zip|gz|bz2?|rar|swf|xml|txt)\$ {
    try_files \$uri =404;
    expires 30d;
    access_log off;
  }
}
EOF

Скрипт создания "базы данных" web-сайта в СУБД "MySQL".

# vi /usr/local/etc/webman/addwebmysql.sh && chmod ug+x /usr/local/etc/webman/addwebmysql.sh

#!/bin/bash

WEBROOT="/var/www"
GROUP=${1}
SITE=${2}
MSITE=$(echo ${SITE} | sed 's/\./_/g' | sed 's/\-/_/g')
MPASSWORD=$(/usr/bin/pwgen --capitalize --numerals --secure 12 1)
LOG="$(dirname ${0})/add.log"
DATE=$(date +"%Y-%m-%d %H:%M:%S")

# Проверяем наличие ожидаемых приложений и утилит
[ -x "$(command -v mysql)" ] || { echo "Не обнаружен необходимый для работы набор приложений и утилит. Операция создания "баз данных" MySQL для сайта прервана."; exit 1; }

# Проверяем корректность вводимых данных и выводим подсказку в случае необходимости
[ ! "${1}" ] && { echo "Ожидаемый формат: ${0} \"group\" \"domain.name\""; echo "Не указано название \"группы сайтов\". Операция создания БД MySQL для сайта прервана"; exit 1; }
[ ! -d "${WEBROOT}/${GROUP}" ] && { echo "Указанная \"группа сайтов\" отсутствует. Операция создания БД MySQL для сайта прервана."; exit 1; }
#
[ ! "${2}" ] && { echo "Ожидаемый формат: ${0} \"${GROUP}\" \"domain.name\""; echo "Не указано имя сайта. Операция создания БД MySQL для сайта прервана"; exit 1; }
[ $(find ${WEBROOT}/${GROUP}/* -maxdepth 1 -type d -name "${SITE}" | wc -l) -eq "0" ] && { echo "Указанный сайт отсутствует. Операция создания БД MySQL для сайта прервана."; exit 1; }

echo >> "${LOG}"
echo "${DATE}:" >> "${LOG}"

# Проверяем наличие БД MySQL для сайта и добавляем таковую только при необходимости
if [ $(mysql -h localhost -u root -e "show databases" | grep -i -c -e "^${MSITE}$") -eq "0" ] ; then

  # Создаём БД
  mysql -h localhost -u root -e "create database if not exists ${MSITE}"
  if [ "${?}" -eq "0" ] ; then
    echo "Успешно создана БД MySQL \"${MSITE}\" для сайта \"${SITE}\"." | tee -a "${LOG}"
  else
    echo "На этапе создания БД MySQL \"${MSITE}\" случился сбой. Операция создания базы данных сайта \"${SITE}\" прервана."
    exit 1
  fi
else
  echo "Создаваемая БД MySQL \"${MSITE}\" уже существует."
fi

# Проверяем наличие пользователя БД MySQL для сайта и добавляем такового только при необходимости
if [ $(mysql -h localhost -u root -e "select user from mysql.user" | grep -i -c -e "^${MSITE}$") -eq "0" ] ; then

  # Добавляем пользователя БД сайта
  mysql -h localhost -u root -e "grant alter,create,create temporary tables,delete,drop,index,insert,lock tables,references,select,update on ${MSITE}.* to '${MSITE}'@'localhost' identified by '${MPASSWORD}'"
  if [ "${?}" -eq "0" ] ; then
    # Для скорейшего принятия СУБД новых параметров пользовательских привилегий отдаём команду явно их обновить
    mysql -h localhost -u root -e "flush privileges"

    # Распечатываем сведения о созданной БД
    echo
    echo "Успешно создан пользователь БД MySQL для сайта \"${SITE}\":" | tee -a "${LOG}"
    echo "  MySQL Server: localhost" | tee -a "${LOG}"
    echo "  MySQL DB: ${MSITE}" | tee -a "${LOG}"
    echo "  MySQL User: ${MSITE}" | tee -a "${LOG}"
    echo "  MySQL Password: ${MPASSWORD}" | tee -a "${LOG}"
    echo
  else
    echo "На этапе создания пользователя \"${MSITE}\" БД MySQL для сайта \"${SITE}\" случился сбой. Операция создания пользователя базы данных сайта прервана."
    exit 1
  fi
else
  echo "Создаваемый пользователь \"${MSITE}\" БД MySQL уже существует."
fi

exit ${?}

Скрипт создания "базы данных" web-сайта в СУБД "MongoDB".

# vi /usr/local/etc/webman/addwebmongodb.sh && chmod ug+x /usr/local/etc/webman/addwebongobd.sh

#!/bin/bash

WEBROOT="/var/www"
GROUP=${1}
SITE=${2}
MSITE=$(echo ${SITE} | sed 's/\./_/g' | sed 's/\-/_/g')
MPASSWORD=$(/usr/bin/pwgen --capitalize --numerals --secure 12 1)
LOG="$(dirname ${0})/add.log"
DATE=$(date +"%Y-%m-%d %H:%M:%S")

# Проверяем наличие ожидаемых приложений и утилит
[ -x "$(command -v mongo)" ] && [ -x "$(command -v pwgen)" ] || { echo "Не обнаружен необходимый для работы набор приложений и утилит. Операция создания "баз данных" MongoDB для сайта прервана."; exit 1; }

# Проверяем корректность вводимых данных и выводим подсказку в случае необходимости
[ ! "${1}" ] && { echo "Ожидаемый формат: ${0} \"group\" \"domain.name\""; echo "Не указано название \"группы сайтов\". Операция создания БД MongoDB для сайта прервана"; exit 1; }
[ ! -d "${WEBROOT}/${GROUP}" ] && { echo "Указанная \"группа сайтов\" отсутствует. Операция создания БД MongoDB для сайта прервана."; exit 1; }
#
[ ! "${2}" ] && { echo "Ожидаемый формат: ${0} \"${GROUP}\" \"domain.name\""; echo "Не указано имя сайта. Операция создания БД MongoDB для сайта прервана"; exit 1; }
[ $(find ${WEBROOT}/${GROUP}/* -maxdepth 1 -type d -name "${SITE}" | wc -l) -eq "0" ] && { echo "Указанный сайт отсутствует. Операция создания БД MongoDB для сайта прервана."; exit 1; }

echo >> "${LOG}"
echo "${DATE}:" >> "${LOG}"

# Регистрируем временный "алиас" для упрощения синтаксиса дальнейших команд
alias mongo='mongo --quiet localhost/admin --username root --password "`cat /root/.mongodb`"'
shopt -s expand_aliases

# Проверяем наличие БД MongoDB для сайта и добавляем таковую только при необходимости
if [ $(mongo --eval "db.adminCommand('listDatabases')" | grep -i "name" | grep -i -c -e "\"${MSITE}\"") -eq "0" ] ; then

  # Создаём БД
  if [ $(mongo --eval "db=db.getSiblingDB('${MSITE}');db.init.insert({sitename:'${MSITE}'})" | grep -i "nInserted" | grep -i -c "1") -eq "1" ] ; then
    echo "Успешно создана БД MongoDB \"${MSITE}\" для сайта \"${SITE}\"." | tee -a "${LOG}"
  else
    echo "На этапе создания БД MongoDB \"${MSITE}\" случился сбой. Операция создания базы данных сайта \"${SITE}\" прервана."
    exit 1
  fi
else
  echo "Создаваемая БД MongoDB \"${MSITE}\" уже существует."
fi

# Проверяем наличие пользователя БД MongoDB для сайта и добавляем такового только при необходимости
if [ $(mongo --eval "db = db.getSiblingDB('${MSITE}') ; db.getUsers()" | grep -i "user" | grep -i -c -e "\"${MSITE}\"") -eq "0" ] ; then

  # Добавляем пользователя БД сайта
  if [ $(mongo --eval "db=db.getSiblingDB('${MSITE}');db.createUser({user:'${MSITE}',pwd:'${MPASSWORD}',roles:[{role:'readWrite',db:'${MSITE}'}]})" | grep -i -c "successfully") -eq "1" ] ; then

    # Распечатываем сведения о созданной БД
    echo
    echo "Успешно создан пользователь БД MongoDB для сайта \"${SITE}\":" | tee -a "${LOG}"
    echo "  MongoDB Server: localhost" | tee -a "${LOG}"
    echo "  MongoDB DB: ${MSITE}" | tee -a "${LOG}"
    echo "  MongoDB User: ${MSITE}" | tee -a "${LOG}"
    echo "  MongoDB Password: ${MPASSWORD}" | tee -a "${LOG}"
    echo
  else
    echo "На этапе создания пользователя \"${MSITE}\" БД MongoDB для сайта \"${SITE}\" случился сбой. Операция создания пользователя базы данных сайта прервана."
    exit 1
  fi
else
  echo "Создаваемый пользователь \"${MSITE}\" БД MongoDB уже существует."
fi

exit ${?}

Скрипт создания аккаунта web-разработчика.

# vi /usr/local/etc/webman/addwebuser.sh && chmod ug+x /usr/local/etc/webman/addwebuser.sh

#!/bin/bash

WEBROOT="/var/www"
GROUP=${1}
USER=${2}
PASSWORD=$(/usr/bin/pwgen --capitalize --numerals --secure 12 1)
LOG="$(dirname ${0})/add.log"
DATE=$(date +"%Y-%m-%d %H:%M:%S")

# Проверяем корректность вводимых данных и выводим подсказку в случае необходимости
[ ! "${1}" ] && { echo "Ожидаемый формат: ${0} \"group\" \"username\""; echo "Не указано название \"группы сайтов\". Операция создания пользователя прервана"; exit 1; }
[ ! -d "${WEBROOT}/${GROUP}" ] && { echo "Указанная \"группа сайтов\" отсутствует. Операция создания пользователя прервана"; exit 1; }
#
[ ! "${2}" ] && { echo "Ожидаемый формат: ${0} \"${GROUP}\" \"username\""; echo "Не указано имя пользователя. Операция создания такового прервана"; exit 1; }
[ $(echo "${USER}" | grep -i -c -e "^\(git\|www\)-") -ne "0" ] && { echo "Имя пользователя следует указывать без префиксов вроде \"git-\" или \"www-\". Операция создания пользователя прервана."; exit 1; }
[ $(echo "${USER}" | grep -i -c -e "-${GROUP}$") -ne "0" ] && { echo "Имя пользователя следует указывать без суффиксов вроде наименования группы \"${GROUP}\". Операция создания пользователя прервана."; exit 1; }

# Проверяем наличие пользователя и добавляем его только при необходимости
id ${USER}-${GROUP} > /dev/null 2>&1
if [ "${?}" -ne "0" ] ; then

  echo >> "${LOG}"
  echo "${DATE}:" >> "${LOG}"

  # Создаём пользователя для доступа к "группе сайтов"
  useradd --shell /bin/bash --no-create-home --home-dir ${WEBROOT}/${GROUP}/home --gid www-${GROUP} ${USER}-${GROUP}
  if [ "${?}" -eq "0" ] ; then
    # Задаём пароль созданной учётной записи
    echo "${USER}-${GROUP}:${PASSWORD}" | chpasswd

    # Явно вводим пользователя в группы доступа к необходимым ресурсам
    usermod --gid www-${GROUP} ${USER}-${GROUP} > /dev/null 2>&1
    usermod --append --groups www-data ${USER}-${GROUP} > /dev/null 2>&1

    # Распечатываем сведения о созданном пользователе
    echo
    echo "Успешно создан пользователь для группы сайтов \"${GROUP}\":" | tee -a "${LOG}"
    echo "  SFTP Server: $(hostname)" | tee -a "${LOG}"
    echo "  SFTP User: ${USER}-${GROUP}" | tee -a "${LOG}"
    echo "  SFTP Password: ${PASSWORD}" | tee -a "${LOG}"
    echo
  fi
else
  echo "Создаваемый пользователь \"${USER}-${GROUP}\" уже существует."
fi

exit ${?}

Пример использования.

Для создания нового сайта "site.example.net" в группе сайтов "group0" достаточно ввести следующие четыре команды:

# addwebgroup.sh group0
# addwebsite.sh group0 site.example.net
# addwebmysql.sh group0 site.example.net
# addwebmongodb.sh group0 site.example.net
# addwebuser.sh group0 userName

Напоминаю, что здесь приведены примеры автоматизации рутинных процедур создания новых "сводных площадок" групп сайтов и сайтов как таковых, поэтапно описанных в предыдущей основной инструкции по развёртыванию web-сервиса, и скрипты специально написаны так, чтобы не навредить при повторном запуске - они ничего не удаляют, лишь добавляя недостающее - таким образом их можно использовать в профилактических целях, восстанавливая возможно повреждённую опорную структуру сайта. Это означает, что конфигурационные файлы прикладных сервисов можно править так же и напрямую, без задействования приводимых здесь скриптов.


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


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