Refactor README, architecture, and backup documentation to emphasize the use of Vaultwarden for credential management across various services. Update scripts for Nextcloud, Gitea, Paperless, and others to reference Vaultwarden for sensitive information. Remove outdated references to previous backup strategies and ensure clarity on credential retrieval processes. This improves security practices and streamlines backup operations.
111 lines
5.4 KiB
Bash
111 lines
5.4 KiB
Bash
#!/bin/bash
|
||
# Выгрузка /mnt/backup в Yandex Object Storage (S3) через restic (без каталога photos).
|
||
# Фото бэкапятся отдельно: backup-restic-yandex-photos.sh.
|
||
# Запускать на хосте Proxmox под root.
|
||
# Секреты: из Vaultwarden (объект RESTIC). Требуется файл с мастер-паролем: /root/.bw-master (chmod 600).
|
||
# Перед первым запуском: установить restic, bw (Bitwarden CLI), jq; bw config server https://vault.katykhin.ru; restic init.
|
||
# Cron: 0 4 * * * (04:00, после окна 01:00–03:30; 05:00 зарезервировано под перезагрузку).
|
||
set -e
|
||
|
||
BACKUP_PATH="/mnt/backup"
|
||
# Время запуска (для логов и уведомлений)
|
||
START_TS=$(date +%s)
|
||
START_HUMAN=$(date '+%Y-%m-%d %H:%M:%S')
|
||
# Исключаем служебные каталоги и photos (фото — отдельный бэкап)
|
||
EXCLUDE_OPTS=(--exclude="$BACKUP_PATH/lost+found" --exclude="$BACKUP_PATH/photos")
|
||
|
||
if [ "$(id -u)" -ne 0 ]; then
|
||
echo "Запускайте под root."
|
||
exit 1
|
||
fi
|
||
|
||
# Секреты из Vaultwarden (объект RESTIC)
|
||
BW_MASTER_PASSWORD_FILE="${BW_MASTER_PASSWORD_FILE:-/root/.bw-master}"
|
||
if [ ! -f "$BW_MASTER_PASSWORD_FILE" ]; then
|
||
echo "Нет файла с мастер-паролем Vaultwarden: $BW_MASTER_PASSWORD_FILE (chmod 600). См. docs/vaultwarden-secrets.md"
|
||
exit 1
|
||
fi
|
||
if ! command -v bw >/dev/null 2>&1 || ! command -v jq >/dev/null 2>&1; then
|
||
echo "Установите bw (Bitwarden CLI) и jq для получения секретов из Vaultwarden."
|
||
exit 1
|
||
fi
|
||
export BW_SESSION
|
||
BW_SESSION=$(bw unlock --passwordfile "$BW_MASTER_PASSWORD_FILE" --raw 2>/dev/null) || { echo "Не удалось разблокировать Vaultwarden. Проверьте мастер-пароль и доступ к vault.katykhin.ru."; exit 1; }
|
||
RESTIC_ITEM=$(bw get item "RESTIC" 2>/dev/null) || { echo "Не найден объект RESTIC в Vaultwarden."; exit 1; }
|
||
export RESTIC_REPOSITORY
|
||
RESTIC_REPOSITORY=$(echo "$RESTIC_ITEM" | jq -r '.fields[] | select(.name=="RESTIC_REPOSITORY") | .value')
|
||
export AWS_ACCESS_KEY_ID
|
||
AWS_ACCESS_KEY_ID=$(echo "$RESTIC_ITEM" | jq -r '.fields[] | select(.name=="AWS_ACCESS_KEY_ID") | .value')
|
||
export AWS_SECRET_ACCESS_KEY
|
||
AWS_SECRET_ACCESS_KEY=$(echo "$RESTIC_ITEM" | jq -r '.fields[] | select(.name=="AWS_SECRET_ACCESS_KEY") | .value')
|
||
export AWS_DEFAULT_REGION
|
||
AWS_DEFAULT_REGION=$(echo "$RESTIC_ITEM" | jq -r '.fields[] | select(.name=="AWS_DEFAULT_REGION") | .value')
|
||
[ -z "$AWS_DEFAULT_REGION" ] && AWS_DEFAULT_REGION="ru-central1"
|
||
RESTIC_PASS=$(echo "$RESTIC_ITEM" | jq -r '.fields[] | select(.name=="RESTIC_BACKUP_KEY") | .value')
|
||
for var in RESTIC_REPOSITORY AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY; do
|
||
if [ -z "${!var}" ]; then
|
||
echo "В Vaultwarden (RESTIC) не задано поле для $var"
|
||
exit 1
|
||
fi
|
||
done
|
||
RESTIC_PASSWORD_FILE=$(mktemp -u)
|
||
echo -n "$RESTIC_PASS" > "$RESTIC_PASSWORD_FILE"
|
||
chmod 600 "$RESTIC_PASSWORD_FILE"
|
||
trap 'rm -f "$RESTIC_PASSWORD_FILE"' EXIT INT TERM
|
||
export RESTIC_PASSWORD_FILE
|
||
|
||
if ! command -v restic >/dev/null 2>&1; then
|
||
echo "restic не установлен. Установите: apt install restic."
|
||
exit 1
|
||
fi
|
||
|
||
echo "Restic backup: $BACKUP_PATH (excl. photos) -> $RESTIC_REPOSITORY"
|
||
# Показываем прогресс restic (без --quiet), чтобы был виден ход бэкапа
|
||
restic backup "$BACKUP_PATH" "${EXCLUDE_OPTS[@]}"
|
||
|
||
echo "Restic forget (retention 3 daily, 2 weekly, 2 monthly)..."
|
||
restic forget --keep-daily 3 --keep-weekly 2 --keep-monthly 2 --prune --quiet
|
||
|
||
echo "Restic prune..."
|
||
restic prune --quiet
|
||
|
||
# Время окончания и длительность
|
||
END_TS=$(date +%s)
|
||
END_HUMAN=$(date '+%Y-%m-%d %H:%M:%S')
|
||
DURATION_SEC=$(( END_TS - START_TS ))
|
||
if [ "$DURATION_SEC" -lt 0 ] 2>/dev/null; then
|
||
DURATION_SEC=0
|
||
fi
|
||
DUR_MIN=$(( DURATION_SEC / 60 ))
|
||
DUR_SEC=$(( DURATION_SEC % 60 ))
|
||
|
||
echo "Restic backup done."
|
||
echo "Время запуска: $START_HUMAN"
|
||
echo "Время завершения: $END_HUMAN"
|
||
echo "Длительность: ${DUR_MIN} мин ${DUR_SEC} сек"
|
||
|
||
# Уведомление в Telegram (шлюз тихо выходит, если конфига нет)
|
||
NOTIFY_SCRIPT="${NOTIFY_SCRIPT:-/root/scripts/notify-telegram.sh}"
|
||
if [ -x "$NOTIFY_SCRIPT" ]; then
|
||
STATS=$(restic stats latest 2>/dev/null) || true
|
||
FILES=$(echo "$STATS" | grep "Total File Count" | sed 's/.*:[[:space:]]*//')
|
||
SIZE=$(echo "$STATS" | grep "Total Size" | sed 's/.*:[[:space:]]*//')
|
||
if [ -n "$FILES" ] && [ -n "$SIZE" ]; then
|
||
BODY="Резервное копирование завершено.
|
||
Объекты: снимок /mnt/backup в Yandex (без photos). Файлов в снимке: $FILES.
|
||
Размер копии: ${SIZE}.
|
||
Время запуска: ${START_HUMAN}.
|
||
Время завершения: ${END_HUMAN}.
|
||
Длительность: ${DUR_MIN} мин ${DUR_SEC} сек."
|
||
"$NOTIFY_SCRIPT" "☁️ Restic Yandex" "$BODY" || true
|
||
else
|
||
BODY="Резервное копирование завершено.
|
||
Объекты: снимок /mnt/backup в Yandex (без photos).
|
||
Размер копии: — (stats недоступны).
|
||
Время запуска: ${START_HUMAN}.
|
||
Время завершения: ${END_HUMAN}.
|
||
Длительность: ${DUR_MIN} мин ${DUR_SEC} сек."
|
||
"$NOTIFY_SCRIPT" "☁️ Restic Yandex" "$BODY" || true
|
||
fi
|
||
fi
|