В этой заметке мы с вами напишем универсальный бэкап-скрипт на Bash, который с помощью утилиты tar будет создавать архивы с файлами локального или удалённых хостов, выполнять ротацию старых бэкапов, шифровать их на лету, а также контролировать дисковое пространство 😌.
🖐️Эй!
Подписывайтесь на наш телеграм @r4ven_me📱, чтобы не пропустить новые публикации на сайте😉. А если есть вопросы или желание пообщаться по тематике - заглядывайте в Вороний чат @r4ven_me_chat🧐. Также в блоге теперь доступно соавторство 🐧🐧🐧.
Вводные данные
Перед тем как запустить скрипт, убедитесь, что в вашей системе установлены следующие необходимые утилиты:
- tar - почти всегда установлен;
- flock - для блокировки одновременных запусков (обычно входит в util-linux);
- Утилита сжатия (опционально): zstd, gzip/pigz, bzip2/pbzip2, xz - на выбор;
- gpg (опционально) - если будем шифровать архивы;
- ssh (опционально) - если будем бэкапить файлы с удалённых машин.
При необходимости выполните их установку (пример для Debian/Ubuntu):
sudo apt update
sudo apt install -y tar util-linux gpg openssh-clientПримеры из статьи выполнялись в среде дистрибутива Linux Mint 22 (Ubuntu 24.04), но скрипт полностью портативен.
Что под капотом скрипта
Логика скрипта сводится к тому, чтобы сформировать подходящую pipeline-команду: tar локально или tar по SSH на удалённой машине → утилита сжатия → утилита шифрования gpg → файл на диск.
Ключевые моменты:
- Переменные конфига вверху - всё, что нужно менять, находится в первых строках, дальше уже логика;
- Блокировка через flock - если запустите скрипт дважды на одни и те же данные, второй запуск не начнётся, пока первый не закончится;
- Бэкап по SSH работает “локально” -
tarзапускается на удалённой машине и перенаправляет поток в локальный pipe, это позволяет не нагружать CPU удалённого хоста, но может повлиять на сеть (в зависимости от объема данных); - Автоматическая ротация - старые бэкапы удаляются по возрасту (за
REMOVE_OLDдней), или сохраняются по количеству (последние N weekly/monthly); - Лейблы в именах файлов - можно добавлять суффиксы вроде
_keep,_weekly,_monthlyдля разной ротации; - Hash файл - при необходимости рядом с архивом можно сохранить его хеш
.sha256для будущей проверки целостности.
Переходим непосредственно к скрипту.
Скрипт

Создаём файл в любом удобном месте, например, с помощью vim:
vim ~/.local/bin/tar.shИ наполняем его следующим содержимым:
#!/usr/bin/env bash
set -Eeuo pipefail
# ==== EDIT THESE ====
SOURCE_DIR="/etc"
SOURCE_FILE=() # specific file(s)/dir(s) inside SOURCE_DIR; empty = everything
# e.g. SOURCE_FILE=("passwd" "My Documents")
OUTPUT_DIR="/var/backups/etc"
EXTRA_PARAMS=() # extra tar flags, e.g. EXTRA_PARAMS=("--exclude=*.tmp")
COMPRESS_FORMAT="zstd" # gzip|pigz|bzip2|pbzip2|xz|zstd|"" (empty = no compression)
COMPRESS_LEVEL="15"
ENCRYPT_PASSWORD="" # empty = no encryption
FILENAME="" # empty = auto-name from OUTPUT_DIR
LABEL_KEEP="0" # mutually exclusive with LABEL_WEEKLY/LABEL_MONTHLY
LABEL_WEEKLY="0"
LABEL_MONTHLY="0"
REMOVE_OLD="" # days; empty = no retention
KEEP="0" # 1 = when removing old backups, keep ones with 'keep' in the name
KEEP_WEEKLY="" # keep the N most recent 'weekly'-labeled backups
KEEP_MONTHLY="" # keep the N most recent 'monthly'-labeled backups
HASH_FILE="1" # 1 = also write a .sha256 next to the archive
# SSH: leave SSH_HOST empty for a local backup. When set, tar runs on the
# remote host and streams the archive back (pull, not push).
SSH_HOST=""
SSH_USER="$(id -un)"
SSH_PORT="22"
SSH_KEY="$HOME/.ssh/id_ed25519"
SSH_EXTRA_PARAMS=() # e.g. SSH_EXTRA_PARAMS=("-o" "ProxyJump=bastion")
# =====================
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" &> /dev/null && pwd -P)"
SCRIPT_PID="$$"
if [[ -t 2 && -z "${NO_COLOR:-}" && "${TERM:-}" != "dumb" ]]; then
LOG_GREEN=$'\033[32m'; LOG_RED=$'\033[31m'; LOG_RESET=$'\033[0m'
else
LOG_GREEN=""; LOG_RED=""; LOG_RESET=""
fi
log() {
local msg="$*" color=""
[[ "$msg" == ERROR:* ]] && color="$LOG_RED"
printf '%s[%s]%s %s%s%s\n' "$LOG_GREEN" "$(date '+%Y-%m-%d %H:%M:%S')" "$LOG_RESET" "$color" "$msg" "$LOG_RESET" >&2
}
# --- preflight (mirrors lib/checks.sh's check_utils/check_disk_space) ---
check_utils() {
local util
for util in "$@"; do
command -v "$util" &> /dev/null || { log "ERROR: required utility not found: $util"; exit 1; }
done
}
check_disk_space() {
local dir="$1" pct
pct=$(df "$dir" --output=pcent | tail -n 1 | tr -d ' %')
if (( pct > 95 )); then
log "ERROR: more than 95% of disk space used in $dir (${pct}%)"
exit 1
fi
return 0
}
# --- SSH (mirrors lib/ssh.sh: accept-new against a local known_hosts, and
# ssh_remote_exec's per-token %q quoting -- not a string built for eval) ---
ssh_build_argv() {
SSH_ARGV=("ssh" "-q" "-o" "StrictHostKeyChecking=accept-new" \
"-o" "UserKnownHostsFile=${SCRIPT_DIR}/.known_hosts_standalone")
SSH_ARGV+=("${SSH_EXTRA_PARAMS[@]}")
SSH_ARGV+=("-l" "$SSH_USER" "-p" "$SSH_PORT" "-i" "$SSH_KEY" "$SSH_HOST")
}
ssh_remote_exec() {
local -n remote_argv="$1"
ssh_build_argv
local quoted=() tok
for tok in "${remote_argv[@]}"; do
quoted+=("$(printf '%q' "$tok")")
done
"${SSH_ARGV[@]}" "${quoted[@]}"
}
# --- retention (mirrors lib/retention.sh's remove_old_backup) ---
remove_old_backups() {
local output_dir="$1" remove_old="$2" keep="$3" keep_weekly="$4" keep_monthly="$5" kind="${6:-file}"
local valid_extensions=(".tar" ".sql" ".gz" ".bz2" ".xz" ".zst" ".gpg")
local now weekly_entries=() monthly_entries=()
now=$(date +%s)
local entry_path
for entry_path in "$output_dir"/*; do
local entry_name
entry_name=$(basename "$entry_path")
if [[ "$kind" == "dir" ]]; then
[[ -d "$entry_path" && ! -L "$entry_path" ]] || continue
else
[[ -f "$entry_path" ]] || continue
local matched=0 ext
for ext in "${valid_extensions[@]}"; do
[[ "$entry_name" == *"$ext" ]] && matched=1
done
(( matched )) || continue
fi
(( keep )) && [[ "$entry_name" == *keep* ]] && continue
if (( keep_weekly > 0 )) && [[ "$entry_name" == *weekly* ]]; then
weekly_entries+=("$entry_path")
continue
fi
if (( keep_monthly > 0 )) && [[ "$entry_name" == *monthly* ]]; then
monthly_entries+=("$entry_path")
continue
fi
local entry_last_mod
entry_last_mod=$(stat -c %Y "$entry_path")
if (( now - entry_last_mod > remove_old * 86400 )); then
log "Deleting old backup: $entry_path"
rm -rf "$entry_path" "${entry_path}.sha256"
fi
done
clean_old_generations() {
local n="$1" label="$2"
shift 2
local entries=("$@")
(( ${#entries[@]} <= n )) && return 0
local sorted=() e
mapfile -t sorted < <(
for e in "${entries[@]}"; do
printf '%s\t%s\n' "$(stat -c %Y "$e")" "$e"
done | sort -nr -k1,1 | cut -f2-
)
local old
for old in "${sorted[@]:$n}"; do
log "Deleting old $label backup: $old"
rm -rf "$old" "${old}.sha256"
done
return 0
}
(( keep_weekly > 0 )) && clean_old_generations "$keep_weekly" "weekly" "${weekly_entries[@]}"
(( keep_monthly > 0 )) && clean_old_generations "$keep_monthly" "monthly" "${monthly_entries[@]}"
return 0
}
[[ -n "$SSH_HOST" || -d "$SOURCE_DIR" ]] || { log "ERROR: source dir not found: $SOURCE_DIR"; exit 1; }
required_utils=("tar" "flock")
[[ -n "$COMPRESS_FORMAT" ]] && required_utils+=("$COMPRESS_FORMAT")
[[ -n "$ENCRYPT_PASSWORD" ]] && required_utils+=("gpg")
[[ -n "$SSH_HOST" ]] && required_utils+=("ssh")
check_utils "${required_utils[@]}"
labels=0
[[ "$LABEL_KEEP" == "1" ]] && labels=$((labels + 1))
[[ "$LABEL_WEEKLY" == "1" ]] && labels=$((labels + 1))
[[ "$LABEL_MONTHLY" == "1" ]] && labels=$((labels + 1))
(( labels > 1 )) && { log "ERROR: only one label allowed"; exit 1; }
mkdir -p "$OUTPUT_DIR"
[[ -z "$FILENAME" ]] && FILENAME="backup_tar_$(basename "$(realpath -m "$OUTPUT_DIR")")"
LOCK_FILE="${SCRIPT_DIR}/.${FILENAME}.lock"
exec {lock_fd}>> "$LOCK_FILE"
flock -n "$lock_fd" || { log "ERROR: another backup for this item is already running"; exit 1; }
echo "$SCRIPT_PID" > "$LOCK_FILE"
release_lock() {
if [[ -f "$LOCK_FILE" ]] && [[ "$(< "$LOCK_FILE")" == "$SCRIPT_PID" ]]; then
rm -f "$LOCK_FILE"
fi
}
trap release_lock EXIT
[[ "$LABEL_KEEP" == "1" ]] && FILENAME="${FILENAME}_keep"
[[ "$LABEL_WEEKLY" == "1" ]] && FILENAME="${FILENAME}_weekly"
[[ "$LABEL_MONTHLY" == "1" ]] && FILENAME="${FILENAME}_monthly"
FILENAME="${FILENAME}_$(date '+%F_%H-%M-%S').tar"
if [[ -n "$REMOVE_OLD" ]]; then
remove_old_backups "$OUTPUT_DIR" "$REMOVE_OLD" "$KEEP" "${KEEP_WEEKLY:-0}" "${KEEP_MONTHLY:-0}" file
fi
check_disk_space "$OUTPUT_DIR"
case "$COMPRESS_FORMAT" in
gzip|pigz) FILENAME="${FILENAME}.gz" ;;
bzip2|pbzip2) FILENAME="${FILENAME}.bz2" ;;
xz) FILENAME="${FILENAME}.xz" ;;
zstd) FILENAME="${FILENAME}.zst" ;;
"") ;;
*) log "ERROR: unsupported COMPRESS_FORMAT: $COMPRESS_FORMAT"; exit 1 ;;
esac
[[ -n "$ENCRYPT_PASSWORD" ]] && FILENAME="${FILENAME}.gpg"
RESULT_FILE="${OUTPUT_DIR}/${FILENAME}"
producer=("tar" "--create")
producer+=("${EXTRA_PARAMS[@]}")
producer+=("--file=-" "--directory=${SOURCE_DIR}")
if [[ ${#SOURCE_FILE[@]} -eq 0 ]]; then
producer+=("./")
else
producer+=("${SOURCE_FILE[@]}")
fi
run_producer() {
if [[ -n "$SSH_HOST" ]]; then
ssh_remote_exec producer
else
"${producer[@]}"
fi
}
run_compress() {
case "$COMPRESS_FORMAT" in
gzip|pigz) "$COMPRESS_FORMAT" "-$COMPRESS_LEVEL" ;;
bzip2|pbzip2) "$COMPRESS_FORMAT" "-$COMPRESS_LEVEL" ;;
xz) xz "-$COMPRESS_LEVEL" --stdout ;;
zstd) zstd "-$COMPRESS_LEVEL" -c ;;
*) cat ;;
esac
}
run_encrypt() {
if [[ -z "$ENCRYPT_PASSWORD" ]]; then
cat
return
fi
local tmpdir passfile
tmpdir="$(mktemp -d /dev/shm/.encrypt.XXXXXX)"
passfile="$tmpdir/passfile"
trap 'rm -rf "$tmpdir"' RETURN
printf "%s" "$ENCRYPT_PASSWORD" > "$passfile"
chmod 600 "$passfile"
gpg --quiet --batch --yes --symmetric --cipher-algo AES256 --homedir "$tmpdir" --passphrase-file "$passfile"
}
log "Running: ${producer[*]}$([[ -n "$SSH_HOST" ]] && echo " (on $SSH_HOST)") > $RESULT_FILE"
if run_producer | run_compress | run_encrypt > "$RESULT_FILE"; then
log "Backup created: $RESULT_FILE"
else
rm -f "$RESULT_FILE"
log "ERROR: backup failed"
exit 1
fi
if [[ "$HASH_FILE" == "1" ]]; then
sha256sum "$RESULT_FILE" > "${RESULT_FILE}.sha256"
log "Hash: ${RESULT_FILE}.sha256"
fiИзменять нужно будет только переменные между строками # ==== EDIT THESE ==== и # =====================. И то не все.
Тут не стану расписывать все блоки скрипта, при необходимости запросите пояснения у нейронок. У них это сегодня неплохо получается 😉.
Примеры использования

Ниже я покажу несколько сценариев использования нашего скрипта. Все примеры рабочие и безопасные - используют тестовые директории в /tmp/, а не реальные данные.
Подготовка тестового окружения
Сначала создадим тестовое окружение с данными:
# Создаём директории
mkdir -vp /tmp/backup-demo/{data,config}
# Генерируем тестовые файлы разного размера
dd if=/dev/zero of=/tmp/backup-demo/data/file1.bin bs=1M count=10
dd if=/dev/zero of=/tmp/backup-demo/data/file2.bin bs=1M count=5
echo "app_version=1.0" > /tmp/backup-demo/config/app.conf
echo "db_host=localhost" > /tmp/backup-demo/config/db.conf
echo "secret_token=abc123" > /tmp/backup-demo/config/.env
# Выходные директории для бэкапов
mkdir -vp /tmp/backups/{local,remote,weekly,monthly,encrypted}
# Проверяем, что все готово
ls -lh /tmp/backup-demo/
du -sh /tmp/backup-demo/*
Пример 1: Простое резервное копирование с сжатием zstd
Простой кейс - копируем директорию с данными, сжимаем архив современным алгоритмом zstd (хороший баланс скорости и степени сжатия). Получаем архив с хешем для проверки целостности. Это подходит для ежедневных бэкапов без особых требований.
В нашем скрипте корректируем следующие переменные:
#!/usr/bin/env bash
SOURCE_DIR="/tmp/backup-demo"
OUTPUT_DIR="/tmp/backups/local"
COMPRESS_FORMAT="zstd"
COMPRESS_LEVEL="15"
HASH_FILE="1"
SSH_HOST=""
Добавляем флаг исполнения скрипту и запускаем его:
chmod +x ~/.local/bin/tar.sh
~/.local/bin/tar.shПосле выполнения увидим подобный вывод в терминале:

Проверим целостность архива:
bash -c 'cd /tmp/backups/local/; sha256sum -c *.sha256'Хеши должны совпадать:

Просмотреть содержимое архива можно с помощью того же tar и ключа -t:
tar -tvf /tmp/backups/local/backup_tar_local_2026-08-20_07-38-39.tar.zst
Пример 2: Резервное копирование конкретных файлов с шифрованием GPG
Не всегда нужна вся директория целиком. Для этого наш скрипт умеет копировать отдельные файлы, например, конфиги + добавляем шифрование AES256 с помощью утилиты gpg.
Правим переменные:
#!/usr/bin/env bash
SOURCE_DIR="/tmp/backup-demo"
SOURCE_FILE=("config") # только папка config, без data/
OUTPUT_DIR="/tmp/backups/encrypted"
COMPRESS_FORMAT="zstd"
COMPRESS_LEVEL="15"
ENCRYPT_PASSWORD="i-like-r4ven-me" # в боевом коде лучше читать из переменной окружения
HASH_FILE="1"
SSH_HOST=""Стартуем:
~/.local/bin/tar.sh
Создадим директорию /tmp/restore и попробуем распаковать и расшифровать (будет запрошен пароль) наш архив в неё:
mkdir -vp /tmp/restore/
gpg --quiet --decrypt /tmp/backups/encrypted/*.tar.zst.gpg | zstd -d | tar -xvC /tmp/restore/

Пример 3: Резервное копирование с ротацией и retention политикой
Бэкапы имеют свойство накапливаться, поэтому их нужно грамотно ротировать. Настроим следующую политику хранения: архивы старше 7 дней - удаляем, если имя содержит “keep” - сохраняем всегда, остальное по сроку. Запустим скрипт несколько раз, чтобы увидеть удаление.
Корректируем переменные скрипта:
#!/usr/bin/env bash
SOURCE_DIR="/tmp/backup-demo"
OUTPUT_DIR="/tmp/backups/weekly"
COMPRESS_FORMAT="zstd"
COMPRESS_LEVEL="12"
HASH_FILE="1"
REMOVE_OLD="7" # удаляем бэкапы старше 7 дней
KEEP="1" # если в имени файла есть "keep" - не удаляем
KEEP_WEEKLY="3" # оставляем 3 последних weekly бэкапа
LABEL_WEEKLY="1" # добавляем suffix "_weekly"
SSH_HOST=""Запускаем несколько раз, чтобы создать N-е количество архивов:
for i in {1..5}; do ~/.local/bin/tar.sh; sleep 1; echo '---'; done
Видим, что при создании 5го архива, скрипт видит 4 weekly архива (а должно оставаться 3 - KEEP_WEEKLY="3") и удаляем самый старый из них. В итоге получится 4 бэкапа: 3 сохраняем + последний.
Теперь давайте отключим сохранение недельных копий (KEEP_WEEKLY="0") и дату последнего изменения одного из архивов искусственно уменьшим на 8 дней:
sed -i 's/KEEP_WEEKLY="3"/KEEP_WEEKLY="0"/' ~/.local/bin/tar.sh
touch -d '-8 days' /tmp/backups/weekly/backup_tar_weekly_2026-08-20_08-22-37.tar.zst.gpg
Запустим скрипт еще раз:
~/.local/bin/tar.sh
Видим, что копия старше 7 дней была удалена.
Теперь давайте еще один файл сделаем старше 7 дней, но добавим к существующему файлу в имя слово keep:
touch -d '-8 days' /tmp/backups/weekly/backup_tar_weekly_2026-08-20_08-45-21.tar.zst.gpg
mv -v /tmp/backups/weekly/backup_tar_weekly_{,keep_}2026-08-20_08-45-21.tar.zst.gpgПробуем выполнить бэкап:
~/.local/bin/tar.sh
Ничего не было удалено.
Но если вновь вернуть старое имя без keep и запустить ещё раз:
mv -v /tmp/backups/weekly/backup_tar_weekly_{keep_,}2026-08-20_08-45-21.tar.zst.gpg
~/.local/bin/tar.shУвидим ожидаемый результат:

Пример 4: Резервное копирование по SSH из docker контейнера
Бывает нужно скопировать данные с удаленного хоста по SSH. Чтобы не засорять и не нагружать лишний раз наши машины, используем Docker контейнер с демоном sshd внутри. Скрипт подключится к контейнеру и скопирует его файлы.
Сначала подготовим Docker окружение с помощью cat и here doc:
# Создадим простой Dockerfile с sshd
mkdir -vp /tmp/ssh-demo
cat > /tmp/ssh-demo/Dockerfile << 'EOF'
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y openssh-server tar zstd && \
mkdir -p /run/sshd && \
mkdir -p /root/.ssh && \
ssh-keygen -A
# Разрешим root логин (только для демо!)
RUN echo "PermitRootLogin yes" >> /etc/ssh/sshd_config && \
mkdir -p /root/.ssh/authorized_keys && \
chmod 700 /root/.ssh/authorized_keys
CMD ["/usr/sbin/sshd", "-D"]
EOF
# Собираем образ
docker build -t backup-demo-sshd /tmp/ssh-demo/
# Запускаем контейнер
docker run -d --name backup-demo-ssh -p 2222:22 -v /tmp/backup-demo:/data:ro backup-demo-sshd
Импортируем наш публичный ключ для пользователя root в контейнере:
docker exec backup-demo-ssh sh -c "echo $(cat ~/.ssh/id_ed25519.pub) > /root/.ssh/authorized_keys"
docker exec backup-demo-ssh chmod 600 /root/.ssh/authorized_keys💡 Совет
Если у вас еще нет клиентского SSH ключа, сгенерируйте его командой:
ssh-keygen -q -N "" -t ed25519 -f ~/.ssh/id_ed25519Теперь правим наш скрипт резервного копирования:
#!/usr/bin/env bash
SOURCE_DIR="/data" # путь на удаленной машине
SOURCE_FILE=()
OUTPUT_DIR="/tmp/backups/remote"
COMPRESS_FORMAT="zstd"
COMPRESS_LEVEL="15"
ENCRYPT_PASSWORD=""
HASH_FILE="1"
SSH_HOST="127.0.0.1" # или IP контейнера
SSH_USER="root"
SSH_PORT="2222" # порт который мы открыли
SSH_KEY="$HOME/.ssh/id_ed25519" # или любой другой ваш ключ
SSH_EXTRA_PARAMS=("-C")Запускаем бэкап:
~/.local/bin/tar.sh
Проверим результат:
ls -lh /tmp/backups/remote/
# Распакуем и проверим
mkdir -vp /tmp/restore-ssh
tar -xf /tmp/backups/remote/backup_tar_remote_2026-08-20_09-34-15.tar.zst -C /tmp/restore-ssh/
Не забываем почистить Docker:
docker stop backup-demo-ssh
docker rm backup-demo-ssh
docker rmi backup-demo-sshdПример 5: Ежемесячные и еженедельные метки с разной ротацией
В боевых системах часто нужна стратегия “держи все дневные бэкапы неделю, все недельные 3 месяца, все месячные год”. Настроим скрипт с метками и разными retention политиками.
#!/usr/bin/env bash
SOURCE_DIR="/tmp/backup-demo"
OUTPUT_DIR="/tmp/backups/monthly"
COMPRESS_FORMAT="zstd"
COMPRESS_LEVEL="12"
HASH_FILE="1"
REMOVE_OLD="1" # удаляем старше 1 дня (для демо)
KEEP_MONTHLY="3" # держим 3 последних месячных бэкапа
LABEL_MONTHLY="1" # добавляем suffix "_monthly"
SSH_HOST=""Запускаем бэкап:
~/.local/bin/tar.shТеперь симулируем несколько месячных бэкапов:
for i in {1..5}; do ~/.local/bin/tar.sh; echo '---'; sleep 1; done
ls -l /tmp/backups/monthly/*.tar.zstВ директории останется только 3 месячных бэкапа, старые будут удалены.

Пример 6: Резервное копирование с исключением файлов
При копировании больших директорий, часто нужно исключить временные файлы, логи, кэши и пр. Добавим флаги tar для фильтрации:
#!/usr/bin/env bash
SOURCE_DIR="/tmp/backup-demo"
OUTPUT_DIR="/tmp/backups/local"
COMPRESS_FORMAT="zstd"
COMPRESS_LEVEL="15"
HASH_FILE="1"
EXTRA_PARAMS=(
"--exclude=*.tmp"
"--exclude=.cache"
"--exclude=.env"
"--exclude=__pycache__"
)
SSH_HOST=""Бэкапимся:
~/.local/bin/tar.shПроверим что существующий .env файл не попал в архив:
tar -tvf /tmp/backups/local/backup_tar_local_2026-08-20_17-54-30.tar.zst
Пример 7: Резервное копирование без сжатия для максимальной скорости
Если нужна максимальная скорость, то можно отключить сжатие, получаем чистый TAR архив:
#!/usr/bin/env bash
SOURCE_DIR="/tmp/backup-demo"
OUTPUT_DIR="/tmp/backups/local"
COMPRESS_FORMAT="" # пусто - без сжатия
COMPRESS_LEVEL=""
HASH_FILE="1"
EXTRA_PARAMS=("--verbose") # видим процесс
SSH_HOST=""Запускаем скрипт:
~/.local/bin/tar.sh
Итоговый файл-архив будет почти такого же размера как исходные данные, но создаваться заметно быстрее. Может быть полезно, если хранилище уже сжимает данные на уровне файловой системы.
Очистка тестового окружения
После экспериментов удалим тестовые данные:
rm -vrf /tmp/backup-demo /tmp/backups /tmp/ssh-demo /tmp/restore*Послесловие
Скрипт получился довольно универсальным. Покрывает большинство задач для несложного бэкапа файлов/папок.
Он также имеет некоторые механизмы защиты, например, проверку доступного дискового пространства (должно быть занято не более 95%), в противном случае ошибка:
~/.local/bin/tar.sh
[2026-08-20 18:50:54] ERROR: more than 95% of disk space used in /tmp/backups/local (97%)Или предотвращение race condition: если один экземпляр скрипта еще не завершился, запуск второго с такими же параметрами не произойдёт:
~/.local/bin/tar.sh
[2026-08-20 18:53:18] ERROR: another backup for this item is already runningВ следующей статье мы рассмотрим похожий скрипт, но уже с использованием rsync. Подписывайтесь на телегу, чтобы не пропустить.
Ну а если вы ищете комплексное решение для вашей Self hosted инфраструктуры, то рекомендую рассмотреть такой великолепный инструмент, как Arkeep - сервер с GUI панелью, в основе работы которого лежат замечательные restic и rclone.
Спасибо, что читаете! Старайтесь уделять больше времени, своим бэкапам.
Полезные материалы
- SSH — Безопасное подключение к удалённым хостам: введение
- Командная строка Linux, архивирование и сжатие: команды tar, gzip, bzip2, xz, zstd и zip, 7z, rar
- Arkeep - Централизованный Backup сервер с агентами, на базе Restic и Rclone
👨💻Ну и…
Не забывайте про нашу телегу📱и чат 💬
Или может хотите стать соавтором? Тогда клик сюда🔗
Всех благ✌️
That should be it. If not, check the logs 🙂



Комментарии