Created
December 5, 2025 16:51
-
-
Save MrPandir/8ad01c014bdf5ef729c459887b21311f to your computer and use it in GitHub Desktop.
Automatic rollback on server restart
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # Конфигурация | |
| BACKUP_ROOT="/backup/rsnapshot" | |
| STATE_FILE="/var/lib/maintenance.state" | |
| LOG_FILE="/var/log/maintenance.log" | |
| TASK_FILE="/etc/cron.d/emergency_restore" | |
| # Список директорий и файлов для исключения при восстановлении | |
| EXCLUDE_LIST=( | |
| ".cache" | |
| "/lib/docker" | |
| "/etc/rsnapshot.conf" | |
| "/lib/systemd" # /var | |
| "$STATE_FILE" | |
| "$TASK_FILE" | |
| "$LOG_FILE" | |
| "/log/maintenance.log" | |
| ) | |
| # Проверка необходимых инструментов | |
| REQUIRED_TOOLS=("rsync" "rsnapshot" "find" "stat" "date" "read" "cat" "rm" "echo" "tee") | |
| for tool in "${REQUIRED_TOOLS[@]}"; do | |
| if ! command -v "$tool" &> /dev/null; then | |
| echo "Ошибка: Необходимый инструмент '$tool' не найден. Установите его." >&2 | |
| exit 1 | |
| fi | |
| done | |
| # Проверка прав root | |
| if [ "$EUID" -ne 0 ]; then | |
| echo "Ошибка: Скрипт должен быть запущен с правами root (sudo)." >&2 | |
| exit 1 | |
| fi | |
| # Функция логирования | |
| log() { | |
| echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE" | |
| } | |
| # Проверка состояния | |
| get_state() { | |
| if [ -f "$STATE_FILE" ]; then | |
| cat "$STATE_FILE" | |
| else | |
| echo "off" | |
| fi | |
| } | |
| case "$1" in | |
| start) | |
| log "Запуск режима обслуживания" | |
| echo "on" > "$STATE_FILE" | |
| # Создание задачи для экстренного восстановления | |
| cat > "$TASK_FILE" << EOL | |
| # Emergency restore task | |
| @reboot root /bin/bash $0 restore | |
| EOL | |
| log "Режим обслуживания включен, задача на восстановление при перезагрузке добавлена" | |
| ;; | |
| stop) | |
| log "Остановка режима обслуживания" | |
| echo "off" > "$STATE_FILE" | |
| rm -f "$TASK_FILE" | |
| log "Режим обслуживания выключен, задача на восстановление удалена" | |
| ;; | |
| backup) | |
| log "Запуск создания бэкапа" | |
| rsnapshot manually | |
| if [ $? -eq 0 ]; then | |
| log "Бэкап успешно создан" | |
| else | |
| log "Ошибка при создании бэкапа" | |
| exit 1 | |
| fi | |
| ;; | |
| restore) | |
| BACKUP_VERSION=${2:-"manually.0"} | |
| BACKUP_PATH="$BACKUP_ROOT/$BACKUP_VERSION/localhost" | |
| log "Начало восстановления из $BACKUP_PATH" | |
| if [ ! -d "$BACKUP_PATH" ]; then | |
| log "Ошибка: Бэкап $BACKUP_PATH не найден" | |
| exit 1 | |
| fi | |
| # Перебор всех директорий в бэкапе | |
| find "$BACKUP_PATH" -maxdepth 1 -type d | while read -r SOURCE; do | |
| # Пропускаем саму корневую директорию бэкапа | |
| [ "$SOURCE" = "$BACKUP_PATH" ] && continue | |
| # Получаем относительный путь | |
| DIR=${SOURCE#$BACKUP_PATH} | |
| DEST="$DIR" | |
| # Пропускаем восстановление директории /backup | |
| [ "$DEST" = "/backup" ] && continue | |
| log "Восстановление $DIR -> $DEST" | |
| # Подготовка списка исключений для rsync | |
| RSYNC_EXCLUDES=() | |
| for exclude in "${EXCLUDE_LIST[@]}"; do | |
| RSYNC_EXCLUDES+=("--exclude=$exclude") | |
| done | |
| # Добавляем абсолютный путь лога как отдельное исключение | |
| RSYNC_EXCLUDES+=("--exclude=$LOG_FILE") | |
| # Подробное восстановление с выводом изменений | |
| rsync -av --delete \ | |
| --progress \ | |
| --stats \ | |
| "${RSYNC_EXCLUDES[@]}" \ | |
| "$SOURCE/" "$DEST/" 2>> "$LOG_FILE" | while IFS= read -r line; do | |
| log "rsync: $line" | |
| done | |
| if [ ${PIPESTATUS[0]} -eq 0 ]; then | |
| log "Успешно восстановлено $DIR" | |
| else | |
| log "Ошибка при восстановлении $DIR" | |
| exit 1 | |
| fi | |
| done | |
| log "Восстановление завершено" | |
| log "Рекомендуется перезагрузить систему для применения изменений" | |
| # Предупреждение о режиме обслуживания | |
| if [ "$(get_state)" = "on" ]; then | |
| log "ВНИМАНИЕ: Режим обслуживания активен. Рекомендуется сначала выполнить '$0 stop' перед перезагрузкой," | |
| log "чтобы избежать автоматического восстановления при следующем запуске системы" | |
| fi | |
| ;; | |
| status) | |
| echo "Режим обслуживания: $(get_state)" | |
| # Информация о последнем бэкапе (manually.0) | |
| BACKUP_VERSION="manually.0" | |
| BACKUP_PATH="$BACKUP_ROOT/$BACKUP_VERSION" | |
| if [ -d "$BACKUP_PATH" ]; then | |
| BACKUP_TIME=$(stat -c '%Y' "$BACKUP_PATH" | date -d "@$(cat -)" '+%Y-%m-%d %H:%M:%S') | |
| echo "Время создания последнего бэкапа: $BACKUP_TIME" | |
| else | |
| echo "Последний бэкап ($BACKUP_VERSION) не найден" | |
| fi | |
| ;; | |
| *) | |
| echo "Использование: $0 {start|stop|backup|restore [version]|status}" | |
| echo "Пример: $0 restore manually.1" | |
| exit 1 | |
| ;; | |
| esac | |
| exit 0 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # Путь к корню бэкапов rsnapshot | |
| BACKUP_ROOT="/backup/rsnapshot" | |
| # По умолчанию используем daily.0, но можно передать аргумент | |
| BACKUP_VERSION=${1:-"manually.0"} | |
| # Путь к бэкапу | |
| BACKUP_PATH="$BACKUP_ROOT/$BACKUP_VERSION/localhost" | |
| # Лог-файл для записи действий | |
| LOG_FILE="/var/log/rollback.log" | |
| # Список директорий для восстановления | |
| DIRECTORIES=( | |
| "/etc/" | |
| "/var/" | |
| "/home/" | |
| "/root/" | |
| "/boot/" | |
| "/usr/local/" | |
| "/usr/" | |
| "/lib/" | |
| "/bin/" | |
| "/sbin/" | |
| ) | |
| # Проверка, запущен ли скрипт от root | |
| if [ "$EUID" -ne 0 ]; then | |
| echo "Ошибка: Скрипт должен быть запущен с правами root (sudo)." >&2 | |
| exit 1 | |
| fi | |
| # Проверка существования бэкапа | |
| if [ ! -d "$BACKUP_PATH" ]; then | |
| echo "Файл $BACKUP_PATH не найден. Создаю новый файл..." | |
| touch "$BACKUP_PATH" || { echo "Ошибка: Не удалось создать файл $BACKUP_PATH." >&2; exit 1; } | |
| fi | |
| # Логирование начала выполнения | |
| echo "Начало отката из $BACKUP_PATH: $(date)" | tee -a "$LOG_FILE" | |
| # Восстановление каждой директории | |
| for DIR in "${DIRECTORIES[@]}"; do | |
| SOURCE="$BACKUP_PATH$DIR" | |
| DEST="/${DIR#/}" # Убираем ведущий слеш для корректного пути назначения | |
| if [ -d "$SOURCE" ]; then | |
| echo "Восстановление $DIR из $SOURCE..." | tee -a "$LOG_FILE" | |
| rsync -a --delete \ | |
| --exclude=.cache \ | |
| --exclude=/lib/docker \ | |
| --exclude=/etc/rsnapshot.conf \ | |
| --exclude="$LOG_FILE" \ | |
| "$SOURCE" "$DEST" 2>> "$LOG_FILE" | |
| if [ $? -eq 0 ]; then | |
| echo "Успешно восстановлено $DIR" | tee -a "$LOG_FILE" | |
| else | |
| echo "Ошибка при восстановлении $DIR, проверьте $LOG_FILE" >&2 | |
| exit 1 | |
| fi | |
| else | |
| echo "Предупреждение: $SOURCE не найден в бэкапе, пропускаю." | tee -a "$LOG_FILE" | |
| fi | |
| done | |
| # Логирование завершения | |
| echo "Откат завершён: $(date)" | tee -a "$LOG_FILE" | |
| echo "Система восстановлена из $BACKUP_PATH. Перезагрузите систему для проверки." | tee -a "$LOG_FILE" | |
| exit 0 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment