90 lines
4.4 KiB
Bash
90 lines
4.4 KiB
Bash
#!/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
|