# Бэкап селфхоста с Raspberry Pi в стиле мейнфреймов (COBOL-стайл)
=> /en/immich_backup.gmi 🇬🇧 Читать по Английски

Я используют Raspberry Pi с родной ОС как домашний сервер для селфхоста. В моем случае там, помимо прочих контейнеров Docker, крутится Immich — отличная замена облачным галереям. Но когда база фотографий переваливает за пару десятков гигабайт, встает вопрос бэкапов.

Основная рабочая машина у меня на Fedora, и долгое время задача решалась простым перекидыванием файлов. Но у классического бэкапа через tar по SSH есть две проблемы:

* Это черный ящик. И если запускаешь копирование 25..50..100 ГБ вручную, то смотришь в висящий терминал. Непонятно, качается ли архив, с какой скоростью и не отвалилось ли соединение.
* Бэкап «на горячую». Копировать файлы базы данных PostgreSQL, пока работают контейнеры — лотерея. Рано или поздно такой бэкап окажется битым.

Чтобы решить это красиво и надежно, я написала bash-скрипт. Но вместо скучного вывода добавила в него эстетику старых мейнфреймов, перфокарт и языка COBOL. Никаких эмодзи — только хардкорный капс, строгий хронометраж (STEP), наборы данных (DATA SET) и коды завершения (CONDITION CODE).

## Что под капотом?

Скрипт полностью автоматизирует процесс и работает по следующему алгоритму:

* Безопасная остановка: Скрипт по SSH мягко тушит Docker-контейнеры на малинке. Если остановка падает с ошибкой, он делает Rollback (запускает их обратно) и отменяет бэкап ABEND. Вывод докера перехватывается, чистится от ANSI-цветов и переводится в ВЕРХНИЙ РЕГИСТР для сохранения стиля.
* Предварительная оценка размера (Estimate): Перед запаковкой скрипт запрашивает у RPi точный размер папки в байтах через du -sb.
* Прогресс-бар и ETA: Полученный размер скармливается утилите pv (Pipe Viewer) на принимающей стороне (Fedora). Теперь в терминале работает полноценный прогресс-бар с расчетным временем до завершения джоба.
* Ротация: Скрипт оставляет только 4 последних архива (хранит историю за месяц), удаляя старые "наборы данных". Сохраняется всё ровно в ту же папку, откуда был вызван сам скрипт.

## Тот самый код (BASH BATCH PROCESSING)

Запускается скрипт просто: ./immich_backup.sh. Пароль от sudo на малинке запрашиваться не должен (нужно настроить беспарольный sudo для пользователя, хотя он вроде и так из коробки настроен).

```bash
#!/bin/bash

# Настройки
REMOTE_ALIAS="rpi5"
NODE_NAME="${REMOTE_ALIAS^^}"
SOURCE_PARENT="/home/rachel"
SOURCE_NAME="immich"
TARGET_DIR="$(dirname "$(realpath "$0")")"
FILENAME="immich_week_$(date +%V).tar"
TARGET_PATH="$TARGET_DIR/$FILENAME"

log_msg() {
    echo "$(date '+%Y/%m/%d %H:%M:%S') $1"
}

echo "************************************************************************"
echo "* PROGRAM-ID.  IMMICH-BACKUP.                                          *"
echo "* AUTHOR.      SYSTEM ADMIN.                                           *"
echo "* ENVIRONMENT. BASH BATCH PROCESSING.                                  *"
echo "************************************************************************"
echo "IDENTIFICATION DIVISION."
echo "PROCEDURE DIVISION."

log_msg "INFO  - RUN JOB IMMICH-BKP ON NODE $NODE_NAME"

log_msg "STEP 01 - ALLOCATE TARGET DATA SET $TARGET_DIR"
mkdir -p "$TARGET_DIR"

log_msg "STEP 02 - HALT REMOTE DOCKER CONTAINERS"
ssh $REMOTE_ALIAS "cd $SOURCE_PARENT/$SOURCE_NAME && sudo docker compose stop" 2>&1 | while IFS= read -r line; do
    clean_line=$(echo "$line" | sed -r 's/\x1B\[[0-9;]*[a-zA-Z]//g')
    log_msg "DOCKER: ${clean_line^^}"
done

if [ ${PIPESTATUS[0]} -ne 0 ]; then
    log_msg "ABEND - RETURN CODE NOT 00 ON DOCKER STOP"
    log_msg "STEP 02A - ATTEMPT ROLLBACK (RESTART CONTAINERS)"
    ssh $REMOTE_ALIAS "cd $SOURCE_PARENT/$SOURCE_NAME && sudo docker compose start" >/dev/null 2>&1
    log_msg "JOB TERMINATED DUE TO ABEND"
    exit 1
fi
log_msg "STEP 02 - CONDITION CODE 0000"

log_msg "STEP 03 - ESTIMATE REMOTE DATA SET SIZE"
ESTIMATED_SIZE=$(ssh $REMOTE_ALIAS "sudo du -sb $SOURCE_PARENT/$SOURCE_NAME" | cut -f1)

if [[ -n "$ESTIMATED_SIZE" && "$ESTIMATED_SIZE" =~ ^[0-9]+$ ]]; then
    log_msg "INFO  - ESTIMATED SIZE: $ESTIMATED_SIZE BYTES"
    log_msg "STEP 03 - CONDITION CODE 0000"
else
    log_msg "ABEND - FAILED TO ESTIMATE DATA SET SIZE"
    ssh $REMOTE_ALIAS "cd $SOURCE_PARENT/$SOURCE_NAME && sudo docker compose start" >/dev/null 2>&1
    exit 1
fi

log_msg "STEP 04 - EXECUTE REMOTE ARCHIVE AND DATA TRANSFER"
ssh $REMOTE_ALIAS "sudo tar -cf - -C $SOURCE_PARENT $SOURCE_NAME" | pv -ptrbe -s "$ESTIMATED_SIZE" > "$TARGET_PATH"

if [ ${PIPESTATUS[0]} -eq 0 ]; then
    log_msg "STEP 04 - CONDITION CODE 0000, DATA WRITTEN TO $TARGET_PATH"
else
    log_msg "ABEND - DATA TRANSFER FAILED"
    ssh $REMOTE_ALIAS "cd $SOURCE_PARENT/$SOURCE_NAME && sudo docker compose start" >/dev/null 2>&1
    exit 1
fi

log_msg "STEP 05 - COMPUTE DATA SET ALLOCATION SIZE"
if [ -f "$TARGET_PATH" ]; then
    FILE_SIZE=$(stat -c %s "$TARGET_PATH")
    log_msg "INFO  - DATA SET SIZE: $FILE_SIZE BYTES"
    log_msg "STEP 05 - CONDITION CODE 0000"
else
    log_msg "ABEND - DATA SET NOT FOUND"
    exit 1
fi

log_msg "STEP 06 - RESTART REMOTE DOCKER CONTAINERS"
ssh $REMOTE_ALIAS "cd $SOURCE_PARENT/$SOURCE_NAME && sudo docker compose start" 2>&1 | while IFS= read -r line; do
    clean_line=$(echo "$line" | sed -r 's/\x1B\[[0-9;]*[a-zA-Z]//g')
    log_msg "DOCKER: ${clean_line^^}"
done
log_msg "STEP 06 - CONDITION CODE 0000"

log_msg "STEP 07 - EXECUTE ROTATION PROCEDURE (KEEP 4 GENERATIONS)"
cd "$TARGET_DIR" || exit
ls -t immich_week_*.tar 2>/dev/null | tail -n +5 | xargs -I {} bash -c "echo \"\$(date '+%Y/%m/%d %H:%M:%S') INFO  - SCRATCH OLD DATA SET: {}\" | tr '[:lower:]' '[:upper:]' && rm -- \"{}\""
log_msg "STEP 07 - CONDITION CODE 0000"

echo "************************************************************************"
echo "* END OF JOB IMMICH-BKP. MAXIMUM CONDITION CODE 0000                   *"
echo "************************************************************************"
```

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

Полезные ресурсы и инструменты:

=> https://immich.app/ Официальный сайт проекта Immich
=> https://docs.docker.com/compose/ Документация по Docker Compose
=> https://man7.org/linux/man-pages/man1/pv.1.html Мануал по утилите pv (Pipe Viewer)
=> https://www.raspberrypi.com/ Официальный портал Raspberry Pi

---
=> /raspberry/index.gmi Назад к разделу Raspberry 
=> / Назад на Главную страницу