Коли вільного місця на диску стає критично мало, система починає гальмувати та вередувати. Давайте зробимо розумну автоматизацію задач: стискати великі файли та переносити їх на інший диск чи NAS автоматично, щойно простір тане. Сьогодні показую, як зібрати це з inotify, rsync і systemd timers — просто і надійно. Так, це будуть звичайні bash скрипти, але з дорослою дисципліною 😉
Як це працює
Схема така:
- inotify стежить за папкою з великими файлами та ловить події створення/закриття файлів;
- скрипт перевіряє використання диска (скільки вільного місця лишилося);
- якщо вільного місця менше за поріг — стискає великі файли (zstd) і переносить їх rsync на інший том/диск;
- systemd timers періодично запускають перевірку, навіть якщо події непомітні.
Чому не cron? Можна, але cron та systemd timers — різні інструменти. Тут зручніше саме systemd timers: вони виживають перезавантаження, мають Persistent=true і гарно логуються в journal.
Підготовка
Поставте потрібні утиліти. Приклади для різних дистрибутивів:
# Debian/Ubuntu
sudo apt update && sudo apt install -y inotify-tools rsync zstd
# Fedora
sudo dnf install -y inotify-tools rsync zstd
# Arch
sudo pacman -S --noconfirm inotify-tools rsync zstd
Основний How-to: скрипт + systemd timers
1) Створюємо скрипт
Цей скрипт стискає великі файли з робочої папки та переносить їх на інший диск, коли вільного місця стає менше за заданий поріг. Він уміє працювати в режимі одноразового сканування (scan) і режимі спостереження (watch) через inotify.
sudo mkdir -p /usr/local/bin
sudo nano /usr/local/bin/auto-archive-move.sh
#!/usr/bin/env bash
set -euo pipefail
# Налаштування (підкоригуйте під себе)
WATCH_DIR="/data/incoming" # де з'являються великі файли
DEST_DIR="/mnt/archive" # куди переносимо архіви (інший диск/NAS)
STAGING_DIR="/var/tmp/auto-archive" # тимчасове місце для стискання
SIZE_MIN_MB=512 # мінімальний розмір файлу для обробки
MIN_FREE_GB=10 # якщо вільного місця менше або дорівнює — запускаємо
NICE=10 # пріоритет CPU
IONICE_CLASS=2 # 2=best-effort
IONICE_PRIORITY=7 # 0..7 (7 — найнижче)
LOG_FILE="/var/log/auto-archive.log"
need_cmd() { command -v "$1" >/dev/null 2>&1 || { echo "Missing: $1"; exit 1; }; }
need_cmd inotifywait; need_cmd rsync; need_cmd zstd; need_cmd df; need_cmd awk; need_cmd find; need_cmd du
log() { echo "$(date -Is) $*" | tee -a "$LOG_FILE"; }
free_gb() { df -BG --output=avail "$WATCH_DIR" | tail -1 | tr -dc '0-9'; }
big_enough() { local f="$1"; local sz_mb; sz_mb=$(du -m --apparent-size -- "$f" | cut -f1); [ "$sz_mb" -ge "$SIZE_MIN_MB" ]; }
archive_and_move() {
local src="$1"
[ -f "$src" ] || { log "Skip (not a file): $src"; return; }
local rel="${src#$WATCH_DIR/}"
local base; base="$(basename -- "$src")"
local rel_dir; rel_dir="$(dirname -- "$rel")"
local stage_dir="$STAGING_DIR/$rel_dir"
local dest_dir="$DEST_DIR/$rel_dir"
mkdir -p "$stage_dir" "$dest_dir"
local tmp="$stage_dir/${base}.zst.part"
local out="$stage_dir/${base}.zst"
log "Compressing '$src' -> '$out' (zstd)"
if nice -n "$NICE" ionice -c "$IONICE_CLASS" -n "$IONICE_PRIORITY" \
zstd -T0 -19 --force -o "$tmp" -- "$src"; then
mv -f -- "$tmp" "$out"
log "Rsync to '$dest_dir'"
rsync -a --remove-source-files -- "$out" "$dest_dir"/
rm -f -- "$src"
log "Done: archived and moved '$src'"
else
log "Error: compression failed for '$src'"; rm -f -- "$tmp" || true
fi
}
scan_once() {
local avail; avail=$(free_gb)
if [ "$avail" -le "$MIN_FREE_GB" ]; then
log "Free space ${avail}G <= ${MIN_FREE_GB}G: scanning for big files"
find "$WATCH_DIR" -type f -size +"${SIZE_MIN_MB}"M -print0 |
while IFS= read -r -d '' f; do archive_and_move "$f"; done
else
log "Free space OK (${avail}G > ${MIN_FREE_GB}G). Nothing to do."
fi
}
watch_events() {
log "Watching $WATCH_DIR for new/closed files..."
inotifywait -m -r -e close_write,create,move --format '%w%f' -- "$WATCH_DIR" |
while read -r f; do
if [ -f "$f" ] && big_enough "$f"; then
if [ "$(free_gb)" -le "$MIN_FREE_GB" ]; then
archive_and_move "$f"
else
log "Event on '$f' but free space OK — skipping for now"
fi
fi
done
}
case "${1:-scan}" in
scan) scan_once ;;
watch) watch_events ;;
*) echo "Usage: $0 [scan|watch]"; exit 1 ;;
esac
sudo chmod +x /usr/local/bin/auto-archive-move.sh
sudo touch /var/log/auto-archive.log && sudo chown root:adm /var/log/auto-archive.log || true
2) Сервіс і таймер systemd
Один сервіс виконує разове сканування, інший — постійно слухає події inotify. Таймер періодично підстраховує.
# auto-archive.service (разовий прогін)
sudo tee /etc/systemd/system/auto-archive.service >/dev/null <<'UNIT'
[Unit]
Description=Auto-archive big files when low free space
RequiresMountsFor=/data/incoming /mnt/archive
[Service]
Type=oneshot
ExecStart=/usr/local/bin/auto-archive-move.sh scan
Nice=10
IOSchedulingClass=best-effort
IOSchedulingPriority=7
[Install]
WantedBy=multi-user.target
UNIT
# auto-archive.timer (кожні 10 хвилин)
sudo tee /etc/systemd/system/auto-archive.timer >/dev/null <<'UNIT'
[Unit]
Description=Run auto-archive scan every 10 minutes
[Timer]
OnBootSec=5min
OnUnitActiveSec=10min
Persistent=true
Unit=auto-archive.service
[Install]
WantedBy=timers.target
UNIT
# auto-archive-watch.service (постійний моніторинг)
sudo tee /etc/systemd/system/auto-archive-watch.service >/dev/null <<'UNIT'
[Unit]
Description=Watch for new large files and archive on low free space
RequiresMountsFor=/data/incoming /mnt/archive
After=network-online.target
[Service]
Type=simple
ExecStart=/usr/local/bin/auto-archive-move.sh watch
Restart=always
RestartSec=5s
[Install]
WantedBy=multi-user.target
UNIT
sudo systemctl daemon-reload
sudo systemctl enable --now auto-archive.timer
sudo systemctl enable --now auto-archive-watch.service
# Перевірка
systemctl list-timers | grep auto-archive
journalctl -u auto-archive.service -u auto-archive-watch.service -f
Альтернативні способи
- systemd.path замість inotify-tools: менш гнучко за подіями, але без додаткових пакетів.
# auto-archive.path – тригерить сервіс при змінах у каталозі
sudo tee /etc/systemd/system/auto-archive.path >/dev/null <<'UNIT'
[Unit]
Description=Trigger auto-archive on changes
[Path]
PathChanged=/data/incoming
Unit=auto-archive.service
[Install]
WantedBy=multi-user.target
UNIT
sudo systemctl enable --now auto-archive.path
- Швидке стискання: замініть zstd на lz4 для максимальної швидкості (менший коефіцієнт стиснення).
- Якщо архів і призначення на одному файловому системному томі — можна обійтися без rsync та використовувати mv, але rsync надійніший (перевірка й перезапуск).
- Для директорій — замініть рядок зі zstd на tar з компресією: tar -I "zstd -T0 -19" -cf file.tar.zst dir/
GUI-спосіб (коли хочете мишкою)
Спробуйте Back In Time (Qt/GTK). Це GUI-утиліта для інкрементних копій, яка вміє за розкладом копіювати файли на інший диск. Налаштуйте правило на вашу папку з великими файлами та винятки.
# Debian/Ubuntu
sudo apt install -y backintime-qt
# Fedora
sudo dnf install -y backintime-qt
# Arch
sudo pacman -S --noconfirm backintime
- Запустіть Back In Time, додайте каталог-джерело (наприклад, /data/incoming) і ціль (зовнішній диск).
- У розкладі вкажіть періодичність (наприклад, кожні 10–15 хвилин).
- Додайте виключення для дрібних файлів (патерн за розміром або за розширеннями).
Це не чистий inotify, але результат дуже схожий і зручно для тих, хто боїться терміналу Linux.
FAQ
Як змінити поріг і розмір файлів?
Угорі скрипта є змінні MIN_FREE_GB та SIZE_MIN_MB. Підставте свої значення й перезапустіть сервіси.
inotifywait: command not found
Встановіть пакет inotify-tools (див. розділ Підготовка) і переконайтеся, що він є в PATH.
Стискання заповнює /var/tmp — що робити?
Змініть STAGING_DIR на розділ із запасом місця (наприклад, на той самий диск, де DEST_DIR), або зменште рівень стиснення.
Як виключити розширення, які вже стискати безглуздо (mp4, jpg)?
Додайте у scan_once фільтр find, наприклад: -not -iname "*.mp4" -not -iname "*.jpg". Або перевіряйте у функції big_enough ще й розширення.
Отримую Permission denied на цільовому диску
Перевірте права доступу (права доступу Linux) і власника DEST_DIR: sudo chown -R root:root /mnt/archive та належні маски дозволів.
Як подивитися швидко використання диска?
Команди: df -h (простір на розділах), du -sh /data/incoming/* (найбільші підкаталоги). Це допоможе налаштувати пороги.
Обмеження inotify: забагато директорій
Підніміть ліміт спостерігачів: echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p
Rsync на віддалений сервер по SSH?
Задайте DEST_DIR як user@host:/path і додайте ключі SSH. Не забудьте про мережеву надійність та After=network-online.target.
Порада від Kernelka
Щоб не ловити сюрпризів, додайте умову RequiresMountsFor у сервіс (ми вже додали) і тримайте DEST_DIR на окремому фізичному диску. Так ви збережете продуктивність і нерви 🧠
Підсумок
- Налаштували спостереження inotify та періодичний запуск через systemd timers.
- Створили надійні bash скрипти для стиснення великих файлів і перенесення через rsync.
- Врахували використання диска та пороги для автообробки.
- Розглянули альтернативи (systemd.path, інші компресори) і навіть GUI-вариант.
- Тепер ваш Linux сам піклується про вільне місце — красиво й без паніки ✨

Прокоментувати
На сайті відображається лише твоє ім'я та коментар. Електронна пошта зберігається виключно для зв'язку з тобою за потреби та в жодному разі не передається стороннім особам.