Содержание
- Актуальная схема
- Описание
- Условия эксплуатации
- Технические требования
- 1. Установка и настройка сервиса хранения GlusterFS
- 2. Подготовка к установке почтового сервера (*опционально)
- 3. Установка и настройка БД
- 4. Установка Корпоративного сервера
- 5. Выполните дополнительную настройку Nginx и Сервер документов
- 6. Настройка подключения почтового сервера (*опционально)
- 7. Управление переключением БД при неработоспособности первого сервера
- 8. Резервное копирование и восстановление
Актуальная схема

Описание
Архитектура рассчитана на использование одного сервера как основной и второго сервера как запасной с постоянной репликацией. В случае отказа работы основного возможность ручного переключения на запасного с помощью скрипта и изменении ip адреса указывающего на основную ноду.
В архитектуре используются следующие компоненты и входят в состав стандартной установки Корпоративного сервера 2024:
- PostgreSQL 12.22;
- RabbitMQ-Server 3.13.7;
- Redis Server 7.2.7;
- GlusterFS 11.1.
Проверка осуществлялась на версии 2.0.2024.14752 Корпоративного сервера и сервера документов 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 для корректной работы сервисов.
В состав дистрибутива Корпоративный сервер 2024 не включён модуль Р7 Графика. Для установки модуля Р7 Графика необходимо воспользоваться инструкциями, опубликованными в разделе: Р7 Графика > Установка ↗.
1. Установка и настройка сервиса хранения GlusterFS
Описание
Файловое хранилище:
- Для почтового сервера будет использоваться каталог
/mail - Корпоративный Сервер
/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 версии 12 в режиме master-slave.
1.1. Установка и настройка glusterfs
Добавьте запись в /etc/hosts:
192.168.27.186 gluster1 192.168.26.253 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.2. На всех нодах установить, запустить и добавить в автозагрузку
dnf install -y glusterfs-server systemctl start glusterd.service systemctl enable glusterd.service
Также рекомендуется, чтобы gluster корректно работал и соединялся друг с другом и не падал, то нужно посмотреть порты:
cat /etc/glusterfs/glusterd.vol
Пример вывода:

Нам нужно добавить исключения на firewall, чтобы соединения проходили:
sudo firewall-cmd --permanent --add-port=49152-49251/tcp sudo firewall-cmd --permanent --add-port=24007-24008/tcp sudo firewall-cmd --reload
Теперь все должно корректно проходить и gluster будет устанавливать соединения.
Не имеет значения, какой из узлов вы будете использовать, но в следующем примере команда запускается на 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.
На этом этапе два ваших сервера взаимодействуют и готовы к созданию томов хранения друг с другом.
Создание томов выполняется на сервере №1.
Для создания тома вы будете использовать команду 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
sudo gluster volume create cddisk-javamaker replica 2 gluster{1,2}:/cddisk-javamaker 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 sudo gluster volume start cddisk-javamaker
Вы получите следующий вывод, если том запущен корректно:
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 volume start: cddisk-javamaker: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 mkdir -p /opt/r7-office/java-maker/uploaded-files *** 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 mount.glusterfs localhost:/cddisk-javamaker /opt/r7-office/java-maker/uploaded-files
Добавьте в автозагрузку (для почтового сервера):
{
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'
echo 'localhost:/cddisk-javamaker /opt/r7-office/java-maker/uploaded-files 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. Установка и настройка БД
Установите следующие пакеты на оба сервера:
dnf install postgresql-server-12.* postgresql-12.*
3.1. БД
Следующие инструкции выполняются на всех серверах.
Создайте первичный кластер базы данных:
sudo postgresql-setup --initdb sudo systemctl enable --now postgresql
Отредактируйте /var/lib/pgsql/data/postgresql.conf:
listen_addresses = 'localhost,ip_srv1,ip_srv2' # Слушать на адресах port = 5432 # Задать порт БД wal_level = replica # Включить репликацию archive_mode = on # Включить архивирование WAL archive_command = 'cp %p /var/lib/pgsql/archive/%f' # Команда для архивирования WAL max_wal_senders = 5 # Максимальное количество одновременных подключений репликации wal_keep_segments = 50 # Количество WAL-сегментов для хранения hot_standby = on # Разрешить подключения в режиме hot standby
Где, ip_srv1, ip_srv2 — адреса внутренней сети первого и второго почтового сервера.
Следующие инструкции выполняются на всех серверах.
В файле postgresql.conf разрешите логическую репликацию, добавляем строку для прослушивания локального интерфейса:
/var/lib/pgsql/data/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 выполните следующие команды, подтверждая каждую нажатием Enter:
Вместо паролей password и replication_password, cddisk и ds задайте свои пароли.
В поле insert into вместо YOUR_DOMAIN укажите реальный адрес вашего домена.
Не рекомендуется использовать другие имена для баз данных 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; ALTER DATABASE cddisk OWNER TO cddisk; ALTER DATABASE ds OWNER TO ds; ALTER DATABASE pagesdb OWNER 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.
Создайте директорию для архива:
sudo mkdir /var/lib/pgsql/archive sudo chown -R postgres: /var/lib/pgsql/archive sudo chmod -R 750 /var/lib/pgsql/archive
Следующие инструкции выполняются на сервере №1.
Перезагрузите службу PostgreSQL для применения изменений:
systemctl restart postgresql
3.2. Настройка репликации БД
Следующие инструкции выполняются на сервере №2.
Удалите существующую директорию данных:
rm -rf /var/lib/pgsql/data
Создайте базовую резервную копию с мастера:
su — postgres -c "pg_basebackup --host=ip_srv1 --username=replication_user --pgdata=/var/lib/pgsql/data --wal-method=stream --write-recovery-conf"
Где, ip_srv1 — адрес внутренней сети первого сервера, с master БД.
Перезапустите сервис и проверьте его статус:
systemctl restart postgresql.service systemctl status postgresql.service
4. Установка Корпоративного сервера
Выполнение установки производится по инструкции: Установка Корпоративного сервера 2024 на РЕД ОС ↗.
4.1. Предварительная подготовка
Скачайте дистрибутив и рекомендуем, для корректной установки, архив разместите в директории, отличной от /root, например в /mnt или /tmp.
Настройку двухнодовой архитектуры рекомендуется начать с установки на втором сервере, чтобы избежать последующих ошибок с серверами.
Перейдите в каталог:
cd /mnt
Распакуйте архив:
unzip RedOS_*.zip
Для корректной работы Корпоративного сервера обязательно требуется настройка HTTPS. Перед установкой скопируйте crt и key файлы в папку sslcert.
Предоставьте права на скрипт установки:
chmod +x online_installer.sh
Создайте DNS запись для Корпоративного сервера, например:

4.2. Запуск установки второго сервера
Выполните команду:
sudo bash ./online_installer.sh
Выберите Нет, иначе произойдет удаление postgresql:

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

Выберите Да:

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

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

Выберите Да:

Выберите postgresql:

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

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

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

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

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

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

Выберите Да:

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

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

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

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

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

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

Если требуется установка SpamAssassin:
- Выберите 1.
Если не требуется установка SpamAssassin:
- Выберите 2:

Если требуется установка:
- Выберите Да.
Если не требуется установка:
- Выберите Нет:

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

Перезапустите сервер.
4.3. Установка первого сервера
Перед установкой корпоративного сервера №1 необходимо пересоздать базу данных, чтобы избежать дублирования информации.
На сервере №1 (Мастере):
DROP DATABASE IF EXISTS cddisk;
Далее мы создадим базу данных заново на мастере из пункта 3.1.
- На сервере №1.
Скачайте дистрибутив и рекомендуем, для корректной установки, архив разместите в директории, отличной от /root, например в /mnt или /tmp:
Перейдите в каталог :
cd /mnt
Распакуйте архив:
unzip RedOS_*.zip
Для корректной работы Корпоративного сервера обязательно требуется настройка HTTPS. Перед установкой скопируйте crt и key файлы в папку sslcert.
Предоставьте права на скрипт установки:
chmod +x online_installer.sh
4.4. Запуск установки
Выполните команду:
sudo bash ./online_installer.sh
Выберите Нет:

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

Выберите Да:

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

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

Выберите Да:

Выберите postgresql:

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

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

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

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

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

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

Выберите Да:

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

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

Дальнейшие действия нужно выполнять при установки корпоративного сервера с почтовым сервером, иначе перезагрузите сервер.
Выберите 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 "s/set \$secretString [^;]*/set \$secretString НОВОЕ_ЗНАЧЕНИЕ/" $(grep -rl "set \$secretString" /etc/r7-office/documentserver/)
Где замените НОВОЕ_ЗНАЧЕНИЕ на необходимый параметр c вывода предыдущей команды.
Пример: kCj7RMAAYCNtOh6KiGin.
systemctl restart nginx ds-converter.service ds-docservice.service ds-metrics.service
6. Настройка подключения почтового сервера (*опционально)
Перейдите в https://admin.{ВАШ_ДОМЕН} Организации — Выберите организацию, в которую добавлены пользователи — Почтовые серверы:

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

В таком случае управлять почтовыми ящиками можно через консоль сервера.
Интеграция с почтовым сервером не поддерживается из-за наличия конфликтов двух одинаковых записей доменов.
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"— расположение файла логирования скрипта.
Константы для 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"— укажите порт БД.
Константы для java-maker (external.properties):
JAVA_DB_NAME="pagesdb"— не рекомендуется изменять;JAVA_DB_PORT="5432"— укажите порт БД.
#!/bin/bash
# === Константы для подключения к БД ===
MASTER_IP="192.168.27.186"
SLAVE_IP="192.168.26.253"
REPLICATION_USER="replication_user"
REPLICATION_PASS="replication_password"
LOG_FILE="/var/log/switch_postgres.log"
# === Константы для 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"
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 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 "Начало восстановления конфигураций из резервных копий..."
local files=(
"/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"
)
for file in "${files[@]}"; 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 "Строка 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
return 0
}
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
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;"
local files=(
"/opt/r7-office/Api/appsettings.json"
"/opt/r7-office/Sso.Api/appsettings.json"
"/opt/r7-office/Processing/appsettings.json"
)
for file in "${files[@]}"; 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 promote_slave_to_master() {
log "Продвижение slave в master..."
local pg_data="/var/lib/pgsql/data"
if [[ ! -f "$pg_data/standby.signal" && ! -f "$pg_data/recovery.signal" ]]; then
log "⚠️ Сервер не находится в режиме standby. Пропущено продвижение в master."
return 0
fi
sudo -u postgres 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 /var/lib/pgsql/data/*
mkdir -p /var/lib/pgsql/data
log "Создание новой реплики с мастера: $MASTER_IP..."
# Передаем пароль через PGPASSWORD вместо stdin
sudo -u postgres env PGPASSWORD="$REPLICATION_PASS" pg_basebackup \
-h "$MASTER_IP" \
-D /var/lib/pgsql/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 (только актуальные):"
local files=(
"/opt/r7-office/Api/appsettings.json"
"/opt/r7-office/Sso.Api/appsettings.json"
"/opt/r7-office/Processing/appsettings.json"
)
for file in "${files[@]}"; 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 get_postgres_data_dir() {
local dir="/var/lib/pgsql/data"
if [[ -d "$dir" ]]; then
echo "$dir"
return 0
else
log "❌ Каталог данных PostgreSQL не найден."
return 1
fi
}
function get_postgres_role() {
local pg_data="/var/lib/pgsql/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
echo "=== Добро пожаловать в систему управления PostgreSQL ==="
echo "Лог-файл: $LOG_FILE"
echo ""
while true; do
echo "=== Меню управления PostgreSQL ==="
echo "1. Установка параметров master"
echo "2. Установка параметров slave"
echo "3. Показать текущие конфиги"
echo "4. Проверка подключения с новыми параметрами"
echo "5. Восстановить конфигурации из .bak"
echo "6. Перезапустить сервисы приложений"
echo "7. Показать текущий статус сервера"
echo "8. Выход"
read -p "Выберите действие (1-8): " choice
case $choice in
1)
switch_to_slave_as_master
show_status_replication
read -p "Нажмите Enter для продолжения..."
;;
2)
switch_to_master_as_slave
show_status_replication
read -p "Нажмите Enter для продолжения..."
;;
3)
show_current_configurations
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
read -p "Нажмите Enter для продолжения..."
;;
7)
show_status_replication
read -p "Нажмите Enter для продолжения..."
;;
8)
log "Выход..."
exit 0
;;
*)
echo "Неверный выбор."
sleep 2
;;
esac
clear
done
}
# === MAIN ===
check_superuser
main_menuОписание пунктов:
- Установка параметров master — устанавливает параметры master для сервера используемого основным (меняет конфигурации приложений на использование БД master) и создает в том же каталоге резервную копию изменяемых файлов;
- Установка параметров slave — устанавливает параметры slave для сервера используемым запасным (меняет конфигурации приложений на использование БД master и включение репликации на этом сервере) и создает в том же каталоге резервную копию изменяемых файлов;
- Показать текущие конфиги — показывает текущие параметры приложений;
- Проверка подключения с новыми параметрами — проверяет доступ для приложений с заданными константами по подключению к master;
- Восстановить конфигурации из
.bak— при ранее измененных конфигураций приложений может восстановить резервные копии файлов из формата.bak - Перезапустить сервисы приложений — перезапускает сервисы приложений;
- Показать текущий статус сервера — указывает состояние БД на текущем сервере.
7.2. Проверка работы серверов
Для проверки потребуется переключать в DNS А-запись ведущая на первый сервер.
Перейдите по адресу https://admin.{ВАШ_ДОМЕН}.
Убедитесь что используется корректная адресация на первый адрес Корпоративного сервера (используйте команду 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.










