🪞 madWebMirrorMagick — «магическое зеркало» для сайта Linux
Внимание! Утилита немного недоделана, у нее проблема с переключалкой серверов. Потом исправлю, если руки дойдут.
Программа-демон, которая сама следит за сайтом, делает бэкапы и при падении основного вебсервера автоматически включает его на резервном. Экономит энергию мозга™ — настроил один раз и больше не паришься.
-  Health-check локального веба (через curlпо URL/порту).
- Автопереключение на зеркале: remote ⇄ local для nginx на хосте-зеркале.
- Ежедневные бэкапы сайта и БД по расписанию.
- Работает как systemd-сервис и ставит второй watchdog-сервис на зеркало автоматом.
🧰 Предварительные требования (чек-лист)
- 🐧 На обоих серверах (в на шем случае это «198» — основной и «202» — зеркало) установлена операционная система Linux с systemd, веб-сервер nginx или apache2, mysql-сервер.
- 🔑 Есть рабочий доступ по SSH с основного на зеркало (логин и пароль известны).
- 📦 На основном сервере (где запускается наша программа) должны быть установлены пакеты: sudo apt-get install libssh-dev curl tar gzip pv
- 🗝️ Под рукой должны быть учётные данные: - Пароль от SSH-пользователя на зеркале (в моем случае это madmentat).
- Пароль rootна зеркале (для установки systemd-юнитов).
- Пароль sudo на зеркале (если отличается от SSH/Root).
- Пользователь / пароль для базы данных (MySQL/MariaDB).
 
- Пароль от SSH-пользователя на зеркале (в моем случае это 
- ⚙️ Всё остальное — скрипты переключения nginx, systemd-юниты и watchdog — программа устанавливает автоматически, руками ничего создавать не нужно.
📁 Структура исходников проекта
Репозиторий madWebMirrorMagick организован просто: всё, что исполняется, находится в src/, заголовки — в include/mad/, а наследованный «легаси»-код подключаем единым файлом из legacy/. Сборкой управляет Makefile, итоговый бинарь — madbackuper.
madWebMirrorMagick/
├─ Makefile
├─ src/
│  ├─ main.cpp                 # входная точка + адаптер к legacy
│  └─ modules/
│     ├─ core.cpp              # конфиг, валидация, утилиты (run_local и пр.)
│     ├─ net.cpp               # SSH/SFTP обёртки (libssh), прогресс загрузок
│     ├─ deploy.cpp            # построение команд деплоя (nginx/apache, БД, права)
│     └─ daemon.cpp            # демонический цикл, systemd install/uninstall,
│                              # удалённый watchdog-скрипт и его unit на зеркале
├─ include/
│  └─ mad/
│     ├─ core.hpp              # структура Config, константы путей, прототипы
│     ├─ net.hpp               # объявления SSH/SFTP функций
│     ├─ deploy.hpp            # интерфейсы builder'ов команд деплоя
│     └─ daemon.hpp            # API демона (run_daemon_loop, daemon_install,…)
├─ legacy/
│  └─ madbackuper.cpp          # старый монолит, подключается из main.cpp
└─ (build) madbackuper         # собранный бинарь (после make)
run_daemon_loop при --daemonConfig)На машине при установке демона автоматически появляются:
-  /usr/local/bin/madbackuper— бинарь (основной демон на 198).
-  /etc/systemd/system/madbackuper.service— локальный юнит (198).
-  /usr/local/bin/mad_watchdog_198.sh— скрипт на зеркале (202), пингует 198 и переключает nginx.
-  /etc/systemd/system/madbackuper-watchdog.service— юнит на зеркале (202).
-  /etc/madbackuper.conf— конфигурация (пароли в статье заменяй на XXX).
🔩 Сборка и установка
# собрать
make clean && make
# установить и запустить как демон
sudo ./madWebMirrorMagick --daemon-install
# проверить статусы
systemctl status madbackuper            # основной демон на 198
# после инсталла он сам создаст watchdog на 202:
#  madbackuper-watchdog.service
 В качестве исполняемого файла используется бинарь, устанавливаемый в /usr/local/bin/madbackuper. Название системного юнита оставлено историческим: madbackuper.service.
🧪 Быстрый тест
- Открой journalctl -u madbackuper -fна 198 иjournalctl -u madbackuper-watchdog -fна 202.
- Останови локальный веб на 198 на минутку — увидишь автопереключение 202 → local.
- Верни веб — зеркало вернётся в remote.
⚙️ Конфигурация (/etc/madbackuper.conf)
 Все параметры — простые key=value. CLI-ключи могут их переопределять.
target_server=nginx                      # nginx | apache2 (логика переключения сейчас для nginx)
remote_host=192.168.88.202               # Зеркало (куда ставим watchdog)
ssh_port=22
remote_user=madmentat
remote_pass=XXX                          # SSH-пароль (замените!)
remote_root_pass=XXX                     # Не обязателен, зарезервировано под будущие сценарии
remote_sudo_pass=XXX                     # Пароль для sudo на Зеркале; если пусто — пробуем без пароля
local_site_dir=/webserver/madmentat.ru
remote_site_dir=/webserver/madmentat.ru
remote_backup_base=/webserver/.backup
server_name=madmentat.ru                 # влияет на имя setup-скрипта на Зеркале: /root/setup_<server_name>_nginx.sh
php_version=8.3
php_fpm_sock=                            # можно оставить пустым, если используем php_version
db_user=madmentat
db_pass=XXX
db_name=mad
proxy_target=192.168.88.198              # Основной хост (198), куда указывает REMOTE в конфигурации nginx на Зеркале
local_http_port=8081                     # Локальный порт приложения на 198, когда зеркало в режиме LOCAL
local_https_port=0                       # 0 — не используем https локально (иначе укажи порт и ssl_cert/ssl_key)
health_url=http://127.0.0.1:80/          # URL для health-check на 198 (можно оставить 127.0.0.1:80)
health_host_header=                      # Если нужен Host для проверки (виртуальный хост) — впиши домен
health_interval_sec=60                   # Период проверок
switch_to_local=true                     # Разрешено ли переключать зеркало в LOCAL при падении 198
ssl_cert=
ssl_key=
schedule_hhmm=04:00                      # Будущее: время суточного бэкапа (legacy-часть уже унаследована)
🧭 Как это интерпретируется
- Health-check идёт по health_url. Если он пуст, берётсяhttp://127.0.0.1:<local_http_port>/.
- Если health_host_headerзадан — добавляем-H "Host: ..."кcurl.
- На Зеркале (202) устанавливается watchdog-скрипт, который пингует http://<proxy_target>:<HTTP_PORT>/и вызывает/root/setup_<server_name>_nginx.shс аргументомremoteилиlocal.
- Важный нюанс портов: health_urlпроверяет «внешний» фронт 198 (часто это 80). Для локального режима nginx на 202 проксирует наlocal_http_port(например, 8081) — это уже внутренняя сцепка.
🖥️ Управление (CLI)
- --daemon— запуск в цикле демона (обычно делает systemd).
- --daemon-install— установить локальный сервис и удалённый watchdog.
- --daemon-uninstall— удалить локальный сервис и удалённый watchdog.
- Параметры конфигурации можно переопределить ключами вида --health-url=...,--proxy-target=...и т. п.
🧪 Примеры «граблей» и как мы их обошли
Тут под "мы" - подразумеваюсь я и нейросетка.
SFTP: «Permission denied» при укладке на /usr/local/bin
Сначала пытались заливать файл сразу в /usr/local/bin через SFTP — упирались в права. Решение:
- Заливаем на Зеркало во /tmpпо SFTP;
- Дальше перемещаем через sudo installс передачей пароля поstdin:
# было (ошибка прав)
sftp: put watchdog.sh /usr/local/bin/mad_watchdog_198.sh
# стало (работает)
put watchdog.sh /tmp/mad_watchdog_198.sh
printf %s "$SUDOPASS" | sudo -S -p '' install -m 0755 /tmp/mad_watchdog_198.sh /usr/local/bin/mad_watchdog_198.sh
/bin/sh и «local: not in a function»
Watchdog-скрипт писали под bash, а unit стартовал через /bin/sh. Исправили шебанг и ExecStart:
#!/bin/sh
local var=1   # <-- bash-синтаксис, sh ругается
#!/usr/bin/env bash
set -Eeuo pipefail
# ...
# в unit:
# ExecStart=/usr/bin/env bash /usr/local/bin/mad_watchdog_198.sh
sudo требует пароль в non-interactive
Ловили «a password is required». Решение — всегда формируем команду вида:
printf %s "$SUDOPASS" | sudo -S -p '' <команда>
В madWebMirrorMagick это уже встроено: берём remote_sudo_pass (или remote_pass, если первое пусто) и прокладываем его в команду.
📁 Что раскладывается на Зеркале автоматически
- /usr/local/bin/mad_watchdog_198.sh— bash-скрипт, пингует 198 и дёргает setup-скрипт.
- /etc/systemd/system/madbackuper-watchdog.service— unit, запускающий скрипт.
Их не нужно править руками — всё обновляется/удаляется командами --daemon-install и --daemon-uninstall.
🧭 Троттлинг логов и диагностика
# основной демон (198)
sudo journalctl -u madbackuper -f
# watchdog на зеркале (202)
sudo journalctl -u madbackuper-watchdog -f
# ручная проверка health-check
curl -v -m 5 http://127.0.0.1:80/      # подставь свой health_url
/root/setup_<server_name>_nginx.sh или он без +x. Добавь и проверь вручную: ssh 202 "ls -l /root/setup_<server_name>_nginx.sh && /root/setup_... remote"🔐 Безопасность
- Пароли в примерах заменены на XXX. В бою — используйте секрет-хранилище или хотя бы права 600на конфиг.
- Ограничьте SSH доступ к зеркалу по сети/ключам.
- Следите, чтобы watchdog не открывал наружу ничего лишнего — он локальный systemd-сервис.
🧩 Сырцы
Архив можно скачать ЗДЕСЬ.
А это ссылочка на Гитхаб.
🎉 Итог
madWebMirrorMagick — это «поставил и забыл»: следит за сайтом, бэкапит, ловко тумблерит зеркало при проблемах. Как мы и хотели — минимум внимания, максимум спокойствия.
