🪞 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).
  • ⚙️ Всё остальное — скрипты переключения 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)
→src/main.cpp парсит режимы, подключает legacy, запускает run_daemon_loop при --daemon
→ modules/daemon.cpp health-check, автопереключение, установка/удаление systemd-юнитов, SSH на зеркало
→ modules/net.cpp SSH/SFTP (libssh), бинарные и текстовые заливки, прогресс
→ modules/deploy.cpp генерация sh-команд: tar, права, mysql, nginx reload
include/mad/*.hpp интерфейсы и конфигурация (структура Config)

На машине при установке демона автоматически появляются:

  • /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.

🧪 Быстрый тест

  1. Открой journalctl -u madbackuper -f на 198 и journalctl -u madbackuper-watchdog -f на 202.
  2. Останови локальный веб на 198 на минутку — увидишь автопереключение 202 → local.
  3. Верни веб — зеркало вернётся в 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 — упирались в права. Решение:

  1. Заливаем на Зеркало во /tmp по SFTP;
  2. Дальше перемещаем через 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
Совет: если в логах вижу «(no setup script)», значит нет файла /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 — это «поставил и забыл»: следит за сайтом, бэкапит, ловко тумблерит зеркало при проблемах. Как мы и хотели — минимум внимания, максимум спокойствия.