Хорошо написанный скрипт bash покрывает 90% потребностей в резервном копировании Linux без установки чего-либо. tar, gzip и systemd timer уже есть на каждом сервере. Никаких агентов, никаких демонов, никаких репозиториев пакетов для настройки.

Сценарий в этом руководстве обрабатывает несколько каталогов источников, три варианта сжатия (gzip, bzip2, xz), проверку дискового пространства перед каждым запуском, очистку от удержания, блокировку файлов для предотвращения перекрывающихся запусков и переопределение файла конфигурации для различных профилей резервного копирования.

Предпосылки

  • Сервер под управлением Ubuntu 26.04
  • Права пользователя: пользователь root или обычный пользователь с привилегиями sudo.
  • Место резервного копирования с достаточным количеством свободного места: локальный диск, установленный NFS-аккаунт или USB-накопитель.

Конвенции

1
2
# - данные команды должны выполняться с правами root либо непосредственно от имени пользователя root, либо с помощью команды sudo.
$ - данные команды должны выполняться от имени обычного пользователя

Сценарий Backup Script

Этот скрипт архивирует несколько каталогов в один сжатый tarball с меткой времени имя файла, которое включает имя хоста. Перед запуском он проверяет доступное дисковое пространство и приобретает файл блокировки, поэтому два экземпляра не могут работать одновременно. После завершения резервного копирования он удаляет архивы старше настроенного периода хранения. Каждое действие регистрируется с помощью меток времени.

Создайте файл сценария:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
sudo tee /usr/local/bin/system-backup.sh > /dev/null <<'EOF'
#!/bin/bash

# =====================================================================
# Linux System Backup Script
# =====================================================================
# Creates compressed, timestamped backups of specified directories.
# Supports gzip/bzip2/xz compression, disk space checks, retention
# cleanup, and optional external config files.
#
# Usage: system-backup.sh [config_file]
# =====================================================================

set -euo pipefail

# =====================================================================
# Configuration (override via config file passed as $1)
# =====================================================================
BACKUP_DIR="/backup"
DIRECTORIES_TO_BACKUP=("/etc" "/home" "/root" "/opt")
EXCLUDE_PATTERNS=("*.tmp" "*.swp" "*.swx" ".cache" "__pycache__" "node_modules" "/home/*/Downloads" "/home/*/Trash" "/home/*/.local/share/Trash")
RETENTION_DAYS=30
LOG_FILE="/var/log/system-backup.log"
DATE_FORMAT="%Y-%m-%d_%H-%M-%S"
COMPRESSION_TYPE="gzip"   # gzip, bzip2, or xz
REQUIRED_SPACE_GB=1
LOCK_FILE="/var/run/system-backup.lock"

# =====================================================================
# Functions
# =====================================================================

log() {
    local level="$1"
    shift
    local msg="$*"
    local ts
    ts=$(date "+%Y-%m-%d %H:%M:%S")
    echo "[$ts] [$level] $msg" | tee -a "$LOG_FILE"
}

load_config() {
    local cfg="$1"
    if [ -f "$cfg" ]; then
        log "INFO" "Loading configuration from $cfg"
        # shellcheck source=/dev/null
        source "$cfg"
    else
        log "WARNING" "Config file $cfg not found, using defaults"
    fi
}

check_root() {
    if [ "$(id -u)" -ne 0 ]; then
        log "ERROR" "This script must be run as root"
        exit 1
    fi
}

acquire_lock() {
    exec 9>"$LOCK_FILE"
    if ! flock -n 9; then
        log "ERROR" "Another backup is already running (lock: $LOCK_FILE)"
        exit 1
    fi
}

check_disk_space() {
    local required_kb=$((REQUIRED_SPACE_GB * 1048576))
    local available_kb
    available_kb=$(df -k "$BACKUP_DIR" | awk 'NR==2 {print $4}')
    local available_gb
    available_gb=$(awk "BEGIN {printf \"%.1f\", $available_kb / 1048576}")

    if [ "$available_kb" -lt "$required_kb" ]; then
        log "ERROR" "Not enough disk space. Required: ${REQUIRED_SPACE_GB}GB, Available: ${available_gb}GB"
        exit 1
    fi
    log "INFO" "Disk space available: ${available_gb}GB (need ${REQUIRED_SPACE_GB}GB)"
}

create_backup() {
    local ts
    ts=$(date +"$DATE_FORMAT")
    local host
    host=$(hostname -s)
    local backup_file="${BACKUP_DIR}/backup_${host}_${ts}.tar"
    local compression_opt=""

    case "$COMPRESSION_TYPE" in
        gzip)  backup_file="${backup_file}.gz";  compression_opt="-z" ;;
        bzip2) backup_file="${backup_file}.bz2"; compression_opt="-j" ;;
        xz)    backup_file="${backup_file}.xz";  compression_opt="-J" ;;
        *)     backup_file="${backup_file}.gz";   compression_opt="-z" ;;
    esac

    # Build exclude flags
    local exclude_args=()
    for pattern in "${EXCLUDE_PATTERNS[@]}"; do
        exclude_args+=("--exclude=$pattern")
    done

    # Filter to directories that actually exist
    local valid_sources=()
    for src in "${DIRECTORIES_TO_BACKUP[@]}"; do
        if [ -d "$src" ]; then
            valid_sources+=("$src")
        else
            log "WARNING" "Skipping $src (does not exist)"
        fi
    done

    if [ ${#valid_sources[@]} -eq 0 ]; then
        log "ERROR" "No valid source directories to back up"
        exit 1
    fi

    log "INFO" "Backing up: ${valid_sources[*]}"
    log "INFO" "Destination: $backup_file"

    tar "$compression_opt" -cf "$backup_file" \
        "${exclude_args[@]}" \
        "${valid_sources[@]}" 2>/tmp/backup_errors.log

    local rc=$?
    if [ "$rc" -eq 0 ] || [ "$rc" -eq 1 ]; then
        local size
        size=$(du -h "$backup_file" | cut -f1)
        log "INFO" "Backup complete: $backup_file ($size)"

        if [ "$rc" -eq 1 ]; then
            log "WARNING" "Some files changed during archiving (tar exit 1, non-fatal)"
        fi
    else
        log "ERROR" "Backup failed (tar exit code $rc)"
        if [ -s /tmp/backup_errors.log ]; then
            log "ERROR" "$(head -5 /tmp/backup_errors.log)"
        fi
        exit 1
    fi
}

clean_old_backups() {
    if [ "$RETENTION_DAYS" -le 0 ]; then
        log "INFO" "Retention disabled, skipping cleanup"
        return
    fi

    local count
    count=$(find "$BACKUP_DIR" -maxdepth 1 -name "backup_*.tar.*" -mtime +"$RETENTION_DAYS" | wc -l)

    if [ "$count" -gt 0 ]; then
        find "$BACKUP_DIR" -maxdepth 1 -name "backup_*.tar.*" -mtime +"$RETENTION_DAYS" -delete
        log "INFO" "Removed $count backup(s) older than $RETENTION_DAYS days"
    else
        log "INFO" "No backups older than $RETENTION_DAYS days to remove"
    fi
}

# =====================================================================
# Main
# =====================================================================

if [ $# -gt 0 ]; then
    load_config "$1"
fi

check_root
acquire_lock
mkdir -p "$BACKUP_DIR"

log "INFO" "========== BACKUP STARTED =========="

START=$(date +%s)

check_disk_space
create_backup
clean_old_backups

END=$(date +%s)
DURATION=$((END - START))
log "INFO" "Total runtime: ${DURATION}s"
log "INFO" "========== BACKUP FINISHED =========="
EOF

Сделайте сценарий исполняемым:

1
sudo chmod +x /usr/local/bin/system-backup.sh

Основные функции сценария

Все переменные установлены в верхней части сценария. Любой из них может быть переопределен путем передачи файла конфигурации (охватываемого в следующем разделе).

  1. Конфигурация:
  • Настраиваемое резервное местоположение, каталоги для резервного копирования и период хранения
  • Исключает общие ненужные файлы (*.tmp, *.swp, .cache, и т.д.)
  • Поддержка различных типов сжатия (gzip, bzip2, xz)
  1. Особенности безопасности:
  • Проверка корневых привилегий
  • Проверка доступного дискового пространства
  • Включает обработку ошибок и регистрацию
  • Создает подробные журналы с таймерами
  1. Возможности резервного копирования:
  • Временные метки на именах
  • Настраиваемые варианты сжатия
  • Создает список резервных файлов
  • Автоматическая очистка старых резервных копий

Тестирование Backup Script

Запустите сценарий вручную, чтобы проверить, что все работает, прежде чем настраивать таймер. Выполните его как root, так как ему нужен доступ к системным каталогам:

1
sudo /usr/local/bin/system-backup.sh

На выходе показан каждый этап процесса резервного копирования:

1
2
3
4
5
6
7
8
[2026-05-26 23:54:39] [INFO] ========== BACKUP STARTED ==========
[2026-05-26 23:54:39] [INFO] Disk space available: 16.7GB (need 1GB)
[2026-05-26 23:54:39] [INFO] Backing up: /etc /home /root /opt
[2026-05-26 23:54:39] [INFO] Destination: /backup/backup_web01_2026-05-26_23-54-39.tar.gz
[2026-05-26 23:54:39] [INFO] Backup complete: /backup/backup_web01_2026-05-26_23-54-39.tar.gz (2.6M)
[2026-05-26 23:54:39] [INFO] No backups older than 30 days to remove
[2026-05-26 23:54:39] [INFO] Total runtime: 0s
[2026-05-26 23:54:39] [INFO] ========== BACKUP FINISHED ==========

Проверьте, существует ли архив в резервном каталоге:

1
ls -lh /backup/

Вы должны увидеть архив с отметками времени:

1
2
total 2.6M
-rw-r--r-- 1 root root 2.6M May 26 23:54 backup_web01_2026-05-26_23-54-39.tar.gz

Файл журнала в /var/log/system-backup.log содержит тот же вывод для последующего просмотра. Каждый запуск добавляется к этому файлу, давая вам полную историю каждой резервной копии.

Используйте файл config

Вместо того, чтобы редактировать сценарий каждый раз, когда вам нужны разные настройки, передайте файл конфигурации в качестве первого аргумента. Конфигурационный файл представляет собой простой файл bash, который перекрывает любую из переменных по умолчанию.

Создайте файл конфигурации для меньшей, сжатой xz резервной копии с 7-дневным сохранением:

1
2
3
4
5
6
7
sudo tee /etc/backup.conf > /dev/null <<'EOF'
BACKUP_DIR="/backup"
DIRECTORIES_TO_BACKUP=("/etc" "/opt/myapp")
COMPRESSION_TYPE="xz"
RETENTION_DAYS=7
REQUIRED_SPACE_GB=1
EOF

Запустите скрипт с файлом конфигурирования:

1
sudo /usr/local/bin/system-backup.sh /etc/backup.conf

Сценарий загружает конфигурацию и использует сжатие xz:

1
2
3
4
5
6
7
8
9
[2026-05-26 23:55:02] [INFO] Loading configuration from /etc/backup.conf
[2026-05-26 23:55:02] [INFO] ========== BACKUP STARTED ==========
[2026-05-26 23:55:02] [INFO] Disk space available: 16.7GB (need 1GB)
[2026-05-26 23:55:02] [INFO] Backing up: /etc /opt/myapp
[2026-05-26 23:55:02] [INFO] Destination: /backup/backup_web01_2026-05-26_23-55-02.tar.xz
[2026-05-26 23:55:04] [INFO] Backup complete: /backup/backup_web01_2026-05-26_23-55-02.tar.xz (2.5M)
[2026-05-26 23:55:04] [INFO] No backups older than 7 days to remove
[2026-05-26 23:55:04] [INFO] Total runtime: 2s
[2026-05-26 23:55:04] [INFO] ========== BACKUP FINISHED ==========

Обратите внимание, что архив xz немного меньше (2,5 М против 2,6 М для gzip), но занял 2 секунды вместо менее 1. Этот компромисс имеет большее значение для больших наборов данных. Вы можете создать несколько файлов конфигурации для различных профилей резервного копирования и вызвать их из отдельных системных таймеров.

Восстановление из Backup

Резервное копирование полезно только в том случае, если вы можете восстановить его. Проверьте это, извлекая архив во временный каталог.

Создайте тестовый каталог и извлеките полный архив:

1
2
3
mkdir -p /tmp/restore-test
cd /tmp/restore-test
tar xf /backup/backup_web01_2026-05-26_23-54-39.tar.gz

Проверьте восстановленную структуру каталога:

1
find /tmp/restore-test -maxdepth 2 -type d | head -20

Результат подтверждает сохранение иерархии исходных каталогов:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
/tmp/restore-test
/tmp/restore-test/etc
/tmp/restore-test/etc/ssh
/tmp/restore-test/etc/apt
/tmp/restore-test/etc/default
/tmp/restore-test/etc/sysctl.d
/tmp/restore-test/etc/systemd
/tmp/restore-test/etc/network
/tmp/restore-test/etc/security
/tmp/restore-test/home
/tmp/restore-test/root
/tmp/restore-test/opt

Чтобы извлечь один файл из архива (например, /etc/hostname):

1
tar xf /backup/backup_web01_2026-05-26_23-54-39.tar.gz etc/hostname

Обратите внимание, что путь внутри архива не начинается с ведущего слэша. Чтобы восстановить файл до его первоначального местоположения, используйте -C / флаг:

1
sudo tar xf /backup/backup_web01_2026-03-30_23-54-39.tar.gz -C / etc/hostname

Это извлекает etc/hostname непосредственно в /etc/hostname, перезаписывая текущий файл. Используйте это осторожно в запущенной системе.

Очистите каталог тестов, когда это будет сделано:

1
rm -rf /tmp/restore-test

Автоматизация с помощью Systemd Timer

Работа cron работает, но системные таймеры дают вам лучшую регистрацию, управление зависимостью и встроенные рандомизированные задержки, поэтому несколько серверов не все попадают в долю NFS одновременно.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
sudo tee /etc/systemd/system/system-backup.service > /dev/null <<'EOF'
[Unit]
Description=System directory backup
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot
ExecStart=/usr/local/bin/system-backup.sh /etc/backup.conf
Nice=19
IOSchedulingClass=idle
TimeoutStartSec=3600
EOF

Настройки Nice=19 и IOSchedulingClass=idle гарантируют, что резервное копирование выполняется с наименьшим приоритетом процессора и ввода/вывода, поэтому оно не будет мешать производственным нагрузкам. TimeoutStartSec=3600 обеспечивает большие резервные копии до часа, прежде чем система уничтожит процесс.

Создайте блок таймера:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
sudo tee /etc/systemd/system/system-backup.timer > /dev/null <<'EOF'
[Unit]
Description=Run system backup daily at 3:00 AM

[Timer]
OnCalendar=*-*-* 03:00:00
RandomizedDelaySec=900
Persistent=true

[Install]
WantedBy=timers.target
EOF

RandomizedDelaySec = 900 добавляет случайную задержку до 15 минут, что предотвращает одновременное забивание цели резервного копирования несколькими серверами. Persistent=true гарантирует, что резервная копия запускается при следующей загрузке, если сервер был отключен в запланированное время.

Перезагрузить систему и включить таймер:

1
2
sudo systemctl daemon-reload
sudo systemctl enable --now system-backup.timer

Подтвердите, что таймер активен и показывает следующий запланированный запуск:

1
systemctl list-timers system-backup.timer

Выход показывает, когда будет запущена следующая резервная копия:

1
2
3
4
NEXT                        LEFT     LAST PASSED UNIT                 ACTIVATES
Tue 2026-05-27 03:00:00 UTC 2h left  n/a  n/a    system-backup.timer  system-backup.service

1 timers listed.

Чтобы немедленно протестировать системную интеграцию, запустите службу вручную и посмотрите выход журнала:

1
2
sudo systemctl start system-backup.service
journalctl -u system-backup.service --no-pager

Журнал фиксирует полный вывод резервного копирования:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
May 26 23:55:10 web01 systemd[1]: Starting system-backup.service - System directory backup...
May 26 23:55:10 web01 system-backup.sh[1767]: [2026-03-26 23:55:10] [INFO] Loading configuration from /etc/backup.conf
May 26 23:55:10 web01 system-backup.sh[1767]: [2026-03-26 23:55:10] [INFO] ========== BACKUP STARTED ==========
May 26 23:55:10 web01 system-backup.sh[1767]: [2026-03-26 23:55:10] [INFO] Disk space available: 16.7GB (need 1GB)
May 26 23:55:10 web01 system-backup.sh[1767]: [2026-03-26 23:55:10] [INFO] Backing up: /etc /opt/myapp
May 26 23:55:10 web01 system-backup.sh[1767]: [2026-03-26 23:55:10] [INFO] Destination: /backup/backup_web01_2026-03-26_23-55-10.tar.xz
May 26 23:55:12 web01 system-backup.sh[1767]: [2026-03-26 23:55:12] [INFO] Backup complete: /backup/backup_web01_2026-03-26_23-55-10.tar.xz (2.5M)
May 26 23:55:12 web01 system-backup.sh[1767]: [2026-03-26 23:55:12] [INFO] No backups older than 7 days to remove
May 26 23:55:12 web01 system-backup.sh[1767]: [2026-03-26 23:55:12] [INFO] Total runtime: 2s
May 26 23:55:12 web01 system-backup.sh[1767]: [2026-03-26 23:55:12] [INFO] ========== BACKUP FINISHED ==========
May 26 23:55:12 web01 systemd[1]: Finished system-backup.service - System directory backup.

Для мониторинга вы также можете настроить оповещения по электронной почте или Slack о сбое. Простой подход заключается в добавлении email в раздел [Unit] файла службы с соответствующим блоком службы уведомлений.

Завершение

Сценарий обрабатывает ежедневное, еженедельное и ежемесячное вращение с использованием жестких ссылок для эффективности пространства с ограничением пропускной способности и встроенным структурированным журналированием. Систематизированный таймер заменяет cron лучшей интеграцией журналов, управлением ресурсами через Nice и IOSchedulingClass и Persistent=true, чтобы наверстать упущенное после перезагрузки или простоя.

Эти скрипты могут показаться простыми на первый взгляд, но они выполняют критически важные задачи, которые поддерживают стабильность, безопасность и бесперебойную работу серверов.

Вы также можете поделиться статьей со своими друзьями в социальных сетях, которым может быть интересна эта статья, или просто оставить комментарий ниже. Спасибо.