Skip to content

Instantly share code, notes, and snippets.

@MrPandir
Created December 5, 2025 16:51
Show Gist options
  • Select an option

  • Save MrPandir/8ad01c014bdf5ef729c459887b21311f to your computer and use it in GitHub Desktop.

Select an option

Save MrPandir/8ad01c014bdf5ef729c459887b21311f to your computer and use it in GitHub Desktop.
Automatic rollback on server restart
#!/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
#!/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