Темы
Сервис архивации
Чистка каталогов по списку
Создание пользователей и баз для MySQL
Добавление виртуального хоста APACHE пользователей и баз данных
Включение по сети (Wake-on-LAN)
Построение файлов зон для DNS-сервера
Построение MD5-ХЭШ сумм файлов
Примеры монтирования CIFS
Синхронизация локального репозитория для CentOS
Очистка и переиндексация баз данных Postgres
Скрипт для архивирования баз
Список активных хостов в сети
Тестирование группы хостов на корректность записей в прямой и обратной DNS-зоне
Управление виртуальными машинами скриптами
NMAP: Сканирование хостов и портов (обёртка)
Резервное копирование Файловой системы XFS
Перезапуск сервисов после сна
Динамическое сообщение дня (motd)
Резервное копирование виртуальных машин VirtualBox
Переключение виртуальных сред VirtualBox и KVM
Дополнительные материалы
Wake on Lan: Удалённое включение компьютера
Сервис архивации
backup-hosts.sh
- Код: выделить все
!/bin/bash
# ============================================================
# Web Hosts Backup Service
# ============================================================
VERSION="3.2"
# ------------------------------------------------------------
# Настройки
# ------------------------------------------------------------
BACKUP_PATH="/mnt/BACKUP/WebHost/"
WEB_DIR="/var/www/"
LOG_FILE="/var/log/backup-hosts.log"
KEEP_BACKUPS=6 # Количество хранимых бэкапов
# MySQL/MariaDB
MYSQL_CMD="mariadb" # или mysql
MYSQLDUMP_CMD="mariadb-dump" # или mysqldump
MYSQL_USER="root"
MYSQL_PASS="RootMySQL"
LOG_LEVEL=3
USE_SYSLOG=false
# ------------------------------------------------------------
# Проверка: mysql или mariadb?
# ------------------------------------------------------------
function detect_mysql_client() {
if command -v mariadb &>/dev/null; then
MYSQL_CMD="mariadb"
MYSQLDUMP_CMD="mariadb-dump"
log_debug "Используется MariaDB клиент"
elif command -v mysql &>/dev/null; then
MYSQL_CMD="mysql"
MYSQLDUMP_CMD="mysqldump"
log_debug "Используется MySQL клиент"
else
log_error "MySQL/MariaDB клиент не найден!"
return 1
fi
return 0
}
# ------------------------------------------------------------
# Unicode / ASCII
# ------------------------------------------------------------
if [[ "${1:-}" == "--no-icons" ]]; then
INFO_ICON=">"
OK_ICON="+"
WARN_ICON="!"
ERR_ICON="x"
DEBUG_ICON="D"
STAR_ICON="*"
else
INFO_ICON="→"
OK_ICON="✓"
WARN_ICON="⚠"
ERR_ICON="✗"
DEBUG_ICON="•"
STAR_ICON="★"
fi
# ------------------------------------------------------------
# Переменные
# ------------------------------------------------------------
STATUS="SUCCESS"
LOG_SESSION_ENDED=false
LOG_SESSION_STARTED=false
DATE="$(date '+%d-%m-%Y')"
TIME="$(date '+%H-%M')"
BACKUP_DIR="$BACKUP_PATH/$DATE"
TOTAL_ARCHIVES=0
TOTAL_SIZE=0
# ------------------------------------------------------------
# Логирование
# ------------------------------------------------------------
function _log() {
local level="$1"; shift
local message="$*"
local timestamp
timestamp="$(date '+%Y-%m-%d %H:%M:%S')"
local level_num level_tag color_tag color_reset
case "$level" in
ERROR) level_num=1; level_tag="${ERR_ICON} ERROR"; color_tag="\033[1;31m" ;;
WARN) level_num=2; level_tag="${WARN_ICON} WARN "; color_tag="\033[1;33m" ;;
INFO) level_num=3; level_tag="${INFO_ICON} INFO "; color_tag="\033[1;34m" ;;
OK) level_num=3; level_tag="${OK_ICON} OK "; color_tag="\033[1;32m" ;;
DEBUG) level_num=4; level_tag="${DEBUG_ICON} DEBUG"; color_tag="\033[0;90m" ;;
*) level_num=3; level_tag="INFO"; color_tag="\033[0m" ;;
esac
color_reset="\033[0m"
# Запись в лог-файл
echo "[$timestamp] [$level_tag] $message" >> "$LOG_FILE"
# Запись в syslog
if [[ "$USE_SYSLOG" == true ]]; then
logger -t "$(basename "$0")" "[$level] $message"
fi
# Вывод в консоль
if (( level_num <= LOG_LEVEL )); then
if [[ -t 1 ]]; then
echo -e "${color_tag}[$timestamp] [$level_tag]${color_reset} $message"
else
echo "[$timestamp] [$level_tag] $message"
fi
fi
}
log_info() { _log INFO "$@"; }
log_warn() { _log WARN "$@"; }
log_error() { _log ERROR "$@"; }
log_ok() { _log OK "$@"; }
log_debug() { _log DEBUG "$@"; }
# ------------------------------------------------------------
# Session Log
# ------------------------------------------------------------
function log_session_start() {
local line="START | MODE: WEB_BACKUP | VERSION: $VERSION"
local width=60
local padding=$(( (width - ${#line}) / 2 ))
{
printf '=%.0s' $(seq 1 $width)
printf "\n%*s%s\n" "$padding" "" "$line"
printf '=%.0s' $(seq 1 $width)
echo
echo
} >> "$LOG_FILE"
echo
printf '=%.0s' $(seq 1 $width)
printf "\n%*s%s\n" "$padding" "" "$line"
printf '=%.0s' $(seq 1 $width)
echo
echo
LOG_SESSION_STARTED=true
}
function log_session_end() {
local status="$1"
local line="END | STATUS: $status"
local width=60
local padding=$(( (width - ${#line}) / 2 ))
{
echo
printf '=%.0s' $(seq 1 $width)
echo
printf "%*s%s\n" "$padding" "" "$line"
printf '=%.0s' $(seq 1 $width)
echo
} >> "$LOG_FILE"
echo
printf '=%.0s' $(seq 1 $width)
echo
printf "%*s%s\n" "$padding" "" "$line"
printf '=%.0s' $(seq 1 $width)
echo
LOG_SESSION_ENDED=true
}
# ------------------------------------------------------------
# Cleanup
# ------------------------------------------------------------
cleanup() {
if [[ "$LOG_SESSION_ENDED" == false ]]; then
log_session_end "$STATUS"
fi
}
trap cleanup EXIT
trap 'STATUS="FAILED"; exit 1' INT TERM
# ------------------------------------------------------------
# Header
# ------------------------------------------------------------
log_session_start
log_info "Скрипт: $(basename "$0")"
log_info "Версия: $VERSION"
log_info "Каталог бэкапов: $BACKUP_DIR"
# ------------------------------------------------------------
# Проверка зависимостей
# ------------------------------------------------------------
function check_dependencies() {
local deps=("tar" "find" "du" "awk")
local missing=()
for dep in "${deps[@]}"; do
if ! command -v "$dep" &>/dev/null; then
missing+=("$dep")
fi
done
# Проверка MySQL/MariaDB
if ! detect_mysql_client; then
missing+=("mariadb/mysql")
fi
if [[ ${#missing[@]} -gt 0 ]]; then
log_error "Отсутствуют зависимости: ${missing[*]}"
return 1
fi
log_ok "Все зависимости установлены"
return 0
}
# ------------------------------------------------------------
# Форматирование размера
# ------------------------------------------------------------
function format_size() {
local size="$1"
if [[ -z "$size" ]] || [[ "$size" -eq 0 ]]; then
echo "0 B"
return
fi
if [[ "$size" -gt 1073741824 ]]; then
echo "$(echo "scale=2; $size/1073741824" | bc 2>/dev/null || echo "0") GB"
elif [[ "$size" -gt 1048576 ]]; then
echo "$(echo "scale=2; $size/1048576" | bc 2>/dev/null || echo "0") MB"
elif [[ "$size" -gt 1024 ]]; then
echo "$(echo "scale=2; $size/1024" | bc 2>/dev/null || echo "0") KB"
else
echo "${size} B"
fi
}
# ------------------------------------------------------------
# Создание каталогов
# ------------------------------------------------------------
function create_directories() {
local backup_dir="$1"
log_info "Создание структуры каталогов..."
if [[ ! -d "$backup_dir" ]]; then
mkdir -p "$backup_dir"/{WWW,SQL,USERS,SYS} 2>/dev/null
log_ok "Создана структура: $backup_dir"
else
log_warn "Каталог уже существует: $backup_dir"
fi
}
# ------------------------------------------------------------
# Проверка свободного места
# ------------------------------------------------------------
function check_free_space() {
local backup_dir="$1"
local required_gb="$2"
if [[ ! -d "$backup_dir" ]]; then
mkdir -p "$backup_dir"
fi
local free_space=$(df "$backup_dir" | awk 'NR==2 {print $4}')
local free_gb=$((free_space / 1024 / 1024))
log_info "Свободно на диске: ${free_gb} GB"
if [[ "$free_gb" -lt "$required_gb" ]]; then
log_error "Недостаточно места! Требуется ~${required_gb} GB, доступно ${free_gb} GB"
return 1
fi
log_ok "Места достаточно"
return 0
}
# ------------------------------------------------------------
# Бэкап WEB-хостов
# ------------------------------------------------------------
function backup_web_hosts() {
local web_dir="$1"
local backup_dir="$2"
local time="$3"
log_info "Бэкап WEB-хостов..."
if [[ ! -d "$web_dir" ]]; then
log_error "Каталог не существует: $web_dir"
return 1
fi
local dirs=($(find "$web_dir" -mindepth 1 -maxdepth 1 -type d -printf '%f\n' 2>/dev/null))
if [[ ${#dirs[@]} -eq 0 ]]; then
log_warn "Нет хостов для бэкапа"
return 0
fi
local count=0
for dir in "${dirs[@]}"; do
if [[ "$dir" =~ ^[a-zA-Z] ]]; then
if [[ -d "$web_dir/$dir" ]]; then
local archive_name="${time}.${dir}.tar.gz"
local archive_path="$backup_dir/WWW/$archive_name"
log_debug " Архивирование: $dir"
if tar -czpf "$archive_path" -C "$web_dir" "$dir" 2>/dev/null; then
local size=$(stat -c%s "$archive_path" 2>/dev/null || echo 0)
((count++))
((TOTAL_ARCHIVES++))
((TOTAL_SIZE += size))
log_ok " $archive_name ($(format_size $size))"
else
log_error " Ошибка создания архива: $dir"
fi
fi
fi
done
log_info "Заархивировано хостов: $count"
}
# ------------------------------------------------------------
# Бэкап баз данных
# ------------------------------------------------------------
function backup_databases() {
local backup_dir="$1"
local time="$2"
log_info "Бэкап баз данных MySQL/MariaDB..."
# Получаем список баз данных
local databases=$($MYSQL_CMD --user="$MYSQL_USER" --password="$MYSQL_PASS" -e "SHOW DATABASES;" 2>/dev/null | tr -d "| " | grep -v "Database" | grep -v "information_schema" | grep -v "performance_schema" | grep -v "mysql" | grep -v "sys")
if [[ -z "$databases" ]]; then
log_warn "Нет баз данных для бэкапа"
return 0
fi
local count=0
for db in $databases; do
if [[ "$db" =~ ^[a-zA-Z] ]]; then
local dump_name="${time}.${db}.sql"
local dump_path="$backup_dir/SQL/$dump_name"
log_debug " Дамп базы: $db"
if $MYSQLDUMP_CMD --user="$MYSQL_USER" --password="$MYSQL_PASS" -c -e -B "$db" > "$dump_path" 2>/dev/null; then
local size=$(stat -c%s "$dump_path" 2>/dev/null || echo 0)
((count++))
((TOTAL_ARCHIVES++))
((TOTAL_SIZE += size))
log_ok " $dump_name ($(format_size $size))"
else
log_error " Ошибка дампа базы: $db"
fi
fi
done
log_info "Заархивировано баз данных: $count"
}
# ------------------------------------------------------------
# Бэкап системных файлов
# ------------------------------------------------------------
function backup_system_files() {
local backup_dir="$1"
local time="$2"
log_info "Бэкап системных файлов..."
declare -A sys_dirs=(
["named"]="/var/named"
["etc"]="/etc"
["services"]="/usr/lib/systemd/system"
["my-script"]="/sbin/my-script"
["efi"]="/boot/efi"
["tftp"]="/var/lib/tftpboot"
)
local count=0
for name in "${!sys_dirs[@]}"; do
local source_dir="${sys_dirs[$name]}"
local archive_name="${time}.${name}.tar.gz"
local archive_path="$backup_dir/SYS/$archive_name"
if [[ -d "$source_dir" ]]; then
log_debug " Архивирование: $name ($source_dir)"
if tar -czpf "$archive_path" -C "$source_dir" . 2>/dev/null; then
local size=$(stat -c%s "$archive_path" 2>/dev/null || echo 0)
((count++))
((TOTAL_ARCHIVES++))
((TOTAL_SIZE += size))
log_ok " $archive_name ($(format_size $size))"
else
log_warn " Ошибка архивирования: $name"
fi
else
log_debug " Пропуск (нет каталога): $source_dir"
fi
done
log_info "Заархивировано системных каталогов: $count"
}
# ------------------------------------------------------------
# Бэкап пользовательских файлов
# ------------------------------------------------------------
function backup_user_files() {
local backup_dir="$1"
local time="$2"
log_info "Бэкап пользовательских файлов..."
declare -A user_items=(
["doc"]="/root/doc"
["scripts"]="/root/SH"
["conky"]="/home/serg/.conky"
["FilterRules"]="/home/serg/.thunderbird/3l1n9p02.default/ImapMail/imap.gmail.com/msgFilterRules.dat"
)
local count=0
for name in "${!user_items[@]}"; do
local source="${user_items[$name]}"
local archive_name="${time}.${name}.tar.gz"
local archive_path="$backup_dir/USERS/$archive_name"
if [[ -e "$source" ]]; then
log_debug " Архивирование: $name ($source)"
if [[ -f "$source" ]]; then
local dir=$(dirname "$source")
local file=$(basename "$source")
if tar -czpf "$archive_path" -C "$dir" "$file" 2>/dev/null; then
local size=$(stat -c%s "$archive_path" 2>/dev/null || echo 0)
((count++))
((TOTAL_ARCHIVES++))
((TOTAL_SIZE += size))
log_ok " $archive_name ($(format_size $size))"
else
log_warn " Ошибка архивирования файла: $source"
fi
else
if tar -czpf "$archive_path" -C "$source" . 2>/dev/null; then
local size=$(stat -c%s "$archive_path" 2>/dev/null || echo 0)
((count++))
((TOTAL_ARCHIVES++))
((TOTAL_SIZE += size))
log_ok " $archive_name ($(format_size $size))"
else
log_warn " Ошибка архивирования каталога: $source"
fi
fi
else
log_debug " Пропуск (не существует): $source"
fi
done
log_info "Заархивировано пользовательских файлов: $count"
}
# ------------------------------------------------------------
# Ротация старых бэкапов
# ------------------------------------------------------------
function rotate_backups() {
local backup_root="$1"
local keep_count="$2"
log_info "Ротация бэкапов (хранить $keep_count)..."
if [[ ! -d "$backup_root" ]]; then
log_info "Каталог бэкапов не существует"
return 0
fi
local dirs=($(find "$backup_root" -maxdepth 1 -type d ! -path "$backup_root" -printf '%T@ %f\n' 2>/dev/null | sort -n | awk '{print $2}'))
local total=${#dirs[@]}
if [[ "$total" -le "$keep_count" ]]; then
log_info "Бэкапов: $total (не превышает $keep_count)"
return 0
fi
local to_delete=$((total - keep_count))
local deleted=0
local deleted_size=0
for ((i=0; i<to_delete; i++)); do
local dir="$backup_root/${dirs[$i]}"
if [[ -d "$dir" ]]; then
local size=$(du -sb "$dir" 2>/dev/null | awk '{print $1}')
rm -rf "$dir"
((deleted++))
((deleted_size += size))
log_debug "Удалён бэкап: $(basename "$dir")"
fi
done
log_ok "Удалено $deleted старых бэкапов ($(format_size $deleted_size))"
}
# ------------------------------------------------------------
# Главная функция
# ------------------------------------------------------------
function main() {
log_info "Запуск резервного копирования WEB-хостов..."
if ! check_dependencies; then
STATUS="FAILED"
return 1
fi
if ! check_free_space "$BACKUP_PATH" 2; then
STATUS="FAILED"
return 1
fi
create_directories "$BACKUP_DIR"
backup_web_hosts "$WEB_DIR" "$BACKUP_DIR" "$TIME"
backup_databases "$BACKUP_DIR" "$TIME"
backup_system_files "$BACKUP_DIR" "$TIME"
backup_user_files "$BACKUP_DIR" "$TIME"
echo
echo "${STAR_ICON} ${STAR_ICON} ${STAR_ICON} ИТОГИ БЭКАПА ${STAR_ICON} ${STAR_ICON} ${STAR_ICON}"
echo " ${INFO_ICON} Создано архивов: $TOTAL_ARCHIVES"
echo " ${SIZE_ICON} Общий размер: $(format_size $TOTAL_SIZE)"
echo " ${INFO_ICON} Каталог: $BACKUP_DIR"
echo
log_ok "Бэкап завершён успешно"
rotate_backups "$BACKUP_PATH" "$KEEP_BACKUPS"
return 0
}
# ------------------------------------------------------------
# Справка
# ------------------------------------------------------------
function show_help() {
cat << EOF
${STAR_ICON} ${STAR_ICON} ${STAR_ICON} WEB HOSTS BACKUP SERVICE ${STAR_ICON} ${STAR_ICON} ${STAR_ICON}
Назначение: Резервное копирование WEB-хостов, БД MySQL/MariaDB и системных файлов
Использование: $(basename "$0") [ОПЦИИ]
Опции:
-b, --backup Создать бэкап (действие по умолчанию)
-c, --clean Только очистить старые бэкапы
-k, --keep N Хранить N последних бэкапов (по умолчанию: 6)
-p, --path ПУТЬ Каталог для бэкапов (по умолчанию: $BACKUP_PATH)
--no-icons Отключить иконки
-h, --help Показать эту справку
-v, --version Показать версию
Примеры:
# Создать бэкап
$(basename "$0")
# Создать бэкап с хранением 10 копий
$(basename "$0") -k 10
# Только очистить старые бэкапы
$(basename "$0") -c
# Указать другой каталог
$(basename "$0") -p /mnt/backup/web
EOF
}
# ------------------------------------------------------------
# Версия
# ------------------------------------------------------------
function show_version() {
echo "Web Hosts Backup Service"
echo "Версия $VERSION"
echo "© SSERGEY Дата: 11.11.2017 (обновлено: 2026)"
}
# ------------------------------------------------------------
# Обработка аргументов
# ------------------------------------------------------------
CLEAN_ONLY=false
while [[ $# -gt 0 ]]; do
case "$1" in
-b|--backup)
shift
;;
-c|--clean)
CLEAN_ONLY=true
shift
;;
-k|--keep)
KEEP_BACKUPS="$2"
shift 2
;;
-p|--path)
BACKUP_PATH="$2"
shift 2
;;
--no-icons)
shift
;;
-h|--help)
show_help
log_session_end "HELP"
exit 0
;;
-v|--version)
show_version
log_session_end "VERSION"
exit 0
;;
*)
log_error "Неизвестный параметр: $1"
show_help
STATUS="FAILED"
exit 1
;;
esac
done
# ------------------------------------------------------------
# Проверка прав
# ------------------------------------------------------------
if [[ $EUID -ne 0 ]]; then
log_error "Требуются права суперпользователя!"
log_info "Попробуйте: sudo $(basename "$0") $*"
STATUS="FAILED"
exit 1
fi
# ------------------------------------------------------------
# Основная логика
# ------------------------------------------------------------
if [[ "$CLEAN_ONLY" == true ]]; then
rotate_backups "$BACKUP_PATH" "$KEEP_BACKUPS"
log_ok "Очистка завершена"
exit 0
fi
if main; then
log_ok "Скрипт завершил работу"
else
STATUS="FAILED"
exit 1
fi
# ------------------------------------------------------------
# Итог
# ------------------------------------------------------------
log_info "Скрипт: $(basename "$0")"
exit 0
Подготавливаем скрипт к работе в режиме демона по расписанию
e /etc/systemd/system/backup-hosts.service
- Код: выделить все
[Unit]
Description=Backup Apache virtual hosts & MySQL databases (serg)
[Service]
Type=oneshot
ExecStart=/sbin/my-script/backup-hosts.sh
StandardOutput=journal
cat /etc/systemd/system/backup-hosts.timer
- Код: выделить все
[Unit]
Description=Run on 21:55 Backup Apache virtual hosts & MySQL databases (serg)
[Timer]
OnCalendar=Mon *-*-* 21:55:00
# Принудительно запустится единожды, если отсутствует последнее время запуска, например, в связи с отключением системы:
#Persistent=true
Unit=backup-hosts.service
[Install]
WantedBy=timers.target
Запуск сервиса
- Код: выделить все
systemctl enable --now backup-hosts.timer
Enable для задания означает то, что оно появится в списке заданий, --now - включит задание в работу
Пример задания на ротацию логов в systemd
cat /etc/systemd/system/logrotate.timer
- Код: выделить все
[Unit]
Description=Run on 23:59 Rotate log files
[Timer]
OnCalendar=23:59
Persistent=true
[Install]
WantedBy=timers.target
cat /etc/systemd/system/logrotate.service
- Код: выделить все
[Unit]
Description=Rotate log files
[Service]
Type=oneshot
ExecStart=/sbin/my-script/logrotate
StandardOutput=rsyslog
Не забываем снести задание в CRON
