Скрипт либо экономит нервы, либо создаёт тихие 3 часа ночи «почему бэкап пустой». Ниже — правила, из‑за которых автоматизация переживает обновление ноутбука и переезд на новый VPS ⚙️

Когда скрипт, а когда нет #

ЗадачаИнструмент
Раз в день rsync каталогаbash + cron/timer
Реакция на webhook / очередьнормальный сервис (systemd, docker)
Деплой с тестамиCI (Forgejo Actions), не cron «git pull»
Секретыrbw/Infisical, не TOKEN=... в скрипте

Шаблон безопасного bash #

#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'

readonly LOG_DIR="${LOG_DIR:-$HOME/var/log}"
readonly SRC="${SRC:-$HOME/data}"
readonly DST="${DST:-/Volumes/Backup/data}"

mkdir -p "$LOG_DIR"
log() { printf '%s %s\n' "$(date -Iseconds)" "$*" | tee -a "$LOG_DIR/backup.log"; }

need() { command -v "$1" >/dev/null || { log "missing: $1"; exit 127; }; }
need rsync

if [[ ! -d "$SRC" ]]; then
  log "SRC not found: $SRC"
  exit 1
fi

log "start $SRC -> $DST"
rsync -aH --delete --stats "$SRC/" "$DST/"
log "done"

Почему так:

  • set -euo pipefail — падаем на ошибках, не глотаем пустые переменные;
  • пути через env — удобно гонять тот же скрипт на другой машине;
  • лог с timestamp — иначе через месяц не вспомните, что случилось.

Идемпотентность #

Повторный запуск не должен удваивать ущерб:

# плохо: каждый раз append без границы
cat report.txt >> archive.txt

# лучше: целевое состояние
install -m 644 report.txt "$DST/report.txt"
# или rsync, который сам сводит дерево

Расписание #

Linux: systemd timer (предпочтительнее cron) #

# ~/.config/systemd/user/backup.service
[Service]
Type=oneshot
ExecStart=%h/bin/backup.sh
# ~/.config/systemd/user/backup.timer
[Timer]
OnCalendar=daily
Persistent=true
[Install]
WantedBy=timers.target
systemctl --user daemon-reload
systemctl --user enable --now backup.timer

macOS: launchd #

<!-- ~/Library/LaunchAgents/ru.evgn.backup.plist -->
<!-- ProgramArguments: /Users/you/bin/backup.sh -->
<!-- StartCalendarInterval: Hour 3 -->
launchctl load ~/Library/LaunchAgents/ru.evgn.backup.plist

cron — когда «и так сойдёт» #

15 3 * * * /home/evgeny/bin/backup.sh >>/home/evgeny/var/log/backup.cron 2>&1

Всегда абсолютные пути и явный user; cron не знает ваш интерактивный PATH.

Уведомления о провале #

Тишина — враг. Минимум:

if ! rsync …; then
  curl -sS -d "backup failed on $(hostname)" ntfy.sh/your-topic || true
  exit 1
fi

Или MAILTO в cron, или systemd OnFailure=.

Чеклист перед «поставил и забыл» #

  1. Сухой прогон вручную под тем же user, что и scheduler.
  2. Логи ротируются (logrotate / newsyslog).
  3. Секреты не в argv (ps aux их видит) — только env/файл 600.
  4. Есть алерт на отсутствие успешного лога N часов (dead man’s switch).

Автоматизация — это не «написал 20 строк». Это повторяемый результат + сигнал, когда сломалось 📡