Продукты Р7
Корпоративный сервер 2024
Корпоративный сервер 2024
Сервер документов
Сервер документов
Редакторы
Редакторы
Корпоративный сервер 2019
Корпоративный сервер 2019
Графика
Графика
Команда
Команда
Мобильные редакторы
Мобильные редакторы
Облачный офис
Облачный офис
Почта
Почта
Органайзер
Органайзер
Дополнительно
Часто задаваемые вопросы
Разработчикам
Интеграции
Новые возможности

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

Обновлено: 24.12.25
Обратите внимание

Для установки потребуется wildcard сертификат, содержащий полную цепочку (fullchain)

Например:

—-BEGIN CERTIFICATE——
(Your Primary SSL certificate: your_domain_name.crt)
——END CERTIFICATE——
——BEGIN CERTIFICATE——
(Your Intermediate certificate: DigiCertCA.crt)
——END CERTIFICATE——
——BEGIN CERTIFICATE——
(Your Root certificate: TrustedRoot.crt)
——END CERTIFICATE——

Где:

Основной Certificate — your_domain_name.crt
Промежуточный Certificate — DigiCertCA.crt
Корневой Certificate — TrustedRoot.crt

Схема

Описание

Архитектура рассчитана на использование одного сервера как основной и второго сервера как запасной с постоянной репликацией. В случае отказа работы основного возможность ручного переключения на запасного с помощью скрипта и изменении 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 для корректной работы сервисов.
[wbcr_text_snippet]: Text snippets error (not passed the snippet ID)

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

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

А также 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 сервера, путь к конфигурационному файлу может отличаться. Все действия описываются для 14 версии.

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

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 задайте свои пароли), подтверждая каждую нажатием 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;
CREATE DATABASE pagesdb OWNER cddisk;
GRANT ALL privileges ON DATABASE cddisk TO cddisk;
GRANT ALL privileges ON DATABASE ds TO ds;
GRANT ALL privileges ON DATABASE pagesdb TO cddisk;
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/11/main/archive
sudo chown -R postgres:postgres /var/lib/postgresql/11/main/archive
sudo chmod -R 750 /var/lib/postgresql/11/main/archive
Важно

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

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

systemctl restart postgresql*

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

Важно

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

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

#в зависимости от версии установленной postgresql сервера, путь к каталогу может отличаться
rm -rf /var/lib/postgresql/11/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*
systemctl status postgresql*

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

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

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

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

Уточнение

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

Нашу настройку двухнодовой архитектуры необходимо начать с установки на втором сервере, чтобы избежать последующих ошибок с серверами.

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

cd /mnt

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

unzip CDinstall_*.zip

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

cd CDDiskPack/CDinstall/
Важно

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

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

chmod +x online_installer.sh

Создайте 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. Запуск установки второго сервера

Важно

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

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

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

./online_installer.sh

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

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

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

./offline_installer.sh

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

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

Выберите Да:

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

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

Выберите Да:

Выберите PostgreSQL:

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

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

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

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

Укажите системного пользователя postgres:

Укажите пользователя cddisk:

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

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

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

Выберите Да:

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

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

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


Обратите внимание

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

Выберите PostgreSQL:

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

 

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

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

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

  • Выберите 1

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

  • Выберите 2

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

  • Выберите Да

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

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

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

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

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

Важно

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

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

DROP DATABASE IF EXISTS cddisk;

Далее мы создадим базу данных заново на мастере из пункта 3.1

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

Уточнение

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

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

cd /mnt

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

unzip CDinstall_*.zip
Важно

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

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

cd CDDiskPack/CDinstall/

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

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 как для второго сервера:

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

Выберите Да:

Выберите PostgreSQL:

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

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

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

Укажите имя БД cddisk:

Укажите пользователя postgres:

Укажите пользователя cddisk для БД cddisk:

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

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

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

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

Выберите Да:

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

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

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


Обратите внимание

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

Выберите PostgreSQL:

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

 

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

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

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

  • Выберите 1

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

  • Выберите 2

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

  • Выберите Да

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

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

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

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

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).

Измените в /etc/r7-office/documentserver/local.json строку:

"dbHost": "localhost"

Где потребуется заменить localhost на адрес текущего мастера (первого сервера), из примера 192.168.27.135.

Выполните команды по перезапуску 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 БД;
  • MASTER_MAIL_FQDN="mx1.stend12.s7-office.site" — укажите FQDN имя почтового сервера на 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.26.67"
MASTER_MAIL_FQDN="mx1.stend12.s7-office.site"
SLAVE_IP="192.168.26.79"
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 = <IP>:<PORT>  не найдена в $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 update_mail_connections() {
    local new_master="$1"
    log "Обновление параметров подключения к почтовому серверу БД ..."
    local db_port="${CDDISK_DB_PORT:-5432}"
    local result
    result=$(echo "$CDDISK_DB_PASS" | PGPASSWORD="$CDDISK_DB_PASS" psql -h "$new_master" -U "$CDDISK_DB_USER" -p "$CDDISK_DB_PORT" -d "$CDDISK_DB_NAME" -c \
    "UPDATE public.\"EmailServers\"
        SET \"Json\" = to_jsonb(
            JSONB_SET(
                \"Json\"::jsonb,   -- Приводим прямо к jsonb
                '{Ssh,Server}',  -- Правильно указываем путь
                '\"$MASTER_MAIL_FQDN\"'::jsonb
                )
        ),
        \"ImapServer\" = '$MASTER_MAIL_FQDN',
        \"SmtpServer\" = '$MASTER_MAIL_FQDN',
        \"PopServer\" = '$MASTER_MAIL_FQDN'
        WHERE \"CustomerId\" = (SELECT \"Id\" FROM public.\"Customers\" WHERE \"IsRoot\" = true);"
    )
    if [[ $result == "UPDATE 1" ]]; then
        log "✅ Подключение к почтовому серверу обновлено"
    else
        log "❌ Не удалось обновить подключение к почтовому серверу"
        return 1
    fi
}
  
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
                update_mail_connections $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.