Реализация программной платформы защищённого NAS


    В предыдущей статье было описано проектирование программной платформы NAS.
    Настало время её реализовать.


    Проверка


    Обязательно, перед тем, как начинать проверьте работоспособность пула:


    zpool status -v

    Пул и все диски в нём должны быть ONLINE.


    Далее я предполагаю, что на предыдущем этапе всё было сделано по инструкции, и работает, либо вы сами хорошо понимаете, что делаете.


    Удобства


    Прежде всего, стоит позаботиться об удобном управлении, если вы этого не сделали с самого начала.
    Потребуются:


    • SSH-сервер: apt-get install openssh-server. Если вы не знаете, как настроить SSH, делать NAS на Linux пока рано можете почитать особенности его использования в данной статье, затем воспользоваться одним из мануалов.
    • tmux или screen: apt-get install tmux. Чтобы сохранять сессию при входах по SSH и использовать несколько окон.

    После установки SSH надо добавить пользователя, чтобы не заходить через SSH под root (вход по умолчанию отключен и не надо его включать):


    zfs create rpool/home/user
    adduser user
    cp -a /etc/skel/.[!.]* /home/user
    chown -R user:user /home/user

    Для удалённого администрирования это достаточный минимум.


    Тем не менее, пока нужно держать подключенными клавиатуру и монитор, т.к. ещё потребуется перезагружаться при обновлении ядра и для того, чтобы убедиться в том, что всё работает сразу после загрузки.


    Альтернативный вариант использовать Virtual KVM, который предоставляет IME. Там есть консоль, правда в моём случае она реализована в виде Java апплета, что не очень удобно.


    Настройка


    Подготовка кэша


    Насколько вы помните, в описанной мной конфигурации есть отдельный SSD под L2ARC, который пока не используется, но взят "на вырост".


    Необязательно, но желательно заполнить этот SSD случайными данными (в случае Samsung EVO всё-равно заполнится нулями после выполнения blkdiscard, но не на всех SSD так):


    dd if=/dev/urandom of=/dev/disk/by-id/ata-Samsung_SSD_850_EVO bs=4M && blkdiscard /dev/disk/by-id/ata-Samsung_SSD_850_EVO

    Отключение сжатия логов


    На ZFS и так используется сжатие, потому сжатие логов через gzip будет явно лишним.
    Выключаю:


    for file in /etc/logrotate.d/* ; do
      if grep -Eq "(^|[^#y])compress" "$file" ; then
        sed -i -r "s/(^|[^#y])(compress)/\1#\2/" "$file"
      fi
    done

    Обновление системы


    Тут всё просто:


    apt-get dist-upgrade --yes
    reboot

    Создание снэпшота для нового состояния


    После перезагрузки, чтобы зафиксировать новое рабочее состояние, надо переписать первый снэпшот:


    zfs destroy rpool/ROOT/debian@install
    zfs snapshot rpool/ROOT/debian@install

    Организация файловых систем


    Подготовка разделов для SLOG


    Первое, что нужно сделать с целью достижения нормальной производительности ZFS — это вынести SLOG на SSD.
    Напомню, что SLOG в используемой конфигурации дублируется на двух SSD: для него будут созданы устройства на LUKS-XTS поверх 4-го раздела каждой SSD:


    dd if=/dev/urandom of=/etc/keys/slog.key bs=1 count=4096
    
    cryptsetup --verbose --cipher "aes-xts-plain64:sha512" --key-size 512 --key-file /etc/keys/slog.key luksFormat /dev/disk/by-id/ata-Samsung_SSD_850_PRO-part4
    
    cryptsetup --verbose --cipher "aes-xts-plain64:sha512" --key-size 512 --key-file /etc/keys/slog.key luksFormat /dev/disk/by-id/ata-Micron_1100-part4
    
    echo "slog0_crypt1 /dev/disk/by-id/ata-Samsung_SSD_850_PRO-part4 /etc/keys/slog.key luks,discard" >> /etc/crypttab
    
    echo "slog0_crypt2 /dev/disk/by-id/ata-Micron_1100-part4 /etc/keys/slog.key luks,discard" >> /etc/crypttab

    Подготовка разделов для L2ARC и подкачки


    Сначала надо создать разделы под swap и l2arc:


    sgdisk -n1:0:48G -t1:8200 -c1:part_swap -n2::196G -t2:8200 -c2:part_l2arc /dev/disk/by-id/ata-Samsung_SSD_850_EVO

    Раздел подкачки и L2ARC будут зашифрованы на случайном ключе, т.к. после перезагрузки они не требуются и их всегда возможно создать заново.
    Поэтому в crypttab прописывается строка для шифрования/расшифрования разделов в plain режиме:


    echo swap_crypt /dev/disk/by-id/ata-Samsung_SSD_850_EVO-part1 /dev/urandom swap,cipher=aes-xts-plain64:sha512,size=512 >> /etc/crypttab
    
    echo l2arc_crypt /dev/disk/by-id/ata-Samsung_SSD_850_EVO-part2 /dev/urandom cipher=aes-xts-plain64:sha512,size=512 >> /etc/crypttab

    Затем нужно перезапустить демоны и включить подкачку:


    echo 'vm.swappiness = 10' >> /etc/sysctl.conf
    sysctl vm.swappiness=10
    systemctl daemon-reload
    systemctl start systemd-cryptsetup@swap_crypt.service
    echo /dev/mapper/swap_crypt none swap sw,discard 0 0 >> /etc/fstab
    swapon -av

    Т.к. активного использования подкачки на SSD не планируется, параметр swapiness, который умолчанию 60, нужно установить в 10.


    L2ARC на данном этапе ещё не используется, но раздел под него уже готов:


    $ ls /dev/mapper/
    control  l2arc_crypt root_crypt1  root_crypt2  slog0_crypt1  slog0_crypt2  swap_crypt  tank0_crypt0  tank0_crypt1  tank0_crypt2  tank0_crypt3

    Пулы tankN


    Будет описано создание пула tank0, tank1 создаётся по аналогии.


    Чтобы не заниматься созданием одинаковых разделов вручную и не допускать ошибок, я написал скрипт для создания шифрованных разделов под пулы:


    create_crypt_pool.sh
    #!/bin/bash
    
    KEY_SIZE=512
    POOL_NAME="$1"
    KEY_FILE="/etc/keys/${POOL_NAME}.key"
    LUKS_PARAMS="--verbose --cipher aes-xts-plain64:sha${KEY_SIZE} --key-size $KEY_SIZE"
    
    [ -z "$1" ] && { echo "Error: pool name empty!" ; exit 1; }
    
    shift
    
    [ -z "$*" ] && { echo "Error: devices list empty!" ; exit 1; }
    
    echo "Devices: $*"
    read -p "Is it ok? " a
    
    [ "$a" != "y" ] && { echo "Bye"; exit 1; }
    
    dd if=/dev/urandom of=$KEY_FILE bs=1 count=4096
    
    phrase="?"
    
    read -s -p "Password: " phrase
    echo
    read -s -p "Repeat password: " phrase1
    echo
    
    [ "$phrase" != "$phrase1" ] && { echo "Error: passwords is not equal!" ; exit 1; }
    
    echo "### $POOL_NAME" >> /etc/crypttab
    
    index=0
    
    for i in $*; do
      echo "$phrase"|cryptsetup $LUKS_PARAMS luksFormat "$i" || exit 1
      echo "$phrase"|cryptsetup luksAddKey "$i" $KEY_FILE || exit 1
      dev_name="${POOL_NAME}_crypt${index}"
      echo "${dev_name} $i $KEY_FILE luks" >> /etc/crypttab
      cryptsetup luksOpen --key-file $KEY_FILE "$i" "$dev_name" || exit 1
      index=$((index + 1))
    done
    
    echo "###" >> /etc/crypttab
    
    phrase="====================================================="
    phrase1="================================================="
    unset phrase
    unset phrase1

    Теперь, используя данный скрипт, надо создать пул для хранения данных:


    ./create_crypt_pool.sh
    
    zpool create -o ashift=12 -O atime=off -O compression=lz4 -O normalization=formD  tank0 raidz1 /dev/disk/by-id/dm-name-tank0_crypt*

    Замечания о параметре ashift=12 смотрите в моих предыдущих статьях и комментариях к ним.


    После создания пула, я выношу его журнал на SSD:


    zpool add tank0 log mirror /dev/disk/by-id/dm-name-slog0_crypt1 /dev/disk/by-id/dm-name-slog0_crypt2

    В дальнейшем, при установленном и настроенном OMV, возможно будет создавать пулы через GUI:


    Создание ZFS пал в OMV WEB GUI


    Включение импорта пулов и автомонтирования томов при загрузке


    Для того, чтобы гарантированно включить автомонтирование пулов, выполните следующие команды:


    rm /etc/zfs/zpool.cache
    systemctl enable zfs-import-scan.service
    systemctl enable zfs-mount.service
    systemctl enable zfs-import-cache.service

    На данном этапе закончена настройка дисковой подсистемы.


    Операционная система


    Первым делом надо установить и настроить OMV, чтобы наконец получить какую-то основу для NAS.


    Установка OMV


    OMV будет установлен в виде deb-пакета. Для того, чтобы это сделать, возможно воспользоваться официальной инструкцией.


    Скрипт add_repo.sh добавляет репозиторий OMV Arrakis в/etc/apt/ sources.list.d, чтобы пакетная система увидела репозиторий.


    add_repo.sh
    cat <<EOF >> /etc/apt/sources.list.d/openmediavault.list
    deb http://packages.openmediavault.org/public arrakis main
    # deb http://downloads.sourceforge.net/project/openmediavault/packages arrakis main
    ## Uncomment the following line to add software from the proposed repository.
    # deb http://packages.openmediavault.org/public arrakis-proposed main
    # deb http://downloads.sourceforge.net/project/openmediavault/packages arrakis-proposed main
    ## This software is not part of OpenMediaVault, but is offered by third-party
    ## developers as a service to OpenMediaVault users.
    deb http://packages.openmediavault.org/public arrakis partner
    # deb http://downloads.sourceforge.net/project/openmediavault/packages arrakis partner
    EOF

    Обратите внимание, что по сравнению с оригиналом, репозиторий partner включен.


    Для установки и первичной инициализации надо выполнить команды, приведённые ниже.


    Команды для установки OMV.
    ./add_repo.sh
    export LANG=C
    export DEBIAN_FRONTEND=noninteractive
    export APT_LISTCHANGES_FRONTEND=none
    apt-get update
    apt-get --allow-unauthenticated install openmediavault-keyring
    apt-get update
    apt-get --yes --auto-remove --show-upgraded \
        --allow-downgrades --allow-change-held-packages \
        --no-install-recommends \
        --option Dpkg::Options::="--force-confdef" \
        --option DPkg::Options::="--force-confold" \
        install postfix openmediavault
    # Initialize the system and database.
    omv-initsystem

    OMV установлен. Он использует своё ядро, и после установки может потребоваться перезагрузка.


    Перезагрузившись, интерфейс OpenMediaVault, будет доступен на порту 80 (зайдите в браузере на NAS по IP-адресу):



    Логин/пароль по умолчанию: admin/openmediavault.


    Настройка OMV


    Далее большая часть настройки будет проходить через WEB-GUI.


    Установка безопасного соединения


    Сейчас надо сменить пароль WEB-администратора и сгенерировать сертификат для NAS, чтобы в дальнейшем работать по HTTPS.


    Смена пароля производится на вкладке "Система->Общие настройки->Пароль Web Администратора".
    Для генерация сертификата на вкладке "Система->Сертификаты->SSL" надо выбрать "Добавить->Создать".


    Созданный сертификат будет виден на той же вкладке:


    Сертификат


    После создания сертификата, на вкладке "Система->Общие настройки" надо включить флажок "Включить SSL/TLS".


    Сертификат потребуется до завершения настройки. В окончательном варианте для обращения к OMV будет использоваться подписанный сертификат.


    Теперь надо перелогиниться в OMV, на порт 443 или просто приписав в браузере префикс https:// перед IP.


    Если войти удалось, на вкладке "Система->Общие настройки" надо включить флажок "Принудительно SSL/TLS".


    Измените порты 80 и 443 на 10080 и 10443.
    И попробуйте войти по следующему адресу: https://IP_NAS:10443.
    Изменение портов важно, потому что порты 80 и 443 будет использовать docker контейнер с nginx-reverse-proxy.


    Первичные настройки


    Минимальные настройки, которое надо сделать в первую очередь:


    • На вкладке "Система->Дата и Время" проверьте значение часового пояса и задайте сервер NTP.
    • На вкладке "Система->Мониторинг" включите сбор статистики производительности.
    • На вкладке "Система->Управление энергопотреблением" видимо стоит выключить "Мониторинг", чтобы OMV не пытался управлять вентиляторами.

    Сеть


    Если второй сетевой интерфейс NAS ещё не был подключен, подключите его к роутеру.


    Затем:


    • На вкладке "Система->Сеть" установите имя хоста в значение "nas" (или то, которые вам нравится).
    • Настройте бондинг для интерфейсов, как показано на рисунке ниже: "Система->Сеть->Интерфейсы->Добавить->Bond".
    • Добавьте нужные правила файрволла на вкладке "Система->Сеть->Брандмауэр". Для начала достаточно доступа на порты 10443, 10080, 443, 80, 22 для SSH и разрешения получения/отправки ICMP.

    Настройка бондинга


    В результате, должны появиться интерфейсы в бондинге, которые роутер будет видеть, как один интерфейс и присвоит ему один IP адрес:


    Интерфейсы в бондинге


    При желании, возможно дополнительно настроить SSH из WEB GUI:


    Настройка SSH


    Репозитории и модули


    На вкладке "Система->Управление обновлениями->Настройки" включите "Обновления поддерживаемые сообществом".


    Сначала требуется добавить репозитории OMV extras.
    Это возможно сделать просто установив плагин, либо пакет, как указано на форуме.


    На странице "Система->Плагины" надо найти плагин "openmediavault-omvextrasorg" и установить его.


    В результате, в меню системы появится значок "OMV-Extras" (его возможно видеть на скриншотах).


    Зайдите туда и включите следующие репозитории:


    • OMV-Extras.org. Стабильный репозиторий, содержащий много плагинов.
    • OMV-Extras.org Testing. Некоторые плагины из этого репозитория отсутствуют в стабильном репозитории.
    • Docker CE. Собственно, Docker.

    На вкладке "Система->OMV Extras->Ядро" вы можете выбрать нужное вам ядро, в том числе ядро от Proxmox (сам я его не ставил, т.к. мне пока не нужно, потому не рекомендую):



    Установите необходимые плагины (жирным выделены абсолютно необходимые, курсивом — опциональные, которые я не устанавливал):


    Список плагинов.
    • openmediavault-apttool. Минимальный GUI для работы с пакетной системой. Добавляет "Сервисы->Apttool".
    • openmediavault-anacron. Добавляет возможность работы из GUI с асинхронным планировщиком. Добавляет "Система->Anacron".
    • openmediavault-backup. Обеспечивает резервное копирование системы в хранилище. Добавляет страницу "Система->Резервное копирование".
    • openmediavault-diskstats. Нужен для сбора статистики по производительности дисков.
    • openmediavault-dnsmasq. Позволяет поднять на NAS сервер DNS и DHCP. Т.к., я делаю это на роутере, мне не требуется.
    • openmediavault-docker-gui. Интерфейс управления Docker контейнерами. Добавляет "Сервисы->Docker".
    • openmediavault-ldap. Поддержка аутентификации через LDAP. Добавляет "Управление правами доступа->Служба каталогов".
    • openmediavault-letsencrypt. Поддержка Let's Encrypt из GUI. Не нужна, потому что используется встраивание в контейнер nginx-reverse-proxy.
    • openmediavault-luksencryption. Поддержка шифрования LUKS. Нужен, чтобы в интерфейсе OMV были видны шифрованные диски. Добавляет "Хранилище->Шифрование".
    • openmediavault-nut. Поддержка ИБП. Добавляет "Сервисы->ИБП".
    • openmediavault-omvextrasorg. OMV Extras уже должен быть установлен.
    • openmediavault-resetperms. Позволяет переустанавливать права и сбрасывать списки контроля доступа на общих каталогах. Добавляет "Управление правами доступа->Общие каталоги->Reset Permissions".
    • openmediavault-route. Полезный плагин для управления маршрутизацией. Добавляет "Система->Сеть->Статический маршрут".
    • openmediavault-symlinks. Предоставляет возможность создавать символические ссылки. Добавляет страницу "Сервисы->Symlinks".
    • openmediavault-unionfilesystems. Поддержка UnionFS. Может пригодиться в будущем, хотя докер и использует ZFS в качестве бэкэнда. Добавляет "Хранилище->Union Filesystems".
    • openmediavault-virtualbox. Может быть использован для встраивания в GUI возможности управления виртуальными машинами.
    • openmediavault-zfs. Плагин добавляет поддержку ZFS в OpenMediaVault. После установки появится страница "Хранилище->ZFS".

    Диски


    Все диски, которые есть в системе, должны быть видны OMV. Удостоверьтесь в этом, посмотрев на вкладке "Хранилище->Диски". Если не все диски видны, запустите сканирование.


    Диски в системе


    Там же, на всех HDD надо включить кэширование записи (кликнув на диске из списка и нажав кнопку "Редактировать").


    Удостоверьтесь, что видны все шифрованные разделы на вкладке "Хранилище->Шифрование":


    Шифрованные разделы


    Теперь пора настроить S.M.A.R.T., указанный, как средство повышения надёжности:


    • Перейдите на вкладку "Хранилище->S.M.A.R.T->Настройки". Включите SMART.
    • Там же выберите значения температурных уровней дисков (критический, как правило 60 C, а оптимальный температурный режим диска 15-45 C).
    • Перейдите на вкладку "Хранилище->S.M.A.R.T->Устройства". Включите мониторинг для каждого диска.
    • Перейдите на вкладку "Хранилище->S.M.A.R.T->Запланированные тесты". Добавьте для каждого диска короткую самопроверку раз в сутки и длительную самопроверку раз в месяц. Причём так, чтобы периоды самопроверки не пересекались.

    На этом настройку дисков возможно считать оконченной.


    Файловые системы и общие каталоги


    Надо создать файловые системы для предопределённых каталогов.
    Сделать это возможно из консоли, либо из WEB-интерфейса OMV (Хранилище->ZFS->Выбрать пул tank0->Кнопка "Добавить"->Filesystem).


    Команды для создания ФС.
    zfs create -o utf8only=on -o normalization=formD -p tank0/user_data/books
    zfs create -o utf8only=on -o normalization=formD -p tank0/user_data/music
    zfs create -o utf8only=on -o normalization=formD -p tank0/user_data/pictures
    zfs create -o utf8only=on -o normalization=formD -p tank0/user_data/downloads
    zfs create -o compression=off -o utf8only=on -o normalization=formD -p tank0/user_data/videos

    В итоге должна получиться следующая структура каталогов:



    После этого, добавьте созданные ФС, как общие каталоги на странице "Управление правами доступа->Общие каталоги->Добавить".
    Обратите внимание, что параметр "Устройство" равен пути к созданной в ZFS файловой системе, а параметр "Путь" у всех каталогов равен "/".



    Резервное копирование


    Резервное копирование производится двумя инструментами:


    • OMV backup plugin. Плагин OMV для резервного копирования системы.
    • zfs-auto-snapshot. Скрипт для автоматического создания снимков ZFS по расписанию и удаления старых.

    Если вы воспользуетесь плагином, скорее всего получите ошибку:


    lsblk: /dev/block/0:22: not a block device

    Для того, чтобы её исправить, по замечанию разработчиков OMV в этой "очень нестандартной конфигурации", возможно было бы отказаться от плагина и воспользоваться средствами ZFS в виде zfs send/receive.
    Либо явно указать параметр "Root device" в виде физического устройства, с которого производится загрузка.
    Мне удобнее использовать плагин и делать резервное копирование ОС из интерфейса, вместо того, чтобы городить что-то своё с zfs send, потому я предпочитаю второй вариант.


    Настройка резервного копирования


    Чтобы резервное копирование работало, сначала создайте через ZFS файловую систему tank0/apps/backup, затем в меню "Система->Резервирование" кликните "+" в поле параметра "Общая папка" и добавьте созданное устройство, как целевое, а поле "Путь" установите в "/".


    С zfs-auto-snapshot тоже есть проблемы. Если её не настроить, она будет делать снимки каждый час, каждый день, каждую неделю, каждый месяц в течение года.
    В итоге получится то, что на скриншоте:


    Много спама от zfs-auto-snapshot


    Если вы уже на это натолкнулись, выполните следующий код для удаления автоматических снимков:


    zfs list -t snapshot -o name -S creation | grep "@zfs-auto-snap" | tail -n +1500 | xargs -n 1 zfs destroy -vr

    Затем настройте запуск zfs-auto-snapshot в cron.
    Для начала, просто удалите /etc/cron.hourly/zfs-auto-snapshot, если вам не требуется делать снимки каждый час.


    E-mail уведомления


    Нотификация по e-mail была указана, как одно из средств достижения надёжности.
    Потому теперь надо настроить E-mail уведомления.
    Для этого, зарегистрируйте на одном из публичных серверов ящик (ну либо настройте SMTP сервер самостоятельно, если у вас действительно есть причины это сделать).


    После чего надо зайти на страницу "Система->Уведомление" и вписать:


    • Адрес SMTP сервера.
    • Порт SMTP сервера.
    • Имя пользователя.
    • Адрес отправителя (обычно первая компонента адреса совпадает с именем).
    • Пароль пользователя.
    • В поле "Получатель" ваш обычный адрес, на который NAS будет отправлять уведомления.

    Крайне желательно включить SSL/TLS.


    Пример настройки для Yandex показан на скриншоте:


    E-mail уведомления


    Настройка сети вне NAS


    IP-адрес


    Я использую белый статический IP-адрес, который стоит плюсом 100 рублей в месяц. Если нет желания платить и ваш адрес динамический, но не за NAT, возможно корректировать внешние DNS записи через API выбранного сервиса.
    Тем не менее, стоит иметь ввиду, что адрес не за NAT может внезапно стать адресом за NAT: как правило, провайдеры не дают никаких гарантий.


    Роутер


    В качестве роутера у меня Mikrotik RouterBoard, похожий на тот, что на картинке ниже.


    Mikrotik Routerboard


    На роутере требуется сделать три вещи:


    • Настроить статические адреса для NAS. В моём случае, адреса выдаются по DHCP, и надо сделать так, чтобы адаптерам с определённым MAC адресом всегда выдавался один и тот же IP адрес. В RouterOS это делается на вкладке "IP->DHCP Server" кнопкой "Make static".
    • Настроить DNS сервер так, чтобы он для имени "nas", а также имён, оканчивающихся на ".nas" и ".NAS.cloudns.cc" (где "NAS" — зона на ClouDNS или подобном сервисе) отдавал IP системы. Где это сделать в RouterOS, показано на скриншоте ниже. В моём случае, это реализовано путём сопоставления имени с регулярным выражением: "^.*\.nas$|^nas$|^.*\.NAS.cloudns.cc$"
    • Настроить проброс портов. В RouterOS это делается на вкладке "IP->Firewall", далее останавливаться я на этом не буду.

    Настройка DNS в RouterOS


    ClouDNS


    С CLouDNS всё просто. Заводите аккаунт, подтверждаете. NS записи уже у вас будут прописаны. Далее требуется минимальная настройка.


    Во-первых, нужно создать необходимые зоны (зона с именем NAS, подчёркнутая на скриншоте красным — это то, что вы должны создать, с другим названием, конечно).


    Создание зоны в ClouDNS


    Во-вторых, в этой зоне вы должны прописать следующие A-записи:


    • nas, www, omv, control и пустое имя. Для обращения к интерфейсу OMV.
    • ldap. Интерфейс PhpLdapAdmin.
    • ssp. Интерфейс для смены паролей пользователей.
    • test. Тестовый сервер.

    Остальные доменные имена будут добавляться по мере добавления служб.
    Кликайте на зону, далее "Add new record", выбираете A-тип, вводите имя зоны и IP адрес роутера, за которым стоит NAS.


    Добавленные A-записи


    Во-вторых, требуется получить доступ к API. В ClouDNS он платный, так что предварительно надо его оплатить. В других сервисах он бесплатный. Если знаете, что лучше, и это поддерживается Lexicon, напишите пожалуйста в комментариях.


    Получив доступ к API, туда надо добавить нового пользователя API.


    Добавление пользователя API ClouDNS


    В поле "IP address" надо вписать IP роутера: это адрес, с которого будет доступен API. После того, как пользователь будет добавлен, вы сможете использовать API, авторизовавшись по auth-id и auth-password. Их надо будет передавать в Lexicon, как параметры.



    На этом настройка ClouDNS закончена.


    Настройка контейнеризации


    Настройка Docker


    Если вы установили плагин openmediavault-docker-gui, пакет docker-ce уже должен был подтянуться по зависимостям.


    Дополнительно, установите пакет docker-compose, поскольку в дальнейшем он будет использован для управления контейнерами:


    apt-get install docker-compose

    Также создайте файловую систему под конфигурацию сервисов:


    zfs create -p /tank0/docker/services

    Все настройки, образы и контейнеры докера хранятся в /var/lib/docker. Он туда интенсивно пишет (надо помнить, что это SSD), но главное, создаёт снэпшоты, клоны и файловые системы с именами в виде хэшей.


    Т.о., через некоторое время там скопится достаточно много мусора и будет не особенно удобно
    с ним разбираться. Пример на скриншоте.



    Чтобы этого избежать, надо локализовать каталог с данными на отдельной файловой системе.
    Изменить расположение базового пути докера не сложно, это возможно сделать даже через GUI плагина, но тогда возникнет проблема: пулы перестанут монтироваться при загрузке, т.к. докер создаст свои каталоги в точке монтирования, и она будет не пуста.


    Решается эта проблема заменой каталога докера в /var/lib на символическую ссылку:


    service docker stop
    zfs create -o com.sun:auto-snapshot=false -p /tank0/docker/lib
    rm -rf /var/lib/docker
    ln -s  /tank0/docker/lib /var/lib/docker
    service docker start

    В результате:


    $ ls -l /var/lib/docker
    lrwxrwxrwx 1 root root 17 Apr  7 12:35 /var/lib/docker -> /tank0/docker/lib

    Теперь надо создать межконтейнерную сеть:


    docker network create docker0

    На этом первичная настройка Docker закончена и возможно приступать к созданию контейнеров.


    Настройка контейнера с nginx-reverse-proxy


    После того как Docker настроен, возможно приступить к реализации диспетчера.


    Найти все конфигурационные файлы вы можете здесь, либо под спойлерами.


    Для него используются два образа: nginx-proxy и letsencrypt-dns.


    Напомню, что порты интерфейса OMV требуется изменить на 10080 и 10443, потому что диспетчер будет работать на портах 80 и 443.


    /tank0/docker/services/nginx-proxy/docker-compose.yml
    version: '2'
    
    networks:
      docker0:
        external:
          name: docker0
    
    services:
      nginx-proxy:
        networks:
          - docker0
        restart: always
        image: jwilder/nginx-proxy
        ports:
          - "80:80"
          - "443:443"
        volumes:
          - ./certs:/etc/nginx/certs:ro
          - ./vhost.d:/etc/nginx/vhost.d
          - ./html:/usr/share/nginx/html
          - /var/run/docker.sock:/tmp/docker.sock:ro
          - ./local-config:/etc/nginx/conf.d
          - ./nginx.tmpl:/app/nginx.tmpl
        labels:
          - "com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy=true"
    
      letsencrypt-dns:
        image: adferrand/letsencrypt-dns
        volumes:
          - ./certs/letsencrypt:/etc/letsencrypt
        environment:
          - "LETSENCRYPT_USER_MAIL=MAIL@MAIL.COM"
          - "LEXICON_PROVIDER=cloudns"
          - "LEXICON_OPTIONS=--delegated NAS.cloudns.cc"
          - "LEXICON_PROVIDER_OPTIONS=--auth-id=CLOUDNS_ID --auth-password=CLOUDNS_PASSWORD"

    В данном конфиге настраиваются два контейнера:


    • nginx-reverse-proxy — cам обратный прокси.
    • letsencrypt-dns — ACME клиент Let's Encrypt.

    Для создания и запуска контейнера с nginx-reverse-proxy используется образ jwilder/nginx-proxy.


    docker0 — межконтейнерная сеть, которая была создана ранее, ей не управляет docker-compose.
    nginx-proxy — сервис обратного прокси, собственной персоной. Он смотрит в сеть docker0. При этом, порты 80 и 443 в секции ports пробрасываются на аналогичные порты хоста (значит, на хосте будут открыты такие же порты, а данные с них будут перенаправляться на порты в сети docker0, которые слушает прокси).
    Параметр restart: always означает, что нужно запускать этот сервис при перезагрузке.


    Тома:


    • Внешний каталог certs отображается в /etc/nginx/certs — там лежат сертификаты, включая сертификаты, полученные от Let's Encrypt. Это общий каталог между контейнером с прокси и контейнером с ACME клиентом.
    • ./vhost.d:/etc/nginx/vhost.d — конфигурация отдельных виртуальных хостов. Сейчас не использую.
    • ./html:/usr/share/nginx/html — статичный контент. Там не нужно ничего настраивать.
    • /var/run/docker.sock, отображаемый в /tmp/docker.sock — сокет для связи с демоном Docker на хосте. Нужен для работы docker-gen внутри оригинального образа.
    • ./local-config, отображаемый в /etc/nginx/conf.d — дополнительные конфигурационные файлы nginx. Требуется для тюнинга параметров, о которых ниже.
    • ./nginx.tmpl, отображаемый в /app/nginx.tmpl — шаблон для конфигурационного файла nginx, из которого docker-gen создаст конфиг.

    Контейнер letsencrypt-dns создаётся из образа adferrand/letsencrypt-dns. Он включает упомянутый выше ACME клиент и Lexicon, для общения с провайдером DNS зоны.


    Общий каталог certs/letsencrypt отображается в /etc/letsencrypt внутри контейнера.


    Чтобы это заработало, требуется настроить ещё несколько переменных окружения внутри контейнера:


    • LETSENCRYPT_USER_MAIL=MAIL@MAIL.COM — почта пользователя Let's Encrypt. Лучше тут указать реальную почту, на которую будут приходить всякие сообщения.
    • LEXICON_PROVIDER=cloudns — провайдер для Lexicon. В моём случае — cloudns.
    • LEXICON_PROVIDER_OPTIONS=--auth-id=CLOUDNS_ID --auth-password=CLOUDNS_PASSWORD --delegated=NAS.cloudns.cc — CLOUDNS_ID на последнем скриншоте в секции по настройке ClouDNS подчёркнут красным. CLOUDNS_PASSWORD — это пароль, который вы задали для пользования API. NAS.cloudns.cc, где NAS — имя вашей DNS зоны. Для cloudns нужен потому, что по умолчанию будут передаваться первые два компонента домена (cloudns.cc), а ClouDNS API требует указывать зону в запросе.

    После этой настройки будут два независимо работающих контейнера: прокси и агент для получения сертификата.
    При этом, прокси будет искать сертификат в каталогах, указанных в конфиге, но не в структуре каталогов, которую создаст агент Let's encrypt:


    $ ls ./certs/letsencrypt/
    accounts  archive  csr  domains.conf  keys  live  renewal  renewal-hooks

    Для того, чтобы прокси начал видеть полученные сертификаты, требуется немного исправить шаблон.


    /tank0/docker/services/nginx-proxy/nginx.tmpl
    {{ $CurrentContainer := where $ "ID" .Docker.CurrentContainerID | first }}
    
    {{ define "upstream" }}
        {{ if .Address }}
            {{/* If we got the containers from swarm and this container's port is published to host, use host IP:PORT */}}
            {{ if and .Container.Node.ID .Address.HostPort }}
                # {{ .Container.Node.Name }}/{{ .Container.Name }}
                server {{ .Container.Node.Address.IP }}:{{ .Address.HostPort }};
            {{/* If there is no swarm node or the port is not published on host, use container's IP:PORT */}}
            {{ else if .Network }}
                # {{ .Container.Name }}
                server {{ .Network.IP }}:{{ .Address.Port }};
            {{ end }}
        {{ else if .Network }}
            # {{ .Container.Name }}
            {{ if .Network.IP }}
                server {{ .Network.IP }} down;
            {{ else }}
                server 127.0.0.1 down;
            {{ end }}
        {{ end }}
    
    {{ end }}
    
    # If we receive X-Forwarded-Proto, pass it through; otherwise, pass along the
    # scheme used to connect to this server
    map $http_x_forwarded_proto $proxy_x_forwarded_proto {
      default $http_x_forwarded_proto;
      ''      $scheme;
    }
    
    # If we receive X-Forwarded-Port, pass it through; otherwise, pass along the
    # server port the client connected to
    map $http_x_forwarded_port $proxy_x_forwarded_port {
      default $http_x_forwarded_port;
      ''      $server_port;
    }
    
    # If we receive Upgrade, set Connection to "upgrade"; otherwise, delete any
    # Connection header that may have been passed to this server
    map $http_upgrade $proxy_connection {
      default upgrade;
      '' close;
    }
    
    # Apply fix for very long server names
    server_names_hash_bucket_size 128;
    
    # Default dhparam
    {{ if (exists "/etc/nginx/dhparam/dhparam.pem") }}
    ssl_dhparam /etc/nginx/dhparam/dhparam.pem;
    {{ end }}
    
    # Set appropriate X-Forwarded-Ssl header
    map $scheme $proxy_x_forwarded_ssl {
      default off;
      https on;
    }
    
    gzip_types text/plain text/css application/javascript application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;
    
    log_format vhost '$host $remote_addr - $remote_user [$time_local] '
                     '"$request" $status $body_bytes_sent '
                     '"$http_referer" "$http_user_agent"';
    
    access_log off;
    
    {{ if $.Env.RESOLVERS }}
    resolver {{ $.Env.RESOLVERS }};
    {{ end }}
    
    {{ if (exists "/etc/nginx/proxy.conf") }}
    include /etc/nginx/proxy.conf;
    {{ else }}
    # HTTP 1.1 support
    proxy_http_version 1.1;
    proxy_buffering off;
    proxy_set_header Host $http_host;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $proxy_connection;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
    proxy_set_header X-Forwarded-Ssl $proxy_x_forwarded_ssl;
    proxy_set_header X-Forwarded-Port $proxy_x_forwarded_port;
    
    # Mitigate httpoxy attack (see README for details)
    proxy_set_header Proxy "";
    {{ end }}
    
    {{ $enable_ipv6 := eq (or ($.Env.ENABLE_IPV6) "") "true" }}
    server {
        server_name _; # This is just an invalid value which will never trigger on a real hostname.
        listen 80;
        {{ if $enable_ipv6 }}
        listen [::]:80;
        {{ end }}
        access_log /var/log/nginx/access.log vhost;
        return 503;
    }
    
    {{ if (and (exists "/etc/nginx/certs/default.crt") (exists "/etc/nginx/certs/default.key")) }}
    server {
        server_name _; # This is just an invalid value which will never trigger on a real hostname.
        listen 443 ssl http2;
        {{ if $enable_ipv6 }}
        listen [::]:443 ssl http2;
        {{ end }}
        access_log /var/log/nginx/access.log vhost;
        return 503;
    
        ssl_session_tickets off;
        ssl_certificate /etc/nginx/certs/default.crt;
        ssl_certificate_key /etc/nginx/certs/default.key;
    }
    {{ end }}
    
    {{ range $host, $containers := groupByMulti $ "Env.VIRTUAL_HOST" "," }}
    
    {{ $host := trim $host }}
    {{ $is_regexp := hasPrefix "~" $host }}
    {{ $upstream_name := when $is_regexp (sha1 $host) $host }}
    
    # {{ $host }}
    upstream {{ $upstream_name }} {
    
    {{ range $container := $containers }}
        {{ $addrLen := len $container.Addresses }}
    
        {{ range $knownNetwork := $CurrentContainer.Networks }}
            {{ range $containerNetwork := $container.Networks }}
                {{ if (and (ne $containerNetwork.Name "ingress") (or (eq $knownNetwork.Name $containerNetwork.Name) (eq $knownNetwork.Name "host"))) }}
                    ## Can be connected with "{{ $containerNetwork.Name }}" network
    
                    {{/* If only 1 port exposed, use that */}}
                    {{ if eq $addrLen 1 }}
                        {{ $address := index $container.Addresses 0 }}
                        {{ template "upstream" (dict "Container" $container "Address" $address "Network" $containerNetwork) }}
                    {{/* If more than one port exposed, use the one matching VIRTUAL_PORT env var, falling back to standard web port 80 */}}
                    {{ else }}
                        {{ $port := coalesce $container.Env.VIRTUAL_PORT "80" }}
                        {{ $address := where $container.Addresses "Port" $port | first }}
                        {{ template "upstream" (dict "Container" $container "Address" $address "Network" $containerNetwork) }}
                    {{ end }}
                {{ else }}
                    # Cannot connect to network of this container
                    server 127.0.0.1 down;
                {{ end }}
            {{ end }}
        {{ end }}
    {{ end }}
    }
    
    {{ $default_host := or ($.Env.DEFAULT_HOST) "" }}
    {{ $default_server := index (dict $host "" $default_host "default_server") $host }}
    
    {{/* Get the VIRTUAL_PROTO defined by containers w/ the same vhost, falling back to "http" */}}
    {{ $proto := trim (or (first (groupByKeys $containers "Env.VIRTUAL_PROTO")) "http") }}
    
    {{/* Get the NETWORK_ACCESS defined by containers w/ the same vhost, falling back to "external" */}}
    {{ $network_tag := or (first (groupByKeys $containers "Env.NETWORK_ACCESS")) "external" }}
    
    {{/* Get the HTTPS_METHOD defined by containers w/ the same vhost, falling back to "redirect" */}}
    {{ $https_method := or (first (groupByKeys $containers "Env.HTTPS_METHOD")) "redirect" }}
    
    {{/* Get the SSL_POLICY defined by containers w/ the same vhost, falling back to "Mozilla-Intermediate" */}}
    {{ $ssl_policy := or (first (groupByKeys $containers "Env.SSL_POLICY")) "Mozilla-Intermediate" }}
    
    {{/* Get the HSTS defined by containers w/ the same vhost, falling back to "max-age=31536000" */}}
    {{ $hsts := or (first (groupByKeys $containers "Env.HSTS")) "max-age=31536000" }}
    
    {{/* Get the VIRTUAL_ROOT By containers w/ use fastcgi root */}}
    {{ $vhost_root := or (first (groupByKeys $containers "Env.VIRTUAL_ROOT")) "/var/www/public" }}
    
    {{/* Get the first cert name defined by containers w/ the same vhost */}}
    {{ $certName := (first (groupByKeys $containers "Env.CERT_NAME")) }}
    
    {{/* Get the best matching cert  by name for the vhost. */}}
    {{ $vhostCert := (closest (dir "/etc/nginx/certs") (printf "%s.crt" $host))}}
    
    {{/* vhostCert is actually a filename so remove any suffixes since they are added later */}}
    {{ $vhostCert := trimSuffix ".crt" $vhostCert }}
    {{ $vhostCert := trimSuffix ".key" $vhostCert }}
    
    {{/* Use the cert specified on the container or fallback to the best vhost match */}}
    {{ $cert := (coalesce $certName $vhostCert) }}
    
    {{ $is_https := (and (ne $https_method "nohttps") (ne $cert "") (or (and (exists (printf "/etc/nginx/certs/letsencrypt/live/%s/fullchain.pem" $cert)) (exists (printf "/etc/nginx/certs/letsencrypt/live/%s/privkey.pem" $cert))) (and (exists (printf "/etc/nginx/certs/%s.crt" $cert)) (exists (printf "/etc/nginx/certs/%s.key" $cert)))) ) }}
    
    {{ if $is_https }}
    
    {{ if eq $https_method "redirect" }}
    server {
        server_name {{ $host }};
        listen 80 {{ $default_server }};
        {{ if $enable_ipv6 }}
        listen [::]:80 {{ $default_server }};
        {{ end }}
        access_log /var/log/nginx/access.log vhost;
        return 301 https://$host$request_uri;
    }
    {{ end }}
    
    server {
        server_name {{ $host }};
        listen 443 ssl http2 {{ $default_server }};
        {{ if $enable_ipv6 }}
        listen [::]:443 ssl http2 {{ $default_server }};
        {{ end }}
        access_log /var/log/nginx/access.log vhost;
    
        {{ if eq $network_tag "internal" }}
        # Only allow traffic from internal clients
        include /etc/nginx/network_internal.conf;
        {{ end }}
    
        {{ if eq $ssl_policy "Mozilla-Modern" }}
        ssl_protocols TLSv1.2;
        ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256';
        {{ else if eq $ssl_policy "Mozilla-Intermediate" }}
        ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
        ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:!DSS';
        {{ else if eq $ssl_policy "Mozilla-Old" }}
        ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2;
        ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:ECDHE-RSA-DES-CBC3-SHA:ECDHE-ECDSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:DES-CBC3-SHA:HIGH:SEED:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!RSAPSK:!aDH:!aECDH:!EDH-DSS-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA:!SRP';
        {{ else if eq $ssl_policy "AWS-TLS-1-2-2017-01" }}
        ssl_protocols TLSv1.2;
        ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:AES128-GCM-SHA256:AES128-SHA256:AES256-GCM-SHA384:AES256-SHA256';
        {{ else if eq $ssl_policy "AWS-TLS-1-1-2017-01" }}
        ssl_protocols TLSv1.1 TLSv1.2;
        ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:AES128-GCM-SHA256:AES128-SHA256:AES128-SHA:AES256-GCM-SHA384:AES256-SHA256:AES256-SHA';
        {{ else if eq $ssl_policy "AWS-2016-08" }}
        ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
        ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:AES128-GCM-SHA256:AES128-SHA256:AES128-SHA:AES256-GCM-SHA384:AES256-SHA256:AES256-SHA';
        {{ else if eq $ssl_policy "AWS-2015-05" }}
        ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
        ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:AES128-GCM-SHA256:AES128-SHA256:AES128-SHA:AES256-GCM-SHA384:AES256-SHA256:AES256-SHA:DES-CBC3-SHA';
        {{ else if eq $ssl_policy "AWS-2015-03" }}
        ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
        ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:AES128-GCM-SHA256:AES128-SHA256:AES128-SHA:AES256-GCM-SHA384:AES256-SHA256:AES256-SHA:DHE-DSS-AES128-SHA:DES-CBC3-SHA';
        {{ else if eq $ssl_policy "AWS-2015-02" }}
        ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
        ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:AES128-GCM-SHA256:AES128-SHA256:AES128-SHA:AES256-GCM-SHA384:AES256-SHA256:AES256-SHA:DHE-DSS-AES128-SHA';
        {{ end }}
    
        ssl_prefer_server_ciphers on;
        ssl_session_timeout 5m;
        ssl_session_cache shared:SSL:50m;
        ssl_session_tickets off;
    
            {{ if (and (exists (printf "/etc/nginx/certs/letsencrypt/live/%s/fullchain.pem" $cert)) (exists (printf "/etc/nginx/certs/letsencrypt/live/%s/privkey.pem" $cert))) }}
        ssl_certificate /etc/nginx/certs/letsencrypt/live/{{ (printf "%s/fullchain.pem" $cert) }};
        ssl_certificate_key /etc/nginx/certs/letsencrypt/live/{{ (printf "%s/privkey.pem" $cert) }};
            {{ else if (and (exists (printf "/etc/nginx/certs/%s.crt" $cert)) (exists (printf "/etc/nginx/certs/%s.key" $cert))) }}
        ssl_certificate /etc/nginx/certs/{{ (printf "%s.crt" $cert) }};
        ssl_certificate_key /etc/nginx/certs/{{ (printf "%s.key" $cert) }};
        {{ end }}
    
        {{ if (exists (printf "/etc/nginx/certs/%s.dhparam.pem" $cert)) }}
        ssl_dhparam {{ printf "/etc/nginx/certs/%s.dhparam.pem" $cert }};
        {{ end }}
    
        {{ if (exists (printf "/etc/nginx/certs/%s.chain.pem" $cert)) }}
        ssl_stapling on;
        ssl_stapling_verify on;
        ssl_trusted_certificate {{ printf "/etc/nginx/certs/%s.chain.pem" $cert }};
        {{ end }}
    
        {{ if (and (ne $https_method "noredirect") (ne $hsts "off")) }}
        add_header Strict-Transport-Security "{{ trim $hsts }}" always;
        {{ end }}
    
        {{ if (exists (printf "/etc/nginx/vhost.d/%s" $host)) }}
        include {{ printf "/etc/nginx/vhost.d/%s" $host }};
        {{ else if (exists "/etc/nginx/vhost.d/default") }}
        include /etc/nginx/vhost.d/default;
        {{ end }}
    
        location / {
            {{ if eq $proto "uwsgi" }}
            include uwsgi_params;
            uwsgi_pass {{ trim $proto }}://{{ trim $upstream_name }};
            {{ else if eq $proto "fastcgi" }}
            root   {{ trim $vhost_root }};
            include fastcgi.conf;
            fastcgi_pass {{ trim $upstream_name }};
            {{ else }}
            proxy_pass {{ trim $proto }}://{{ trim $upstream_name }};
            {{ end }}
    
            {{ if (exists (printf "/etc/nginx/htpasswd/%s" $host)) }}
            auth_basic  "Restricted {{ $host }}";
            auth_basic_user_file    {{ (printf "/etc/nginx/htpasswd/%s" $host) }};
            {{ end }}
            {{ if (exists (printf "/etc/nginx/vhost.d/%s_location" $host)) }}
            include {{ printf "/etc/nginx/vhost.d/%s_location" $host}};
            {{ else if (exists "/etc/nginx/vhost.d/default_location") }}
            include /etc/nginx/vhost.d/default_location;
            {{ end }}
        }
    }
    
    {{ end }}
    
    {{ if or (not $is_https) (eq $https_method "noredirect") }}
    
    server {
        server_name {{ $host }};
        listen 80 {{ $default_server }};
        {{ if $enable_ipv6 }}
        listen [::]:80 {{ $default_server }};
        {{ end }}
        access_log /var/log/nginx/access.log vhost;
    
        {{ if eq $network_tag "internal" }}
        # Only allow traffic from internal clients
        include /etc/nginx/network_internal.conf;
        {{ end }}
    
        {{ if (exists (printf "/etc/nginx/vhost.d/%s" $host)) }}
        include {{ printf "/etc/nginx/vhost.d/%s" $host }};
        {{ else if (exists "/etc/nginx/vhost.d/default") }}
        include /etc/nginx/vhost.d/default;
        {{ end }}
    
        location / {
            {{ if eq $proto "uwsgi" }}
            include uwsgi_params;
            uwsgi_pass {{ trim $proto }}://{{ trim $upstream_name }};
            {{ else if eq $proto "fastcgi" }}
            root   {{ trim $vhost_root }};
            include fastcgi.conf;
            fastcgi_pass {{ trim $upstream_name }};
            {{ else }}
            proxy_pass {{ trim $proto }}://{{ trim $upstream_name }};
            {{ end }}
            {{ if (exists (printf "/etc/nginx/htpasswd/%s" $host)) }}
            auth_basic  "Restricted {{ $host }}";
            auth_basic_user_file    {{ (printf "/etc/nginx/htpasswd/%s" $host) }};
            {{ end }}
            {{ if (exists (printf "/etc/nginx/vhost.d/%s_location" $host)) }}
            include {{ printf "/etc/nginx/vhost.d/%s_location" $host}};
            {{ else if (exists "/etc/nginx/vhost.d/default_location") }}
            include /etc/nginx/vhost.d/default_location;
            {{ end }}
        }
    }
    
    {{ if (and (not $is_https) (exists "/etc/nginx/certs/default.crt") (exists "/etc/nginx/certs/default.key")) }}
    server {
        server_name {{ $host }};
        listen 443 ssl http2 {{ $default_server }};
        {{ if $enable_ipv6 }}
        listen [::]:443 ssl http2 {{ $default_server }};
        {{ end }}
        access_log /var/log/nginx/access.log vhost;
        return 500;
    
        ssl_certificate /etc/nginx/certs/default.crt;
        ssl_certificate_key /etc/nginx/certs/default.key;
    }
    {{ end }}
    
    {{ end }}
    {{ end }}

    Видно, что по умолчанию nginx будет искать сертификаты типа /etc/nginx/certs/%s.crt и /etc/nginx/certs/%s.pem, где %s — имя сертификата (по умолчанию — имя хоста, но его возможно изменять через переменные).


    Агент же хранит сертификаты в структуре каталогов /etc/nginx/certs/letsencrypt/live/%s/{fullchain.pem, privkey.pem}, и потому в нескольких местах шаблона дополнены условия для таких имён сертификатов:


    {{
    $is_https :=
    (and
      (ne $https_method "nohttps")
      (ne $cert "")
      (or
        (and
          (exists (printf "/etc/nginx/certs/letsencrypt/live/%s/fullchain.pem" $cert))
          (exists (printf "/etc/nginx/certs/letsencrypt/live/%s/privkey.pem" $cert))
        )
        (and
          (exists (printf "/etc/nginx/certs/%s.crt" $cert))
          (exists (printf "/etc/nginx/certs/%s.key" $cert))
        )
      )
    )
    }}

    Теперь остаётся указать агенту, для какого домена выдавать сертификат в файле domains.conf.


    /tank0/docker/services/nginx-proxy/certs/letsencrypt/domains.conf
    *.NAS.cloudns.cc NAS.cloudns.cc

    И ещё один маленький нюанс. Для того, чтобы в будущем вы могли загружать файлы приемлемого размера в облако, и их не резал прокси, установите для него параметр client_max_body_size хотя бы гигабайт в 20, как это показано ниже.


    /tank0/docker/services/nginx-proxy/local-config/max_upload_size.conf
    client_max_body_size 20G;

    Настройка закончена, пора запустить контейнер:


    docker-compose up

    Проверьте работоспособность (всё скачалось и запустилось), нажмите Ctrl+C и запустите контейнер в отвязанном от консоли режиме:


    docker-compose up -d

    Настройка контейнера с тестовым сервером


    Тестовый сервер — это минимальный nginx, который должен выводить страницу приветствия. Нужно, чтобы он мог легко запускаться и останавливаться, а его контейнер быстро пересоздавался.
    Он будет первым и пока единственным сервисом, который будет работать в составе NAS.


    Файлы конфигурации находятся здесь.


    Вот его docker-compose файл:


    /tank0/docker/services/test_nginx/docker-compose.yml
    version: '2'
    
    networks:
      docker0:
        external:
          name: docker0
    
    services:
      nginx-local:
        restart: always
        image: nginx:alpine
        expose:
          - 80
          - 443
        environment:
          - "VIRTUAL_HOST=test.NAS.cloudns.cc"
          - "VIRTUAL_PROTO=http"
          - "VIRTUAL_PORT=80"
          - CERT_NAME=NAS.cloudns.cc
        networks:
          - docker0

    Каждому контейнеру со службой нужно указать следующие параметры:


    • docker0 — внешняя сеть. Это указано в заголовке.
    • expose — выставить порты в сеть, где работает контейнер. Как правило, порт 80 для протокола HTTP и 443 для протокола HTTPS.
    • VIRTUAL_HOST=test.NAS.cloudns.cc — в данной переменной указан виртуальный хост, по которому nginx-reverse-proxy будет перенаправлять запрос на этот контейнер.
    • VIRTUAL_PROTO=http — протокол по которому nginx-reverse-proxy будет взаимодействовать с данным сервисом. Если сертификата нет, это HTTP.
    • VIRTUAL_PORT=80 — порт на который будет обращаться nginx-reverse-proxy.
    • CERT_NAME=NAS.cloudns.cc — имя внешнего сертификата. В данном случае, у всех сервисов сертификат один, потому имя везде одинаковое. NAS — имя DNS зоны.
    • networks — в данной секции для всех фронтэндов, которые общаются с nginx-reverse-proxy должна быть указана сеть docker0.

    Контейнер настроен, теперь нужно его поднять. Выполнив docker-compose up, зайдите по адресу test.NAS.cloudns.cc.


    На консоль должно быть выведено примерно следующее:


    $ docker-compose up
    
    Creating testnginx_nginx-local_1
    Attaching to testnginx_nginx-local_1
    nginx-local_1  | 172.22.0.5 - - [29/Jul/2018:15:32:02 +0000] "GET / HTTP/1.1" 200 612 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537 (KHTML, like Gecko) Chrome/67.0 Safari/537" "192.168.2.3"
    nginx-local_1  | 2018/07/29 15:32:02 [error] 8#8: *2 open() "/usr/share/nginx/html/favicon.ico" failed (2: No such file or directory), client: 172.22.0.5, server: localhost, request: "GET /favicon.ico HTTP/1.1", host: "test.NAS.cloudns.cc", referrer: "https://test.NAS.cloudns.cc/"
    nginx-local_1  | 172.22.0.5 - - [29/Jul/2018:15:32:02 +0000] "GET /favicon.ico HTTP/1.1" 404 572 "https://test.NAS.cloudns.cc/" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537 (KHTML, like Gecko) Chrome/67.0 Safari/537" "192.168.2.3"

    А браузер покажет следующую страницу:


    Запущенный Nginx


    Если, в итоге, у вас появилась страница, как на скриншоте выше, могу вас поздравить: всё настроено и работает правильно.


    Теперь этот контейнер больше не нужен, остановите по Ctrl+C, выполнив затем docker-compose down.


    Настройка контейнера с local-rpoxy


    После настройки прокси, неплохо бы поднять контейнер с nginx-default с сервером, проксирующим запросы для хоста nas, omv и подобных через внешнюю сеть на порты 10080 и 10443 ОС хостовой машины.


    Файлы конфигурации находятся здесь.


    /tank0/docker/services/nginx-local/docker-compose.yml
    version: '2'
    
    networks:
      docker0:
        external:
          name: docker0
    
    services:
      nginx-local:
        restart: always
        image: nginx:alpine
        expose:
          - 80
          - 443
        environment:
          - "VIRTUAL_HOST=NAS.cloudns.cc,nas,nas.*,www.*,omv.*,nas-controller.nas"
          - "VIRTUAL_PROTO=http"
          - "VIRTUAL_PORT=80"
          - CERT_NAME=NAS.cloudns.cc
        volumes:
          - ./local-config:/etc/nginx/conf.d
        networks:
          - docker0

    С конфигурацией docker-compose всё должно быть понятно, и останавливаться на её описании я не буду.
    Единственное, что хочу заметить, это то, что один из доменов NAS.cloudns.cc. Это сделано для того, чтобы при обращении к NAS только по имени DNS зоны, запрос переводился на хост.


    /tank0/docker/services/nginx-local/local-config/default.conf
    # If we receive X-Forwarded-Proto, pass it through; otherwise, pass along the
    # scheme used to connect to this server
    map $http_x_forwarded_proto $proxy_x_forwarded_proto {
      default $http_x_forwarded_proto;
      ''      $scheme;
    }
    
    # If we receive X-Forwarded-Port, pass it through; otherwise, pass along the
    # server port the client connected to
    map $http_x_forwarded_port $proxy_x_forwarded_port {
      default $http_x_forwarded_port;
      ''      $server_port;
    }
    
    # Set appropriate X-Forwarded-Ssl header
    map $scheme $proxy_x_forwarded_ssl {
      default off;
      https on;
    }
    
    access_log on;
    error_log on;
    # HTTP 1.1 support
    proxy_http_version 1.1;
    proxy_buffering off;
    proxy_set_header Host $http_host;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
    proxy_set_header X-Forwarded-Ssl $proxy_x_forwarded_ssl;
    proxy_set_header X-Forwarded-Port $proxy_x_forwarded_port;
    
    # Mitigate httpoxy attack (see README for details)
    proxy_set_header Proxy "";
    
    server {
            server_name _; # This is just an invalid value which will never trigger on a real hostname.
            listen 80;
            return 503;
    }
    
    server {
            server_name www.* nas.* omv.* "";
    
            listen 80;
            location / {
                    proxy_pass https://172.21.0.1:10443/;
            }
    }
    
    # nas-controller
    server {
            server_name nas-controller.nas;
    
            listen 80 ;
            location / {
                    proxy_pass https://nas-controller/;
            }
    }

    • 172.21.0.1 — сеть хоста. Перенаправление запроса всегда производится на порт 443, потому что раньше был сгенерирован сертификат и OMV работает по HTTPS. Пусть также и останется даже для внутреннего общения.
    • https://nas-controller/ — по-идее, это интерфейс на котором работает IPMI, и если обратиться к nas, как к nas-controller.nas, запрос будет перенаправлен на внешний адрес nas-controller. Не особенно полезно.

    Установка и настройка LDAP


    Настройка LDAP-сервера


    LDAP-сервер — это центральный компонент системы управления пользователями.
    Он также работает внутри Docker контейнера. В котором, помимо него, запущены интерфейсы для администрирования и смены паролей.


    Файлы конфигурации и LDIF-файлы находятся здесь.


    /tank0/docker/services/ldap/docker-compose.yml
    version: "2"
    
    networks:
      ldap:
      docker0:
        external:
          name: docker0
    
    services:
      open-ldap:
        image: "osixia/openldap:1.2.0"
        hostname: "open-ldap"
        restart: always
        environment:
          - "LDAP_ORGANISATION=NAS"
          - "LDAP_DOMAIN=nas.nas"
          - "LDAP_ADMIN_PASSWORD=ADMIN_PASSWORD"
          - "LDAP_CONFIG_PASSWORD=CONFIG_PASSWORD"
          - "LDAP_TLS=true"
          - "LDAP_TLS_ENFORCE=false"
          - "LDAP_TLS_CRT_FILENAME=ldap_server.crt"
          - "LDAP_TLS_KEY_FILENAME=ldap_server.key"
          - "LDAP_TLS_CA_CRT_FILENAME=ldap_server.crt"
        volumes:
          - ./certs:/container/service/slapd/assets/certs
          - ./ldap_data/var/lib:/var/lib/ldap
          - ./ldap_data/etc/ldap/slapd.d:/etc/ldap/slapd.d
        networks:
          - ldap
        ports:
          - 172.21.0.1:389:389
          - 172.21.0.1::636:636
    
      phpldapadmin:
        image: "osixia/phpldapadmin:0.7.1"
        hostname: "nas.nas"
        restart: always
        networks:
          - ldap
          - docker0
        expose:
          - 443
        links:
          - open-ldap:open-ldap-server
        volumes:
          - ./certs:/container/service/phpldapadmin/assets/apache2/certs
        environment:
          - VIRTUAL_HOST=ldap.*
          - VIRTUAL_PORT=443
          - VIRTUAL_PROTO=https
          - CERT_NAME=NAS.cloudns.cc
    
          - "PHPLDAPADMIN_LDAP_HOSTS=open-ldap-server"
          #- "PHPLDAPADMIN_HTTPS=false"
          - "PHPLDAPADMIN_HTTPS_CRT_FILENAME=certs/ldap_server.crt"
          - "PHPLDAPADMIN_HTTPS_KEY_FILENAME=private/ldap_server.key"
          - "PHPLDAPADMIN_HTTPS_CA_CRT_FILENAME=certs/ldap_server.crt"
          - "PHPLDAPADMIN_LDAP_CLIENT_TLS_REQCERT=allow"
    
      ldap-ssp:
        image: openfrontier/ldap-ssp:https
        volumes:
          #- ./ssp/mods-enabled/ssl.conf:/etc/apache2/mods-enabled/ssl.conf
          - /etc/ssl/certs/ssl-cert-snakeoil.pem:/etc/ssl/certs/ssl-cert-snakeoil.pem
          - /etc/ssl/private/ssl-cert-snakeoil.key:/etc/ssl/private/ssl-cert-snakeoil.key
        restart: always
        networks:
          - ldap
          - docker0
        expose:
          - 80
        links:
          - open-ldap:open-ldap-server
        environment:
          - VIRTUAL_HOST=ssp.*
          - VIRTUAL_PORT=80
          - VIRTUAL_PROTO=http
          - CERT_NAME=NAS.cloudns.cc
    
          - "LDAP_URL=ldap://open-ldap-server:389"
          - "LDAP_BINDDN=cn=admin,dc=nas,dc=nas"
          - "LDAP_BINDPW=ADMIN_PASSWORD"
          - "LDAP_BASE=ou=users,dc=nas,dc=nas"
          - "MAIL_FROM=admin@nas.nas"
          - "PWD_MIN_LENGTH=8"
          - "PWD_MIN_LOWER=3"
          - "PWD_MIN_DIGIT=2"
          - "SMTP_HOST="
          - "SMTP_USER="
          - "SMTP_PASS="

    В конфиге описано три сервиса:


    • open-ldap — LDAP-сервер.
    • phpldapadmin — WEB-интерфейс для его администрирования. Через него возможно добавлять и удалять пользователей, группы и т.п..
    • ldap-ssp — WEB-интерфейс для смены паролей пользователями.

    LDAP-сервер требует настройки некоторых параметров, которые задаются через переменные окружения:


    • LDAP_ORGANISATION=NAS — имя организации. Может быть произвольным.
    • LDAP_DOMAIN=nas.nas — домен. Тоже произвольный. Указать лучше тот же, что и доменное имя.
    • LDAP_ADMIN_PASSWORD=ADMIN_PASSWORD — пароль администратора.
    • LDAP_CONFIG_PASSWORD=CONFIG_PASSWORD — пароль для конфигурации.

    По-идее, не мешает добавить ещё и пользователя "только для чтения", но потом.


    Тома:


    • /container/service/slapd/assets/certs отображён в локальный каталог certs — сертификаты. Сейчас не используется.
    • ./ldap_data/ — локальный каталог, подкаталоги которого проброшены в два каталога внутри контейнеров. Тут LDAP хранит свою базу.

    Сервер работает во внутренней сети ldap, но его порты 389 (незащищённый LDAP) и 636 (LDAP по SSL, пока не используемый) проброшены в сеть хоста.


    PhpLdapAdmin работает в двух сетях: он обращается к серверу LDAP в сети ldap и открывает порт 443 в сети docker0, для того, чтобы к нему мог обратиться nginx-reverse-proxy.


    Настройки:


    • VIRTUAL_HOST=ldap.* — хост, которому nginx-reverse-proxy будет сопоставлять контейнер.
    • VIRTUAL_PORT=443 — порт для nginx-reverse-proxy.
    • VIRTUAL_PROTO=https — протокол для nginx-reverse-proxy.
    • CERT_NAME=NAS.cloudns.cc — имя сертификата, одинаковое для всех.

    Блок переменных после этого предназначен для настройки SSL и сейчас не обязателен.


    SSP доступен по HTTP и тоже работает в двух сетях.
    Тома, в этом контейнере не используются, и проброшенный сертификат остался от старых экспериментов.


    Переменные для настройки — это ограничения на длину пароля и учётные данные для доступа к серверу LDAP.


    • LDAP_URL=ldap://open-ldap-server:389 — адрес и порт LDAP сервера (см. секцию links).
    • LDAP_BINDDN=cn=admin,dc=nas,dc=nas — логин администратора и домен для аутентификации.
    • LDAP_BINDPW=ADMIN_PASSWORD — пароль администратора, который должен совпадать с паролем, указанным для контейнера open-ldap.
    • LDAP_BASE=ou=users,dc=nas,dc=nas — это базовый путь, по которому содержатся учётные данные пользователей.

    Установите на хостовой машине утилиты для работы с LDAP и инициализируйте LDAP каталог:


    apt-get install ldap-utils
    ldapadd -x -H ldap://172.21.0.1  -D "cn=admin,dc=nas,dc=nas" -W -f ldifs/inititialize_ldap.ldif
    ldapadd -x -H ldap://172.21.0.1  -D "cn=admin,dc=nas,dc=nas" -W -f ldifs/base.ldif
    ldapadd -x -H ldap://172.21.0.1  -D "cn=admin,cn=config" -W -f ldifs/gitlab_attr.ldif

    В gitlab_attr.ldif добавляется атрибут, по которому Gitlab (о нём потом) будет находить пользователей.
    После этого вы можете выполнить следующую команду для проверки.


    Проверка работоспособности LDAP сервера
    $ ldapsearch -x -H ldap://172.21.0.1 -b dc=nas,dc=nas -D "cn=admin,dc=nas,dc=nas" -W
    
    Enter LDAP Password: 
    # extended LDIF
    #
    # LDAPv3
    # base <dc=nas,dc=nas> with scope subtree
    # filter: (objectclass=*)
    # requesting: ALL
    #
    
    # nas.nas
    dn: dc=nas,dc=nas
    objectClass: top
    objectClass: dcObject
    objectClass: organization
    o: NAS
    dc: nas
    
    # admin, nas.nas
    dn: cn=admin,dc=nas,dc=nas
    objectClass: simpleSecurityObject
    objectClass: organizationalRole
    cn: admin
    description: LDAP administrator
    ...
    
    # ldap_users, groups, nas.nas
    dn: cn=ldap_users,ou=groups,dc=nas,dc=nas
    cn: ldap_users
    gidNumber: 500
    objectClass: posixGroup
    objectClass: top
    
    # search result
    search: 2
    result: 0 Success
    
    # numResponses: 12
    # numEntries: 11

    На этом настройка LDAP сервера закончена. Управлять сервером вы можете через WEB-интерфейс.


    Настройка OMV для входа по LDAP


    Если LDAP сервер настроен и работает, OMV настраивается на работу с ним очень просто: указываете хост, порт, данные для авторизации, корневой каталог для поиска пользователей и атрибут для определения того, что найденная запись — аккаунт пользователя.


    LDAP плагин вы уже должны были установить.


    Всё показано на скриншоте:


    Настройка OMV для работы с LDAP


    Взаимодействие с источником питания


    Сначала настройте ИБП по инструкции, которая идёт вместе с ним, и подключите его к NAS по USB.
    Плагин для работы с ИБП вы должны были установить ранее.
    Теперь остаётся только настроить NUT через GUI OMV.
    Зайдите на страницу "Сервисы->ИБП", включите ИБП, в поле идентификатор введите любую строку, описывающую ИБП, например "eaton".


    В поле "Директивы конфигурации драйверов" введите следующее:


    driver = usbhid-ups
    port = auto
    desc = "Eaton 9130 700 VA"
    vendorid = 0463
    pollinterval = 10

    • driver = usbhid-ups — ИБП подключен по USB, потому используется драйвер USB HID.
    • vendorid — это идентификатор производителя ИБП, который может быть получен командой lsusb.
    • pollinterval — интервал опроса ИБП в cекундах.

    Остальные параметры возможно посмотреть в документации.


    Вывод lsusb, строка с ИБП указана стрелкой:


    # lsusb 
    Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
    --> Bus 001 Device 003: ID 0463:ffff MGE UPS Systems UPS
    Bus 001 Device 004: ID 046b:ff10 American Megatrends, Inc. Virtual Keyboard and Mouse
    Bus 001 Device 002: ID 046b:ff01 American Megatrends, Inc. 
    Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

    "Режим отключения" надо установить в "низкий заряд батареи".
    Должно получиться примерно так, как показано на скриншоте:


    Настройка ИБП


    Выключите ИБП и снова включите. Если были настроены уведомления, вам на почту придёт письмо о потере питания.
    На этом настройка ИБП окончена.


    Заключение


    На этом основа системы установлена и настроена. Несмотря на то, что многое тут было сделано из консоли, делать так вовсе не обязательно, просто я считаю, что это удобнее.
    Но одно из достоинств системы — её гибкость.


    Если хотите действовать по-другому, OMV позволит вам это.


    Доступно управление сетями из WEB-интерфейса, причём в некотором плане это более удобно, чем через консоль:



    Для Docker тоже есть весьма понятный WEB-интерфейс:



    Кроме того, OMV может рисовать красивые графики.


    График использования сети:


    График использования сети


    График использования памяти:


    График использования памяти


    График использования CPU:


    График использования CPU


    Нереализованное


    • Проблемы с настройкой — отдельная большая тема. Возможно, что с первого раза что-то не заработает. В таком случае, вам в помощь docker-compose exec, а также внимательное изучение докуменатции и исходников.
    • LDAP сервер не мешало бы настроить лучше, особенно в плане безопасности (использовать SSL везде, добавить пользователя для чтения и т.п.).
    • Пока совершенно не затронуты вопросы доверенной загрузки и повышения безопасности, я об этом знаю, но в другой раз.
    • Пользователь ValdikSS дал очень полезный совет использовать DropbearSSH, внедрённый в initramfs для решения проблемы ненамеренных перезагрузок. Об этом будет другая статья.

    На этом всё.
    С Богом!


    • +13
    • 8,5k
    • 2
    Поделиться публикацией

    Комментарии 2

      0
      В ZFS 0.8.0 уже нативное шифрование…
        0

        Здесь я уже отвечал на вопрос, почему не использовал шифрование ZFS.

      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

      Самое читаемое