Files
ForbiddenStarsApp/scripts/fs-backup.sh
T

90 lines
4.4 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# Бэкапы Forbidden Stars с ПК (Linux / macOS / Git Bash). Команды уходят в контейнер backup
# прода на Pi по SSH. На Windows удобнее scripts/fs-backup.ps1 — поведение то же.
# Инструкция: deploy/backup/README.md
#
# scripts/fs-backup.sh status состояние бэкапов на Pi
# scripts/fs-backup.sh list [vps] хронология снимков
# scripts/fs-backup.sh now [--tag before-update] сделать снимок сейчас
# scripts/fs-backup.sh verify проверить целостность данных
# scripts/fs-backup.sh pull [<id>|latest] [--repo vps]
# скачать снимок в backups/ (сверка sha256)
#
# Настройки — из корневого .env (переменная окружения с тем же именем важнее):
# BACKUP_PI_SSH как зайти на Pi по SSH, например pi@192.168.1.10 (или Host из ~/.ssh/config)
# BACKUP_PI_DIR папка на Pi с docker-compose.yml и .env (по умолчанию ~/forbidden-stars)
set -euo pipefail
PROJECT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
die() { echo "ОШИБКА: $*" >&2; exit 1; }
setting() { # setting <ключ> <по умолчанию>: окружение, затем .env (последнее присваивание)
local val="${!1:-}"
if [ -z "$val" ] && [ -f "$PROJECT_DIR/.env" ]; then
val="$(grep -E "^\s*$1\s*=" "$PROJECT_DIR/.env" | tail -n 1 | cut -d= -f2- | sed -e 's/\s*#.*$//' -e 's/^\s*//' -e 's/\s*$//' | tr -d '\r"')"
fi
printf '%s' "${val:-$2}"
}
PI_SSH="$(setting BACKUP_PI_SSH "")"
# shellcheck disable=SC2088 # тильда намеренно не раскрывается здесь — её раскроет shell на Pi
PI_DIR="$(setting BACKUP_PI_DIR "~/forbidden-stars")"
sha256() { if command -v sha256sum >/dev/null 2>&1; then sha256sum "$1" | cut -d' ' -f1; else shasum -a 256 "$1" | cut -d' ' -f1; fi; }
pi() { # pi <shell-команда>: выполнить на Pi в папке прода
[ -n "$PI_SSH" ] || die "задайте BACKUP_PI_SSH в .env (как вы заходите на Pi, например pi@192.168.1.10) — README, шаг 6."
ssh -o ConnectTimeout=15 "$PI_SSH" "cd $PI_DIR && $1"
}
fs() { # fs <аргументы fs-backup…>: выполнить fs-backup в контейнере backup на Pi
pi "docker compose exec -T backup fs-backup $*"
}
cmd_pull() {
local snap=latest repo=local
while [ $# -gt 0 ]; do
case "$1" in
--repo) repo="${2:?--repo требует значение}"; shift ;;
*) snap="$1" ;;
esac
shift
done
mkdir -p "$PROJECT_DIR/backups"
local info id stamp local_file partial expected actual
info="$(fs info "$snap" --repo "$repo" | tail -n 1)"
id="$(printf '%s' "$info" | awk '{ print $1 }')"
stamp="$(printf '%s' "$info" | awk '{ print $2 }')"
[ -n "$id" ] && [ -n "$stamp" ] || die "неожиданный ответ fs-backup info: '$info'"
local_file="$PROJECT_DIR/backups/fs_${stamp}_${id}.tar"
if [ -f "$local_file" ]; then echo "Уже скачан: $local_file"; return 0; fi
partial="$local_file.part"
echo "Снимок $id ($repo) -> $local_file"
local remote="fs-export-$id.tar"
expected="$(pi "docker compose exec -T backup fs-backup export $id --repo $repo > ~/$remote && sha256sum ~/$remote" | tail -n 1 | cut -d' ' -f1)"
scp -o ConnectTimeout=15 "$PI_SSH:$remote" "$partial" || { pi "rm -f ~/$remote"; die "scp не удался"; }
pi "rm -f ~/$remote"
actual="$(sha256 "$partial")"
if [ "$expected" != "$actual" ]; then
rm -f "$partial"
die "контрольная сумма не совпала (ожидалась $expected, получена $actual) — файл удалён, повторите pull."
fi
mv "$partial" "$local_file"
tar -tf "$local_file" | grep -qx 'forbidden_stars.db' || die "в архиве нет forbidden_stars.db: $local_file"
echo "OK: $local_file ($(du -h "$local_file" | cut -f1), $(tar -tf "$local_file" | grep -vc '/$') файлов, sha256 сверена)"
}
cmd="${1:-help}"
[ $# -eq 0 ] || shift
case "$cmd" in
status) fs status ;;
list) fs list "${1:-local}" ;;
now) fs run "$@" ;;
verify) fs verify ;;
pull) cmd_pull "$@" ;;
*) sed -n '2,15p' "$0" | sed 's/^# \{0,1\}//' ;;
esac