• Post category:Single
  • Запись изменена:10.09.2025

Содержание

Схема

Отказоустойчивая архитектура Корпоративный сервер 2024 + Почтовый сервер на Astra Linux 1.7.4

Описание

Архитектура рассчитана на использование одного сервера как основной и второго сервера как запасной с постоянной репликацией. В случае отказа работы основного возможность ручного переключения на запасного с помощью скрипта и изменении IP-адреса указывающего на основную ноду.

В архитектуре используются следующие компоненты и входят в состав стандартной установки Корпоративного сервера 2024:

  • PostgreSQL 11;
  • RabbitMQ-server 3.13.7;
  • Redis server 7.2.7;
  • glusterfs 5.5;

Проверка осуществлялась на версии 2.0.2024.14752 Корпоративного сервера 2024 и 2025.2.1.801 Сервера документов.

Условия эксплуатации

1. При использовании одной доменной А записи с несколькими IP-адресами (с возможностью включения и выключения IP) или изменение доменной записи для Корпоративного сервера вида *.domain.ru, например: *.test2.s7-office.site, и отдельными записями для двух почтовых серверов (*опционально).

2. В случае если почтовый сервер не нужен, то возможна установка без почтового сервера. Предварительно ознакомьтесь с инструкцией чтобы исключить почтовый сервер из установки.

Команды связанные с почтовым сервером отмечены в статье.

3. При ошибках работы приложений на сервере master потребуется переключение сервера slave в режим master с помощью скрипта.

Технические требования

  • 2 Виртуальные машины;
  • Для хранения данных glusterfs;
  • ТХ Машин, для тестирования, возможно использовать:
    от 4 CPU;
    от 8Гб RAM;
    от 50Гб свободного пространства на диске;
  • Более конкретные данные рассчитываются по обращению в ТП;
  • Отключение или перевод selinux в режим permissive для корректной работы сервисов.

1. Установка и настройка сервиса хранения glusterfs

1.1. Описание

Файловое хранилище:

  • Для Почтового сервера будет использоваться каталог: /mail;
  • Для Корпоративного сервера 2024: /var/r7-office/filestorage /var/r7-office/searchindex;
  • Для Сервера документов: /var/www/r7-office/Data /var/lib/r7-office/documentserver/App_Data/cache.

Нет необходимости синхронизировать временные каталоги:

filestorage_temp
filestorage_temp_proc

БД:

Используется PostgreSQL версии 11 или более новая в режиме master-slave.

1.2. Установка и настройка glusterfs

Добавьте запись в /etc/hosts:

192.168.27.10 gluster1
192.168.27.36 gluster2

Рекомендуется использовать запись 127.0.0.1 localhost как стандартную и без использования имени сервера, так как в дальнейшем будут добавлены другие записи для работы почтового сервера.

Укажите соответствующее имя (поправить на свой домен и IP-адрес сервера), вместо mx1 можно использовать любое необходимое имя, например: mail:

На сервере №1

hostnamectl set-hostname mx1.your-domain.ru

Hostname сервера не совпадает с его доменом. Например, server.example.com — полное имя домена, а server — его hostname. Данная запись необходима для почтового сервера.

Без использования почтового сервера возможно задать любое имя сервера.

Укажите соответствующее имя на сервере №2 для почтового сервера (поправить на свой домен):

На сервере №2

hostnamectl set-hostname mx2.your-domain.ru

Добавьте запись в /etc/hosts:

192.168.27.10 mx1.your-domain.ru
192.168.27.36 mx2.your-domain.ru

Укажите имя и IP ваших серверов.

Для корректной установки корпоративного сервера требуется наличие перевода строки в конце файла /etc/hosts.

Без использования почтового сервера возможно задать любое имя сервера.

Для применения параметров выполните перезапуск серверов.

1.3. На всех нодах установить, запустить и добавить в автозагрузку

apt install -y glusterfs-server
systemctl start glusterd.service
systemctl enable glusterd.service

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

gluster peer probe gluster2

Фактически эта команда сообщает gluster1 доверять gluster2 и регистрирует его как часть пула хранения данных.

Если зондирование пройдет успешно, вы получите следующий вывод:

peer probe: success

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

gluster peer status

Если вы запустите эту команду из gluster2, вы увидите следующий вывод:

Number of Peers: 1

Hostname: gluster1

Uuid: 7ecfa2d1-3394-4f15-a1f5-bba484f2bbef

State: Peer in Cluster (Connected)

Рекомендуется на время установки/настройки отключить firewalld.

На этом этапе два ваших сервера взаимодействуют и готовы к созданию томов хранения друг с другом.

Создание томов

Выполняется на первом сервере.

Для создания тома вы будете использовать команду gluster volume create с таким общим синтаксисом:

для почтового сервера 
sudo gluster volume create mail_volume replica 2 gluster{1,2}:/mail_volume force
sudo gluster volume create cddisk-filestorage replica 2 gluster{1,2}:/cddisk-filestorage force
sudo gluster volume create cddisk-searchindex replica 2 gluster{1,2}:/cddisk-searchindex force
sudo gluster volume create ds-data replica 2 gluster{1,2}:/ds-data force
sudo gluster volume create ds-cache replica 2 gluster{1,2}:/ds-cache force

Если том был создан успешно, вы увидите следующий вывод:

volume create: mail_volume: success: please start the volume to access data

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

для почтового сервера
sudo gluster volume start mail_volume 
sudo gluster volume start cddisk-filestorage 
sudo gluster volume start cddisk-searchindex 
sudo gluster volume start ds-data 
sudo gluster volume start ds-cache

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

volume start: mail_volume: success

volume start: cddisk-filestorage: success

volume start: cddisk-searchindex: success

volume start: ds-data: success

volume start: ds-cache: success

Затем проверьте, находится ли том в сети. Запустите следующую команду с любого из ваших узлов:

sudo gluster volume status

В результате вы увидите вывод, аналогичный данному:

Status of volume: mail

Gluster process TCP Port RDMA Port Online Pid

------------------------------------------------------------------------------

Brick gluster1:/mail 49152 0 Y 4495

Brick gluster2:/mail 49152 0 Y 20192

Self-heal Daemon on localhost N/A N/A Y 4518

Self-heal Daemon on gluster2 N/A N/A Y 20215

Task Status of Volume mail_volume

------------------------------------------------------------------------------

There are no active volume tasks

Создайте каталог и смонтируйте

Инструкция ниже выполняется на всех серверах.

#для почтового сервера 
mkdir /mail 
mkdir -p /var/r7-office/filestorage
mkdir -p /var/r7-office/searchindex
mkdir -p /var/www/r7-office/Data
mkdir -p /var/lib/r7-office/documentserver/App_Data/cache

#для почтового сервера 
mount.glusterfs localhost:/mail_volume /mail
mount.glusterfs localhost:/cddisk-filestorage /var/r7-office/filestorage
mount.glusterfs localhost:/cddisk-searchindex /var/r7-office/searchindex
mount.glusterfs localhost:/ds-data /var/www/r7-office/Data
mount.glusterfs localhost:/ds-cache /var/lib/r7-office/documentserver/App_Data/cache

Добавьте в автозагрузку:

#для почтового сервера 
{
echo 'localhost:/mail_volume /mail glusterfs defaults,_netdev,backupvolfile-server=localhost 0 0'
echo 'localhost:/cddisk-filestorage /var/r7-office/filestorage glusterfs defaults,_netdev,backupvolfile-server=localhost 0 0'
echo 'localhost:/cddisk-searchindex /var/r7-office/searchindex glusterfs defaults,_netdev,backupvolfile-server=localhost 0 0'
echo 'localhost:/ds-data /var/www/r7-office/Data glusterfs defaults,_netdev,backupvolfile-server=localhost 0 0'
echo 'localhost:/ds-cache /var/lib/r7-office/documentserver/App_Data/cache glusterfs defaults,_netdev,backupvolfile-server=localhost 0 0'
} | sudo tee -a /etc/fstab

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

Инструкция ниже выполняется на всех серверах.

После перезагрузки сервера, могут наблюдаться проблемы с синхронизацией glusterfs. Поэтому данный метод решает эту проблему:

echo "@reboot sleep 30 && systemctl restart glusterd.service" | crontab -

Проверка монтирования:

df -h
mount -a

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

При настройке разрешенных портов рекомендуется проверить все задействованные порты на двух серверах:

ss -tulnp | grep gluster

2. Подготовка к установке почтового сервера (*опционально)

2.1. Записи в DNS и необходимые конфигурации

Для почтового сервера необходимо сделать A и MX записи в виде:

MX записи:

mx1 - 192.168.27.186
mx2 - 192.168.26.253

А записи:

smtp - 192.168.27.186, 192.168.26.253
imap - 192.168.27.186, 192.168.26.253

Примеры А записей:

Примеры А записей в DNS

А также TXT запись:

v=spf1 +mx ~all

TXT v=spf1 +a +mx -all говорит о том, что отправлять письма от имени домена your-domain.ru могут сервера, указанные в A и MX-записях этого домена, а письма, отправленные от других серверов должны быть удалены (Fail). Важно понимать: SPF-запись не наследуется на поддомены.

3. Установка и настройка БД

Установите следующие пакеты на оба сервера:

sudo apt update && sudo apt install postgresql -y

3.1. БД

Инструкция ниже выполняется на всех серверах.

Отредактируйте postgresql.conf:

sudo nano /etc/postgresql/14/main/postgresql.conf #в зависимости от версии установленной postgresql сервера, путь к конфигурационному файлу может отличаться

Приведите параметры к виду:

listen_addresses = 'localhost,192.168.26.48' # what IP address(es) to listen on;
port = 5432

Где:

  • localhost, 192.168.26.48 — адреса, которые слушает сервис;
  • 5432 — порт, который сервис прослушивает.

В файле postgresql.conf разрешите логическую репликацию. Для этого отредактируйте /etc/postgresql/14/main/postgresql.conf:

listen_addresses = 'localhost,ip_srv,ip_srv2'       # Слушать на адресах
port = 5432                         # Задать порт БД
wal_level = replica             # Включить репликацию
archive_mode = on               # Включить архивирование WAL
archive_command = 'cp %p /var/lib/postgresql/14/main/archive/%f'   # Команда для архивирования WAL, в зависимости от версии установленной postgresql сервера, путь к файлу может отличаться
max_wal_senders = 5            # Максимальное количество одновременных подключений репликации
max_replication_slots = 5          # Количество слотов для репликации
hot_standby = on                # Разрешить подключения в режиме hot standby
hot_standby_feedback = on		# Определяет, будет или нет сервер slave сообщать мастеру о запросах, которые он выполняет.

Где:

ip_srv1, ip_srv2 — адреса внутренней сети первого и второго почтового сервера.

Инструкция ниже выполняется на всех серверах.

Настройте аутентификацию пользователей в базе данных. Приведите к следующему виду файл /etc/postgresql/14/main/pg_hba.conf:

# Разрешить локальные подключения для всех пользователей
# "local" is for Unix domain socket connections only
local   all             all                              trust
# IPv4 local connections:
host    all             all             127.0.0.1/32     trust
host    all             all             ip_srv1/32       trust
host    all             all             ip_srv2/32       trust

Вместо ip_srv1/32 и ip_srv2/32 укажите IP-адрес двух серверов. Режим trust необходим для установки Корпоративного сервера.

И добавьте строки в конце для возможности репликации БД:

# Разрешить репликацию с обоих серверов для пользователя replication_user
host    replication     replication_user        ip_srv1/32    md5
host    replication     replication_user        ip_srv2/32    md5

Где:

  • ip_another_srv/32 — адрес первого или второго почтового сервера, в зависимости от сервера, где производится настройка.

Выполните перезапуск сервиса БД:

sudo systemctl restart postgresql

Инструкция ниже выполняется на сервере №1.

После базовой настройки базы данных, создаем необходимую структуру в БД, создаем пользователей и настраиваем репликацию:

sudo -u postgres psql

В консоли psql> выполните следующие команды (вместо паролей password и replication_password, cddisk и ds задайте свои пароли и в поле insert into вместо YOUR_DOMAIN укажите реальный адрес вашего домена), подтверждая каждую нажатием Enter:

Не рекомендуется использовать другие имена для баз данных cddisk и ds.

CREATE USER cddisk WITH password 'cddisk';
CREATE USER ds WITH password 'ds';
CREATE DATABASE cddisk OWNER cddisk;
CREATE DATABASE ds OWNER ds;
GRANT ALL privileges ON DATABASE cddisk TO cddisk;
GRANT ALL privileges ON DATABASE ds TO ds;
CREATE USER replication_user WITH REPLICATION LOGIN PASSWORD 'replication_password';
GRANT CONNECT ON DATABASE cddisk TO replication_user;
GRANT CONNECT ON DATABASE ds TO replication_user;

\c cddisk
GRANT USAGE ON SCHEMA public TO replication_user;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO replication_user;
GRANT SELECT ON ALL SEQUENCES IN SCHEMA public TO replication_user;

\c ds
GRANT USAGE ON SCHEMA public TO replication_user;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO replication_user;
GRANT SELECT ON ALL SEQUENCES IN SCHEMA public TO replication_user;

Для выхода из БД
\q

Инструкция ниже выполняется на сервере №1.

Создайте директорию для архива:

#в зависимости от версии установленной postgresql сервера, путь к каталогу может отличаться
sudo mkdir -p /var/lib/postgresql/14/main/archive
sudo chown -R postgres:postgres /var/lib/postgresql/14/main/archive
sudo chmod -R 750 /var/lib/postgresql/14/main/archive

Инструкция ниже выполняется на сервере №1.

Перезагрузите службу PostgreSQL для применения изменений:

systemctl restart postgresql

3.2. Настройка репликации БД

Инструкция ниже выполняется на сервере №2.

Удалите существующую директорию данных:

#в зависимости от версии установленной postgresql сервера, путь к каталогу может отличаться
rm -rf /var/lib/postgresql/14/main

Создайте базовую резервную копию с мастера:

#в зависимости от версии установленной postgresql сервера, путь pgdata=/var/lib/postgresql/14/main может отличаться
su - postgres -c "pg_basebackup --host=ip_srv1 --username=replication_user --pgdata=/var/lib/postgresql/14/main --wal-method=stream --write-recovery-conf"

Где:

ip_srv1 — адрес внутренней сети первого сервера, с master БД.

Перезапустите сервис и проверьте его статус:

systemctl restart postgresql.service
systemctl status postgresql.service

4. Установка Корпоративного сервера

Выполнение установки по инструкции Установка Корпоративный сервер 2024 на ОС Астра Линукс.

4.1. Предварительная подготовка

Инструкция ниже выполняется на сервере №1.

Для корректной установки, разместите архив в директории, отличной от /root, например в /mnt или /tmp.

Перейдите в каталог:

cd /mnt

Распакуйте архив:

unzip CDinstall_*.zip

Для корректной работы Корпоративного сервера 2024 обязательно требуется настройка HTTPS. Перед установкой скопируйте crt и key файлы в папку sslcert.

Предоставьте права на скрипт установки:

chmod +x online_installer.sh

Создайте DNS запись для Корпоративного сервера, например:

DNS запись для Корпоративного сервера

Если Offline установка

chmod +x offline_installer.sh

Для offline установки требуется установить пакеты в систему и подключить ISO-образ установочного диска операционный системы в папку distr.

Скачать пакеты можно по данной ссылке.

Архив cddisk.zip содержит необходимые пакеты для Корпоративного сервера 2024.

1. Установить wget;
2. Скачать архив cddisk.zip, распаковать и установить пакеты командой
sh install.sh.

В папку ./distr положите ISO-образ установочного диска операционный системы. Файл должен быть с расширением .iso. Данный образ вы можете скачать с официального сайта производителя.

4.2. Запуск установки первого сервера

Инструкция ниже выполняется на сервере №1.

Если установка online

Выполните команду:

./online_installer.sh

Если установка offline

Убедитесь, что все репозитории отключены, чтобы установка пакетов происходила исключительно из offline архива. Для этого удалите или закомментируйте (добавив символ # в начало строки) все строки в файле /etc/apt/sources.list, а также очистите содержимое папки /etc/apt/sources.list.d.

После этого запустите установку:

./offline_installer.sh

Выберите Нет, иначе произойдет удаление PostgreSQL:

Диалог установки - выбор Нет для PostgreSQL

Выберите Нет:

Диалог установки - повторный выбор Нет

Выберите Да:

Диалог установки - выбор Да для продолжения

Задайте secret. Необходимо ввести секрет (Набор цифр, букв и спецсимволов. Длина от 8 символов) для защищённого доступа Р7-Диска и Сервера документов:

Диалог установки - ввод секрета

Укажите пароль к БД ds, ранее созданный в пункте 3.1 (из примера: ds):

Диалог установки - ввод пароля БД ds

Выберите Да:

Диалог установки - подтверждение выбора

Выберите PostgreSQL:

Диалог установки - выбор PostgreSQL

Выберите Нет:

Диалог установки - отказ от автоматической настройки

Укажите БД master и IP-адрес текущего первого сервера:

Диалог установки - указание БД master и IP-адреса

Укажите по умолчанию порт 5432:

Диалог установки - указание порта 5432

Укажите БД cddisk из пункта 3.1 (из примера: cddisk):

Диалог установки - указание БД cddisk

Укажите пароль к БД cddisk из пункта 3.1 (из примера: cddisk):

Диалог установки - ввод пароля БД cddisk

Измените на актуальный, если есть Корпоративный сервер 2019 и нажмите ОК:

Диалог установки - настройка совместимости с КС 2019

Выберите Да:

Диалог установки - финальное подтверждение

Необходимо указать домен, в котором у вас созданы записи из пункта подготовки:

Диалог установки - указание домена

Далее укажите необходимые префиксы для всех модулей.

Выберите Да (если требуется установка почтового сервера):

Диалог установки - выбор установки почтового сервера

Дальнейшие действия нужно выполнять при установке Корпоративного сервера 2024 с Почтовым сервером, иначе перезагрузите сервер.

Выберите PostgreSQL:

Диалог установки почтового сервера - выбор PostgreSQL

Необходимо указать имя сервера:

Диалог установки почтового сервера - указание имени сервера

Укажите IP-адрес сервера:

Диалог установки почтового сервера - указание IP-адреса

Укажите пароль для пользователя postfix:

Диалог установки почтового сервера - ввод пароля postfix

Если требуется установка SpamAssassin:

  • Выберите 1

Если не требуется установка SpamAssassin:

  • Выберите 2

Диалог установки почтового сервера - выбор SpamAssassin

Если требуется установка:

  • Выберите Да

Если не требуется установка:

  • Выберите Нет

Диалог установки почтового сервера - финальное подтверждение

После инсталляции в консоли будет предложено сделать TXT-запись:

Отказоустойчивая архитектура Корпоративный сервер 2024 + Почтовый сервер на Astra Linux 1.7.4

Перезапустите сервер.

4.3. Установка второго сервера

Инструкция ниже выполняется на сервере №2.

Скачайте дистрибутив.

Для корректной установки, разместите архив в директории, отличной от /root, например в /mnt или /tmp.

Перейдите в каталог:

cd /mnt

Распакуйте архив:

unzip CDinstall_*.zip

Для корректной работы Корпоративного сервера 2024 обязательно требуется настройка HTTPS. Перед установкой скопируйте crt и key файлы в папку sslcert.

Предоставьте права на скрипт установки:

chmod +x online_installer.sh

Если Offline установка

chmod +x offline_installer.sh

Для offline установки требуется установить пакеты в систему и подключить ISO-образ установочного диска операционный системы в папку distr.

Скачать пакеты можно по данной ссылке.

Архив cddisk.zip содержит необходимые пакеты для Корпоративного сервера 2024.

1. Установить wget;
2. Скачать архив cddisk.zip, распаковать и установить пакеты командой
sh install.sh.

В папку ./distr положите ISO-образ установочного диска операционный системы. Файл должен быть с расширением .iso. Данный образ вы можете скачать с официального сайта производителя.

4.4. Запуск установки

Если установка online

Выполните команду:

./online_installer.sh

Если установка offline

Убедитесь, что все репозитории отключены, чтобы установка пакетов происходила исключительно из offline архива. Для этого удалите или закомментируйте (добавив символ # в начало строки) все строки в файле /etc/apt/sources.list, а также очистите содержимое папки /etc/apt/sources.list.d.

После этого запустите установку:

./offline_installer.sh

Выберите Нет:

Диалог установки второго сервера - выбор Нет

Выберите Нет:

Диалог установки второго сервера - повторный выбор Нет

Выберите Да:

Диалог установки второго сервера - выбор Да

Укажите такой же secret как для первого сервера:

Диалог установки второго сервера - ввод secret

Укажите пароль к БД ds из пункта 3.1 (из примера ds):

Диалог установки второго сервера - ввод пароля БД ds

Выберите Да:

Диалог установки второго сервера - подтверждение

Выберите PostgreSQL:

Диалог установки второго сервера - выбор PostgreSQL

Выберите Нет:

Диалог установки второго сервера - отказ от автоконфигурации

Укажите IP-адрес первого сервера master:

Диалог установки второго сервера - указание IP master

Укажите порт 5432:

Диалог установки второго сервера - указание порта

Укажите БД cddisk из пункта 3.1 (из примера: cddisk):

Диалог установки второго сервера - указание БД cddisk

Укажите пароль к БД cddisk из пункта 3.1 (из примера: cddisk):

Диалог установки второго сервера - ввод пароля cddisk

Измените на актуальный, если есть Корпоративный сервер 2019 и нажмите ОК:

Диалог установки второго сервера - настройка совместимости

Выберите Да:

Диалог установки второго сервера - финальное подтверждение

Необходимо указать домен, в котором у вас созданы записи из пункта подготовки:

Диалог установки второго сервера - указание домена

Далее укажите необходимые префиксы для всех модулей, одинаковые с первым сервером.

Выберите Да (если требуется установка почтового сервера):

Диалог установки второго сервера - выбор почтового сервера

Дальнейшие действия нужно выполнять при установке Корпоративного сервера 2024 с Почтовым сервером, иначе перезагрузите сервер.

Выберите PostgreSQL:

Диалог установки почтового сервера на втором сервере

Необходимо указать имя для второго сервера:

Диалог установки почтового сервера на втором сервере - имя

Укажите IP-адрес сервера:

Диалог установки почтового сервера на втором сервере - IP-адрес

Укажите пароль для пользователя postfix (нужно указать такой же пароль, как на первом сервере):

Диалог установки почтового сервера на втором сервере - пароль postfix

Если требуется установка SpamAssassin (выберите тот же вариант, который указали при установке первого сервера):

  • Выберите 1

Если не требуется установка SpamAssassin:

  • Выберите 2

Диалог установки почтового сервера на втором сервере - SpamAssassin

Если требуется установка (выберите тот же вариант, которые указали при установке первого сервера):

  • Выберите Да

Если не требуется установка:

  • Выберите Нет

Диалог установки почтового сервера на втором сервере - финальный выбор

После инсталляции в консоли будет предложено сделать TXT запись:

Установка Р7-Диск на ОС Astra Linux и Debian - TXT запись

Перезапустите сервер.

После установки корпоративного сервера на втором сервере в базу добавляется вторая корневая организация с id = 2.

Вторая корневая организация в базе данных

Для его удаления требуется в таблице Customers изменить IsRoot на false.

Инструкция ниже выполняется на сервере №1.

Выполните:

sudo -u postgres psql
\c cddisk
UPDATE "Customers" SET "IsRoot"=false WHERE "Id"=2;
# Для выхода из БД
\q

Затем удалите вторую корневую организацию через веб-интерфейс.

5. Выполните дополнительную настройку Nginx и Сервер документов

Выведите значение параметра secure_link_secret на первом сервере:

grep -rn "set \$secure_link_secret" /etc/r7-office/documentserver/nginx/ds.conf

Пример вывода:

13: set $secure_link_secret kCj7RMAAYCNtOh6KiGin;

Для второго сервера:

sudo sed -i "s/set \$secure_link_secret [^;]*/set \$secure_link_secret НОВОЕ_ЗНАЧЕНИЕ/" $(grep -rl "set \$secure_link_secret" /etc/r7-office/documentserver/)
sudo sed -i -E "s/\"secretString\": .+/\"secretString\": \"НОВОЕ_ЗНАЧЕНИЕ\"/" $(grep -rl "secretString" /etc/r7-office/documentserver/)

Где, замените НОВОЕ_ЗНАЧЕНИЕ на необходимый параметр c вывода предыдущей команды (пример, kCj7RMAAYCNtOh6KiGin).

Выполните команды по перезапуску Nginx и Сервера документов:

systemctl restart nginx ds-converter.service ds-docservice.service ds-metrics.service

6. Настройка подключения почтового сервера (*опционально)

Перейдите в https://admin.test2.s7-office.site/ Организации — Выберите организацию, в которую добавлены пользователи — Почтовые серверы:

Настройка подключения почтового сервера - интерфейс

Укажите ранее добавленные данные по почтовому серверу:

Настройка подключения почтового сервера - данные сервера

Скопируйте папку /home/mail_cddisk/.ssh из первого сервера во второй сервер.

Выполните на втором сервере:

chown -R mail_cddisk:mail_cddisk /home/mail_cddisk/.ssh

7. Управление переключением БД при неработоспособности первого сервера

7.1. Создайте скрипт управления

Готовый скрипт можно скачать по данной ссылке.

Укажите необходимые константы в начале скрипта. Потребуется указать необходимые параметры, что является master (первый сервер) и slave (второй сервер).

Параметры указанные ранее в пункте 3.1.

Константы для подключения к БД

  • MASTER_IP="192.168.27.186" — укажите адрес master БД;
  • SLAVE_IP="192.168.26.253" — укажите адрес replica БД;
  • REPLICATION_USER="replication_user" — укажите пользователя для репликации;
  • REPLICATION_PASS="replication_password" — пароль от пользователя для репликации;
  • LOG_FILE="/var/log/switch_postgres.log" — расположение файла логирования скрипта;
  • PG_DATA="/var/lib/postgresql/11/main" — расположение каталога с базой;
  • PG_BIN="/usr/lib/postgresql/11/bin" — расположение исполняемых файлов PostgreSQL.

Константы для cddisk (appsettings.json)

  • CDDISK_DB_NAME="cddisk" — не рекомендуется изменять;
  • CDDISK_DB_USER="cddisk" — укажите пользователя Корпоративного сервера 2024;
  • CDDISK_DB_PASS="cddisk" — укажите пароль для пользователя Корпоративного сервера 2024;
  • CDDISK_DB_PORT="5432" — укажите порт БД.

Константы для documentserver (local.json)

  • DS_DB_NAME="ds" — не рекомендуется изменять;
  • DS_DB_USER="ds" — укажите пользователя ДС;
  • DS_DB_PASS="ds" — укажите пароль для пользователя ДС;
  • DS_DB_PORT="5432" — укажите порт БД.

Константы для почтового сервера (postfix, dovecot)

  • MAIL_DB_NAME="postfix" — не рекомендуется изменять;
  • MAIL_DB_USER="postfix" — укажите пользователя Postfix;
  • MAIL_DB_PASS="postfix" — укажите пароль пользователя Postfix;
  • MAIL_DB_PORT="5432" — укажите порт БД.

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

#!/bin/bash
  
# === Константы для подключения к БД ===
MASTER_IP="192.168.99.67"
SLAVE_IP="192.168.99.240"
REPLICATION_USER="replication_user"
REPLICATION_PASS="replication_password"
LOG_FILE="/mail/log/switch_postgres.log"
PG_DATA="/var/lib/postgresql/11/main"
PG_BIN="/usr/lib/postgresql/11/bin"
  
# === Константы для cddisk (appsettings.json) ===
CDDISK_DB_NAME="cddisk"
CDDISK_DB_USER="cddisk"
CDDISK_DB_PASS="cddisk"
CDDISK_DB_PORT="5432"
  
# === Константы для documentserver (local.json) ===
DS_DB_NAME="ds"
DS_DB_USER="ds"
DS_DB_PASS="ds"
DS_DB_PORT="5432"
 
# === Константы для почтового сервера (postfix, dovecot) ===
MAIL_DB_NAME="postfix"
MAIL_DB_USER="postfix"
MAIL_DB_PASS="postfix"
MAIL_DB_PORT="5432"
 
declare -a postfix_configs=(
    "/etc/postfix/pgsql/virtual_alias_maps.cf"
    "/etc/postfix/pgsql/virtual_mailbox_domains.cf"
    "/etc/postfix/pgsql/virtual_mailbox_maps.cf"
)
 
declare -a dovecot_configs=(
    "/etc/dovecot/dovecot-pgsql.conf"
)
 
declare -a cs_ds_configs=(
    "/opt/r7-office/Api/appsettings.json"
    "/opt/r7-office/Sso.Api/appsettings.json"
    "/opt/r7-office/Processing/appsettings.json"
    "/etc/r7-office/documentserver/local.json"
)
 
declare -a cs_configs=(
    "/opt/r7-office/Api/appsettings.json"
    "/opt/r7-office/Sso.Api/appsettings.json"
    "/opt/r7-office/Processing/appsettings.json"
)
 
function log() {
    echo "$(date '+%Y-%m-%d %H:%M:%S') — $1" | tee -a "$LOG_FILE"
}
  
function check_superuser() {
    if [[ $EUID -ne 0 ]]; then
        log "Ошибка: Скрипт должен запускаться от root."
        exit 1
    fi
}
  
function get_local_ip() {
    hostname -I | awk '{print $1}'
}
 
function get_local_hostname() {
    hostname | awk '{print $1}'
}
 
function is_master() {
    [[ "$(get_postgres_role)" == "master" ]]
}
  
function is_slave() {
    [[ "$(get_postgres_role)" == "slave" ]]
}
  
function backup_file_if_needed() {
    local file="$1"
    local bak_file="${file}.bak"
  
    if [[ ! -f "$bak_file" ]]; then
        cp "$file" "$bak_file"
        log "Создана резервная копия: $bak_file"
    fi
}
  
function restore_from_backup() {
    local file="$1"
    local bak_file="${file}.bak"
  
    if [[ -f "$bak_file" ]]; then
        log "Восстановление файла $file из резервной копии..."
        cp "$bak_file" "$file"
    else
        log "⚠️ Резервная копия для $file не найдена. Пропущено."
    fi
}
  
function restore_all_configs() {
    log "Начало восстановления конфигураций из резервных копий..."
  
    for file in "${cs_ds_configs[@]}"; do
        restore_from_backup "$file"
    done
 
    for file in "${postfix_configs[@]}"; do
        restore_from_backup "$file"
    done
 
    for file in "${dovecot_configs[@]}"; do
        restore_from_backup "$file"
    done
  
    log "✅ Восстановление завершено."
}
  
function extract_cddisk_db_credentials() {
    local file="/opt/r7-office/Api/appsettings.json"
  
    if [[ ! -f "$file" ]]; then
        log "Файл $file не найден."
        return 1
    fi
  
    # Извлекаем строку подключения
    local line
    line=$(grep -A2 '"R7StorageServerUserActions"' "$file" | tr -d '
')
    if [[ -z "$line" ]]; then
        log "Стoрока R7StorageServerUserActions не найдена в $file"
        return 1
    fi
  
    local conn_str
    conn_str=$(echo "$line" | sed -E 's/.*"R7StorageServerUserActions"[[:space:]]*:[[:space:]]*"([^"]+)".*/\1/')
  
    if [[ -z "$conn_str" ]]; then
        log "Не удалось извлечь строку подключения из $file"
        return 1
    fi
  
    DB_NAME=$(echo "$conn_str" | grep -oP 'Database=\K[^;]+')
    DB_USER=$(echo "$conn_str" | grep -oP 'Username=\K[^;]+')
    DB_PASS=$(echo "$conn_str" | grep -oP 'Password=\K[^;]+')
    DB_HOST=$(echo "$conn_str" | grep -oP 'Host=\K[^;]+')
    DB_PORT=$(echo "$conn_str" | grep -oP 'Port=\K[^;]*')
  
    DB_PORT="${DB_PORT:-5432}"
  
    if [[ -z "$DB_NAME" || -z "$DB_HOST" ]]; then
        log "В строке подключения отсутствуют необходимые параметры (Database и Host)."
        return 1
    fi
  
    export DB_NAME DB_USER DB_PASS DB_HOST DB_PORT
}
  
function extract_documentserver_db_credentials() {
    local config="/etc/r7-office/documentserver/local.json"
  
    if [[ ! -f "$config" ]]; then
        log "⚠️ Файл $config не найден."
        return 1
    fi
  
    DS_DB_HOST=$(grep '"dbHost"' "$config" | awk -F '"' '{print $4}')
    DS_DB_NAME=$(grep '"dbName"' "$config" | awk -F '"' '{print $4}')
    DS_DB_USER=$(grep '"dbUser"' "$config" | awk -F '"' '{print $4}')
    DS_DB_PASS=$(grep '"dbPass"' "$config" | awk -F '"' '{print $4}')
    DS_DB_PORT=$(grep '"dbPort"' "$config" | awk -F '"' '{print $4}')
  
    DS_DB_PORT="${DS_DB_PORT:-5432}"
  
    if [[ -z "$DS_DB_HOST" || -z "$DS_DB_USER" || -z "$DS_DB_PASS" ]]; then
        log "⚠️ В файле local.json отсутствуют данные для подключения к БД."
        return 1
    fi
  
    export DS_DB_HOST DS_DB_NAME DS_DB_USER DS_DB_PASS DS_DB_PORT
}
  
function check_db_connection_after_change() {
    local new_master="$1"
    log "Проверка подключения к БД с новыми параметрами..."
  
    # === cddisk: используем константы с обновлённым Host ===
    local db_name="$CDDISK_DB_NAME"
    local db_user="$CDDISK_DB_USER"
    local db_pass="$CDDISK_DB_PASS"
    local db_host="$new_master"
    local db_port="${CDDISK_DB_PORT:-5432}"
  
    log "Попытка подключения (cddisk): Host=$db_host, User=$db_user, Database=$db_name, Port=$db_port"
    echo "$db_pass" | PGPASSWORD="$db_pass" psql -h "$db_host" -U "$db_user" -p "$db_port" -d "$db_name" -c "\q" > /dev/null 2>&1
    if [ $? -eq 0 ]; then
        log "✅ Успешное подключение к cddisk"
    else
        log "❌ Не удалось подключиться к cddisk"
        return 1
    fi
  
    # === documentserver: используем DS_* константы с обновлённым Host ===
    local ds_name="$DS_DB_NAME"
    local ds_user="$DS_DB_USER"
    local ds_pass="$DS_DB_PASS"
    local ds_host="$new_master"
    local ds_port="${DS_DB_PORT:-5432}"
  
    log "Попытка подключения (documentserver): Host=$ds_host, User=$ds_user, Database=$ds_name, Port=$ds_port"
    echo "$ds_pass" | PGPASSWORD="$ds_pass" psql -h "$ds_host" -U "$ds_user" -p "$ds_port" -d "$ds_name" -c "\q" > /dev/null 2>&1
    if [ $? -eq 0 ]; then
        log "✅ Успешное подключение к documentserver"
    else
        log "❌ Не удалось подключиться к documentserver"
        return 1
    fi
 
    # === mail: используем MAIL_* константы с обновлённым Host ===
    local ds_name="$MAIL_DB_NAME"
    local ds_user="$MAIL_DB_USER"
    local ds_pass="$MAIL_DB_PASS"
    local ds_host="$new_master"
    local ds_port="${MAIL_DB_PORT:-5432}"
  
    log "Попытка подключения (postfix): Host=$ds_host, User=$ds_user, Database=$ds_name, Port=$ds_port"
    echo "$ds_pass" | PGPASSWORD="$ds_pass" psql -h "$ds_host" -U "$ds_user" -p "$ds_port" -d "$ds_name" -c "\q" > /dev/null 2>&1
    if [ $? -eq 0 ]; then
        log "✅ Успешное подключение к postfix"
    else
        log "❌ Не удалось подключиться к postfix"
        return 1
    fi
  
    return 0
}
 
function extract_mail_db_credentials() {
    local file="/etc/dovecot/dovecot-pgsql.conf"
  
    if [[ ! -f "$file" ]]; then
        log "Файл $file не найден."
        return 1
    fi
  
    DB_NAME=$(grep -E "dbname\s*=\s*+"  "$file" | awk -F"=" '{print $2}') | sed 's/^[[:space:]]*//; s/[[:space:]]*$//'
    if [[ -z "$DB_NAME" ]]; then
        log "Стoрока dbname =  не найдена в $file"
        return 1
    fi
 
    DB_USER=$(grep -E "user\s*=\s*+"  "$file" | awk -F"=" '{print $2}') | sed 's/^[[:space:]]*//; s/[[:space:]]*$//'
    if [[ -z "$DB_USER" ]]; then
        log "Стoрока user =  не найдена в $file"
        return 1
    fi
 
    DB_PASS=$(grep -E "password\s*=\s*+"  "$file" | awk -F"=" '{print $2}') | sed 's/^[[:space:]]*//; s/[[:space:]]*$//'
    if [[ -z "$DB_PASS" ]]; then
        log "Стoрока password =  не найдена в $file"
        return 1
    fi
 
    DB_HOST=$(grep -E "hosts\s*=\s*[^\:]+:"  "$file" | awk '{print $3}'| awk -F":" '{print $1}') | sed 's/^[[:space:]]*//; s/[[:space:]]*$//'
    if [[ -z "$DB_HOST" ]]; then
        log "Стoрока hosts =  не найдена в $file"
        return 1
    fi
 
    DB_PORT=$(grep -E "hosts\s*=\s*[^\:]+:"  "$file" | awk '{print $3}'| awk -F":" '{print $2}') | sed 's/^[[:space:]]*//; s/[[:space:]]*$//'
    if [[ -z "$DB_PORT" ]]; then
        log "Стoрока hosts = :  не найдена в $file"
        return 1
    fi
 
    DB_PORT="${DB_PORT:-5432}"
  
    if [[ -z "$DB_NAME" || -z "$DB_HOST" ]]; then
        log "В строке подключения отсутствуют необходимые параметры (Database и Host)."
        return 1
    fi
  
    export DB_NAME DB_USER DB_PASS DB_HOST DB_PORT
}
 
function check_db_connection() {
    log "Проверка подключения к БД с текущими параметрами..."
  
    # === cddisk ===
    if extract_cddisk_db_credentials; then
        local db_host="$DB_HOST"
        local db_name="$DB_NAME"
        local db_user="$DB_USER"
        local db_pass="$DB_PASS"
        local db_port="${DB_PORT:-5432}"
  
        log "Попытка подключения (cddisk): Host=$db_host, User=$db_user, Database=$db_name, Port=$db_port"
  
        echo "$db_pass" | PGPASSWORD="$db_pass" psql -h "$db_host" -U "$db_user" -p "$db_port" -d "$db_name" -c "SELECT version();" > /dev/null 2>&1
  
        if [ $? -eq 0 ]; then
            log "Успешное подключение к БД от пользователя $db_user"
        else
            log "Не удалось подключиться к БД от пользователя $db_user. Проверьте настройки доступа или пароль."
        fi
    else
        log "⚠️ Пропущена проверка cddisk: не удалось получить данные из appsettings.json"
    fi
  
    # === documentserver ===
    if extract_documentserver_db_credentials; then
        local ds_host="$DS_DB_HOST"
        local ds_name="$DS_DB_NAME"
        local ds_user="$DS_DB_USER"
        local ds_pass="$DS_DB_PASS"
        local ds_port="$DS_DB_PORT"
  
        log "Попытка подключения (documentserver): Host=$ds_host, User=$ds_user, Database=$ds_name, Port=$ds_port"
  
        echo "$ds_pass" | PGPASSWORD="$ds_pass" psql -h "$ds_host" -U "$ds_user" -p "$ds_port" -d "$ds_name" -c "SELECT version();" > /dev/null 2>&1
  
        if [ $? -eq 0 ]; then
            log "Успешное подключение к БД от пользователя $ds_user"
        else
            log "Не удалось подключиться к БД от пользователя $ds_user. Проверьте настройки доступа или пароль."
        fi
    else
        log "⚠️ Пропущена проверка documentserver: не удалось получить данные из local.json"
    fi
 
    # === mail ===
    if extract_mail_db_credentials; then
        local db_host="$DB_HOST"
        local db_name="$DB_NAME"
        local db_user="$DB_USER"
        local db_pass="$DB_PASS"
        local db_port="${DB_PORT:-5432}"
  
        log "Попытка подключения (postfix): Host=$db_host, User=$db_user, Database=$db_name, Port=$db_port"
  
        echo "$db_pass" | PGPASSWORD="$db_pass" psql -h "$db_host" -U "$db_user" -p "$db_port" -d "$db_name" -c "SELECT version();" > /dev/null 2>&1
  
        if [ $? -eq 0 ]; then
            log "Успешное подключение к БД от пользователя $db_user"
        else
            log "Не удалось подключиться к БД от пользователя $db_user. Проверьте настройки доступа или пароль."
        fi
    else
        log "⚠️ Пропущена проверка postfix: не удалось получить данные из virtual_alias_maps.cf"
    fi
  
    return 0
}
  
function update_cddisk_configs() {
    local new_master="$1"
    log "Обновление appsettings.json для cddisk на новый мастер: $new_master..."
  
    # Формируем новые строки подключения с новым IP
    local conn_user_actions="Database=$CDDISK_DB_NAME;Username=$CDDISK_DB_USER;Password=$CDDISK_DB_PASS;Host=$new_master;Port=$CDDISK_DB_PORT;"
    local conn_server="Database=$CDDISK_DB_NAME;Username=$CDDISK_DB_USER;Password=$CDDISK_DB_PASS;Host=$new_master;Port=$CDDISK_DB_PORT;"
 
    for file in "${cs_configs[@]}"; do
        if [[ ! -f "$file" ]]; then
            log "⚠️ Файл $file не найден, пропущено."
            continue
        fi
  
        backup_file_if_needed "$file"
  
        # === R7StorageServerUserActions ===
        local old_line=$(grep -A2 '"R7StorageServerUserActions"' "$file" | tr -d '
')
        if [[ -n "$old_line" ]]; then
            log "Старая строка (R7StorageServerUserActions): $old_line"
            sed -i "s|\"R7StorageServerUserActions\": *\"[^\"]*\"|\"R7StorageServerUserActions\": \"$conn_user_actions\"|" "$file"
            if [[ $? -eq 0 ]]; then
                log "✅ R7StorageServerUserActions в $file обновлён"
            else
                log "❌ Ошибка при обновлении R7StorageServerUserActions в $file"
            fi
        else
            log "⚠️ R7StorageServerUserActions не найден в $file"
        fi
  
        # === R7StorageServer ===
        local old_line=$(grep -A2 '"R7StorageServer"' "$file" | tr -d '
')
        if [[ -n "$old_line" ]]; then
            log "Старая строка (R7StorageServer): $old_line"
            sed -i "s|\"R7StorageServer\": *\"[^\"]*\"|\"R7StorageServer\": \"$conn_server\"|" "$file"
            if [[ $? -eq 0 ]]; then
                log "✅ R7StorageServer в $file обновлён"
            else
                log "❌ Ошибка при обновлении R7StorageServer в $file"
            fi
        else
            log "⚠️ R7StorageServer не найден в $file"
        fi
    done
}
  
function update_documentserver_config() {
    local new_master="$1"
    local config="/etc/r7-office/documentserver/local.json"
    if [[ ! -f "$config" ]]; then
        log "⚠️ Файл $config не найден, пропущено."
        return 1
    fi
    backup_file_if_needed "$config"
  
    current_host=$(grep '"dbHost"' "$config" | awk -F '"' '{print $4}')
    if [[ "$current_host" == "$new_master" ]]; then
        log "IP-адрес в $config уже актуален ($new_master). Изменение не требуется."
        return 0
    fi
  
    log "Старый dbHost: $current_host"
    sed -i "s/\"dbHost[^\"]*\": *\"[^\"]*\"/\"dbHost\": \"$new_master\"/" "$config"
    if [[ $? -eq 0 ]]; then
        log "dbHost в $config успешно обновлён на $new_master"
    else
        log "❌ Ошибка обновления $config"
    fi
}
  
function restart_supervisor_services() {
    log "Перезапуск supervisor-сервисов..."
    supervisorctl restart all
}
  
function restart_documentserver_services() {
    log "Перезапуск ds-сервисов..."
  
    local prepare_script="/usr/bin/documentserver-prepare4shutdown.sh"
  
    if [[ -f "$prepare_script" ]]; then
        log "Выполняется подготовительный скрипт: $prepare_script..."
        "$prepare_script"
        if [[ $? -eq 0 ]]; then
            log "Подготовительный скрипт выполнен успешно."
        else
            log "⚠️ Подготовительный скрипт завершился с ошибкой. Продолжить перезапуск? (y/n)"
            read -p "Продолжить? " confirm
            [[ "$confirm" != "y" && "$confirm" != "Y" ]] && log "Перезапуск отменён пользователем." && return 1
        fi
    else
        log "⚠️ Подготовительный скрипт $prepare_script не найден. Пропущено."
    fi
  
    local services=(
        "ds-converter.service"
        "ds-docservice.service"
        "ds-metrics.service"
    )
  
    for service in "${services[@]}"; do
        if systemctl is-active --quiet "$service"; then
            log "Перезапуск $service..."
            systemctl daemon-reload > /dev/null 2>&1
            systemctl restart "$service"
            log "$service перезапущен."
        else
            if systemctl list-units --type=service | grep -q "$service"; then
                log "⚠️ Сервис $service найден, но не активен. Пропущено."
            else
                log "⚠️ Сервис $service не найден. Проверьте установку документ-сервера."
            fi
        fi
    done
  
    log "✅ Все актуальные ds-сервисы перезапущены."
}
 
function stop_documentserver_services() {
    log "Останов ds-сервисов..."
  
    local prepare_script="/usr/bin/documentserver-prepare4shutdown.sh"
  
    if [[ -f "$prepare_script" ]]; then
        log "Выполняется подготовительный скрипт: $prepare_script..."
        "$prepare_script"
        if [[ $? -eq 0 ]]; then
            log "Подготовительный скрипт выполнен успешно."
        else
            log "⚠️ Подготовительный скрипт завершился с ошибкой. Продолжить останов? (y/n)"
            read -p "Продолжить? " confirm
            [[ "$confirm" != "y" && "$confirm" != "Y" ]] && log "Остановка сервиса отменён пользователем." && return 1
        fi
    else
        log "⚠️ Подготовительный скрипт $prepare_script не найден. Пропущено."
    fi
  
    local services=(
        "ds-converter.service"
        "ds-docservice.service"
        "ds-metrics.service"
    )
  
    for service in "${services[@]}"; do
        if systemctl is-active --quiet "$service"; then
            log "Перезапуск $service..."
            systemctl daemon-reload > /dev/null 2>&1
            systemctl restart "$service"
            log "$service перезапущен."
        else
            if systemctl list-units --type=service | grep -q "$service"; then
                log "⚠️ Сервис $service найден, но не активен. Пропущено."
            else
                log "⚠️ Сервис $service не найден. Проверьте установку документ-сервера."
            fi
        fi
    done
  
    log "✅ Все актуальные ds-сервисы остановлены."
}
 
function update_postfix_configs() {
    local new_master="$1"
    log "Обновление файлов /etc/postfix/pgsql/* для postfix на новый мастер: $new_master..."
  
    for file in "${postfix_configs[@]}"; do
        if [[ ! -f "$file" ]]; then
            log "⚠️ Файл $file не найден, пропущено."
            continue
        fi
  
        backup_file_if_needed "$file"
  
        # === Postfix configs ===
        local old_line=$(grep -E "hosts\s*=\s*[^\:]+:"  "$file" | tr -d '
')
        if [[ -n "$old_line" ]]; then
            log "Старая строка (hosts = ): $old_line"
            sed -i -E "s|hosts\s*=\s*[^\:]+:|hosts = $new_master:|" "$file"
            if [[ $? -eq 0 ]]; then
                log "✅ hosts =  в $file обновлён"
            else
                log "❌ Ошибка при обновлении hosts =  в $file"
            fi
        else
            log "⚠️ hosts =  не найден в $file"
        fi
  
    done
}
 
function update_dovecot_configs() {
    local new_master="$1"
    log "Обновление файла /etc/dovecot/dovecot-pgsql.conf для dovecot на новый мастер: $new_master..."
  
    for file in "${dovecot_configs[@]}"; do
        if [[ ! -f "$file" ]]; then
            log "⚠️ Файл $file не найден, пропущено."
            continue
        fi
  
        backup_file_if_needed "$file"
  
        # === Dovecot configs ===
        local old_line=$(grep -E "host=[^\ ]+"  "$file" | tr -d '
')
        if [[ -n "$old_line" ]]; then
            log "Старая строка (host=): $old_line"
            sed -i -E "s|host=[^\ ]+|host=$new_master|" "$file"
            if [[ $? -eq 0 ]]; then
                log "✅ host= в $file обновлён"
            else
                log "❌ Ошибка при обновлении host= в $file"
            fi
        else
            log "⚠️ host= не найден в $file"
        fi
  
    done
}
 
function restart_mail_services() {
  
    local services=(
        "postfix.service"
        "dovecot.service"
    )
  
    for service in "${services[@]}"; do
        if systemctl is-active --quiet "$service"; then
            log "Перезапуск $service..."
            #systemctl daemon-reload > /dev/null 2>&1
            systemctl restart "$service"
            log "$service перезапущен."
        else
            if systemctl list-units --type=service | grep -q "$service"; then
                log "⚠️ Сервис $service найден, но не активен. Пропущено."
            else
                log "⚠️ Сервис $service не найден. Проверьте установку документ-сервера."
            fi
        fi
    done
  
}
 
function promote_slave_to_master() {
    log "Продвижение slave в master..."
  
    local pg_data="$PG_DATA"
    if [[ -f "$pg_data/standby.signal" && -f "$pg_data/recovery.signal" ]]; then
        log "⚠️ Сервер не находится в режиме standby. Пропущено продвижение в master."
        return 0
    fi
  
    sudo -u postgres ${PG_BIN}/pg_ctl promote -D "$pg_data"
    if [ $? -eq 0 ]; then
        log "✅ Slave успешно переведён в режим master."
    else
        log "⚠️ Не удалось продвинуть в master (возможно, уже является master)."
    fi
}
  
function switch_to_slave_as_master() {
    log "Начало процесса переключения slave -> master..."
    log "Используем MASTER_IP = $MASTER_IP из констант."
  
    # Проверяем доступность БД с новым IP
    check_db_connection_after_change "$MASTER_IP"
  
    # Продвигаем в master (работает только если standby.signal существует)
    promote_slave_to_master
  
    # Обновляем конфиги приложений
    update_cddisk_configs "$MASTER_IP"
    update_documentserver_config "$MASTER_IP"
  
    # Перезапуск сервисов
    restart_supervisor_services
    restart_documentserver_services
  
    log "✅ Сервер переведён в режим master."
}
  
function switch_to_master_as_slave() {
    log "Начало процесса переключения master -> slave..."
    log "Используем MASTER_IP = $MASTER_IP для создания реплики."
    read -p "Вы уверены, что хотите сделать этот сервер slave? Это очистит данные. (y/n): " confirm
    [[ "$confirm" != "y" ]] && log "Операция отменена пользователем." && exit 1
  
    # Убираем запрос пароля — используем REPLICATION_PASS из констант
    if [[ -z "$REPLICATION_PASS" ]]; then
        log "❌ Пароль не может быть пустым."
        exit 1
    fi
  
    log "Остановка PostgreSQL..."
    systemctl stop postgresql
  
    log "Очистка каталога данных PostgreSQL..."
    rm -rf ${PG_DATA}/*
    mkdir -p ${PG_DATA}
  
    log "Создание новой реплики с мастера: $MASTER_IP..."
  
    # Передаем пароль через PGPASSWORD вместо stdin
    sudo -u postgres env PGPASSWORD="$REPLICATION_PASS" pg_basebackup \
        -h "$MASTER_IP" \
        -D ${PG_DATA} \
        -U "$REPLICATION_USER" \
        -P -R
  
    if [ $? -ne 0 ]; then
        log "❌ Ошибка при создании реплики. Проверьте доступность мастера и учётные данные."
        exit 1
    fi
  
    log "Запуск PostgreSQL..."
    systemctl start postgresql
  
    # === Меняем IP на MASTER_IP ===
    check_db_connection_after_change "$MASTER_IP"
    update_cddisk_configs "$MASTER_IP"
    update_documentserver_config "$MASTER_IP"
  
    restart_supervisor_services
    restart_documentserver_services
  
    log "✅ Сервер успешно переведён в режим slave."
}
  
function show_current_configurations() {
    echo -e "
=== Текущие конфигурации с подключением к БД ==="
    echo "Файлы appsettings.json (только актуальные):"
  
    for file in "${cs_configs[@]}"; do
        if [[ ! -f "$file" ]]; then
            echo "  $file — не найден"
            continue
        fi
  
        echo "  === $file ==="
  
        local user_actions=$(grep -A2 '"R7StorageServerUserActions"' "$file" | tr -d '
' | sed 's/[",]//g; s/:[ ]*/:/g')
        if [[ -n "$user_actions" ]]; then
            echo "  R7StorageServerUserActions: ${user_actions#*R7StorageServerUserActions: }"
        else
            echo "  R7StorageServerUserActions: не найден"
        fi
  
        local storage_server=$(grep -A2 '"R7StorageServer"' "$file" | tr -d '
' | sed 's/[",]//g; s/:[ ]*/:/g')
        if [[ -n "$storage_server" ]]; then
            echo "  R7StorageServer: ${storage_server#*R7StorageServer: }"
        else
            echo "  R7StorageServer: не найден"
        fi
  
        echo ""
    done
  
    local doc_config="/etc/r7-office/documentserver/local.json"
    echo "Файл local.json (documentserver):"
    if [[ -f "$doc_config" ]]; then
        grep -E '"(dbHost|dbName|dbUser|dbPass|dbPort)"' "$doc_config" | sed 's/^/  /'
    else
        echo "  Файл не найден"
    fi
 
}
 
 
function show_current_configurations_postfix() {
    echo -e "
=== Текущие конфигурации POSTFIX, подключение к БД ==="
 
    for file in "${postfix_configs[@]}"; do
        if [[ ! -f "$file" ]]; then
            echo "  $file — не найден"
            continue
        fi
  
        echo "  === $file ==="
  
        if [[ -f "$file" ]]; then
            grep -E "(hosts|user|password|dbname)" "$file"
        else
            echo "Файл $file не найден"
        fi
  
        echo ""
    done
}
 
function show_current_configurations_dovecot() {
    echo -e "
=== Текущие конфигурации Dovecot, подключение к БД ==="
 
    for file in "${dovecot_configs[@]}"; do
        if [[ ! -f "$file" ]]; then
            echo "  $file — не найден"
            continue
        fi
  
        echo "  === $file ==="
  
        if [[ -f "$file" ]]; then
            grep -E "(connect = )" "$file"
        else
            echo "Файл $file не найден"
        fi
  
        echo ""
    done
}
  
function get_postgres_data_dir() {
    local dir="${PG_DATA}"
  
    if [[ -d "$dir" ]]; then
        echo "$dir"
        return 0
    else
        log "❌ Каталог данных PostgreSQL не найден."
        return 1
    fi
}
  
function get_postgres_role() {
    local pg_data="${PG_DATA}"
  
    # Попробуем через SQL, если PostgreSQL запущен
    if systemctl is-active --quiet postgresql > /dev/null 2>&1; then
        local role_sql=$(sudo -u postgres psql -tAc "SELECT pg_is_in_recovery();")
        if [[ $? -eq 0 ]]; then
            if [[ "$(echo "$role_sql" | tr '[:upper:]' '[:lower:]')" == "f" ]]; then
                echo "master"
                return 0
            else
                echo "slave"
                return 0
            fi
        else
            log "⚠️ Не удалось выполнить запрос к PostgreSQL."
        fi
    fi
  
    # Если БД не запущена — проверяем сигналы
    if [[ -f "$pg_data/standby.signal" ]]; then
        echo "slave"
        return 0
    elif [[ -f "$pg_data/postmaster.pid" ]]; then
        echo "master"
        return 0
    else
        log "❌ Не удалось определить роль PostgreSQL."
        echo "unknown"
        return 1
    fi
}
  
function is_master() {
    [[ "$(get_postgres_role)" == "master" ]]
}
  
function is_slave() {
    [[ "$(get_postgres_role)" == "slave" ]]
}
  
function show_status_replication() {
    local local_ip=$(get_local_ip)
    echo -e "
=== Текущий статус сервера ==="
    echo "IP сервера: $local_ip"
    echo "MASTER_IP (константа): $MASTER_IP"
    echo "SLAVE_IP (константа): $SLAVE_IP"
    local role=$(get_postgres_role)
    echo "Роль: ${role^}"
    if [[ "$role" == "master" ]]; then
        echo "Проверка состояния репликации:"
        env PGHOME=/tmp sudo -u postgres PGOPTIONS="-c search_path=pg_catalog" psql -c "SELECT * FROM pg_stat_replication;"
    elif [[ "$role" == "slave" ]]; then
        echo "Проверка состояния восстановления:"
        env PGHOME=/tmp sudo -u postgres PGOPTIONS="-c search_path=pg_catalog" psql -c "SELECT * FROM pg_stat_wal_receiver;"
    else
        echo "Роль: Не определена"
    fi
    echo -e "==============================
"
}
  
function main_menu() {
    clear
    local local_ip=$(get_local_ip)
    local local_hostname=$(get_local_hostname)
    echo "=== Добро пожаловать в систему управления PostgreSQL ==="
    echo "Лог-файл: $LOG_FILE"
    echo ""
    while true; do
        log "Скрипт работает на сервере ${local_hostname} с ip - ${local_ip}"
        echo "=== Меню управления PostgreSQL ==="
        echo "1. Установка параметров master"
        echo "2. Установка параметров slave (производиться с удалением базы на текущей машине)"
        echo "3. Показать текущие конфиги"
        echo "4. Проверка подключения с новыми параметрами"
        echo "5. Восстановить конфигурации из .bak"
        echo "6. Перезапустить сервисы приложений"
        echo "7. Показать текущий статус сервера"
        echo "8. Остановка сервисов корпоративного сервера (холодный резерв)"
        echo "9. Выход"
        read -p "Выберите действие (1-9): " choice
        case $choice in
            1)
                switch_to_slave_as_master
                update_postfix_configs $MASTER_IP
                update_dovecot_configs $MASTER_IP
                restart_mail_services
                show_status_replication
                read -p "Нажмите Enter для продолжения..."
                ;;
            2)
                switch_to_master_as_slave
                update_postfix_configs $MASTER_IP
                update_dovecot_configs $MASTER_IP
                restart_mail_services
                show_status_replication
                read -p "Нажмите Enter для продолжения..."
                ;;
            3)
                show_current_configurations
                show_current_configurations_postfix
                show_current_configurations_dovecot
                read -p "Нажмите Enter для продолжения..."
                ;;
            4)
                check_db_connection_after_change "$MASTER_IP"
                read -p "Нажмите Enter для продолжения..."
                ;;
            5)
                restore_all_configs
                read -p "Нажмите Enter для продолжения..."
                ;;
            6)
                log "Перезапуск всех сервисов..."
                supervisorctl restart all
                restart_documentserver_services
                restart_mail_services
                read -p "Нажмите Enter для продолжения..."
                ;;
            7)
                show_status_replication
                read -p "Нажмите Enter для продолжения..."
                ;;
            8)
                log "Остановка всех сервисов корпоративного сервера..."
                log "Состояние сервисов почтового сервера не меняется!!!"
                supervisorctl stop all
                stop_documentserver_services
                read -p "Нажмите Enter для продолжения..."
                ;;
            9)
                log "Выход..."
                exit 0
                ;;
            *)
                echo "Неверный выбор."
                sleep 2
                ;;
        esac
        clear
    done
}
  
# === MAIN ===
check_superuser
main_menu

Описание пунктов:

1. Установка параметров master — устанавливает параметры master для сервера используемого основным (меняет конфигурации приложений на использование БД master) и создает в том же каталоге резервную копию изменяемых файлов;
2. Установка параметров slave — устанавливает параметры slave для сервера используемым запасным (меняет конфигурации приложений на использование БД master и включение репликации на этом сервере) и создает в том же каталоге резервную копию изменяемых файлов;
3. Показать текущие конфиги — показывает текущие параметры приложений;
4. Проверка подключения с новыми параметрами — проверяет доступ для приложений с заданными константами по подключению к master;
5. Восстановить конфигурации из .bak — при ранее измененных конфигураций приложений может восстановить резервные копии файлов из формата .bak;
6. Перезапустить сервисы приложений — перезапускает сервисы приложений;
7. Показать текущий статус сервера — указывает состояние БД на текущем сервере;
8. Остановка сервисов корпоративного сервера (холодный резерв) — останавливает сервисы корпоративного сервера для режима «холодный резерв».

7.2. Настройка второго сервера

Укажите в скрипте текущее представление серверов (master и slave) в константах скрипта (и проверьте другие параметры с корректными данными) и запустите скрипт на втором сервере (slave) пункт меню «Установка параметров slave».

7.3. Проверка работы серверов

Для проверки потребуется переключать в DNS А запись ведущая на первый сервер. Перейдите по адресу https://admin.test2.s7-office.site, убедитесь что используется корректная адресация на первый адрес Корпоративного сервера 2024 (используйте команду ping) и используйте предустановленный логин\пароль superadmin. Создайте пользователя и проверьте редактирование файлов — корректное сохранение файлов и повторное открытие файлов.

Далее переключите А запись на второй сервер и так же проверьте его работу.

В DNS используйте установленную текущую ноду как master. Запустите скрипт управления БД и проверьте текущие статусы серверов.

Настоятельно рекомендуется отработать поэтапное переключение из slave в master на втором сервере в случае ошибок на первом сервере.

8. Резервное копирование и восстановление

Для проведения резервирования и восстановления необходимо использовать скрипты из статьи Резервное копирование/восстановление Корпоративный сервер 2024.

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

# Создание архива с бэкапом
tar cf "$backup_dir/cddisk-${backup_date}.tar.bz2" --selinux --use-compress-prog="lbzip2 -k -n$num_cores" /opt/r7-office $supervisor_dir /etc/r7-office /var/r7-office /var/www/r7-office "$backup_dir/cddisk_db.tar.gz /mail"

При восстановлении на мастер ноде рекомендуется остановить службу:

systemctl stop glusterfs

И после завершения восстановления включить:

systemctl start glusterfs

При восстановлении резервной ноды нет необходимости выполнения восстановления, так как скрипт позволяет назначить вторую ноду репликой первой для БД и автоматическую синхронизацию каталогов производится средствами glusterfs.

Была ли полезна статья?
Позвольте нам стать лучше
Дополнительные материалы