Add notification feature to backup scripts for various services

Enhance backup scripts for Nextcloud, Gitea, Paperless, Vaultwarden, Immich, and VPS configurations by adding Telegram notifications upon completion. Include details such as backup size and objects backed up. Update backup documentation to reflect these changes and ensure clarity on backup processes and retention policies.
This commit is contained in:
2026-02-27 20:42:30 +03:00
parent 56cee83198
commit f319133cee
21 changed files with 1048 additions and 168 deletions

View File

@@ -3,6 +3,8 @@
# Запускать на хосте Proxmox под root. Использует pct exec (SSH не нужен).
# Результат: /mnt/backup/databases/ct101-nextcloud/nextcloud-db-YYYYMMDD-HHMM.sql.gz
set -e
# Чтобы из cron находились bw и jq (часто в /usr/local/bin)
export PATH="/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:${PATH:-}"
CT_ID=101
BACKUP_DIR="/mnt/backup/databases/ct101-nextcloud"
@@ -16,18 +18,56 @@ if [ "$(id -u)" -ne 0 ]; then
exit 1
fi
# Минимальный размер дампа (байт). Пустой gzip ≈ 20 байт — значит pg_dump не отдал данные.
MIN_BACKUP_BYTES=512
mkdir -p "$BACKUP_DIR"
DATE=$(date +%Y%m%d-%H%M)
OUTPUT="$BACKUP_DIR/nextcloud-db-$DATE.sql.gz"
ERR=$(mktemp)
trap "rm -f '$ERR'" EXIT
pct exec $CT_ID -- docker exec "$PG_CONTAINER" pg_dump -U nextcloud nextcloud 2>/dev/null | gzip > "$OUTPUT"
# PGPASSWORD из Vaultwarden (объект NEXTCLOUD: поле dbpassword или пароль). См. docs/backup/proxmox-phase1-backup.md
PG_ENV_ARGS=""
BW_MASTER_PASSWORD_FILE="${BW_MASTER_PASSWORD_FILE:-/root/.bw-master}"
if [ -f "$BW_MASTER_PASSWORD_FILE" ] && command -v bw >/dev/null 2>&1 && command -v jq >/dev/null 2>&1; then
export BW_SESSION=$(bw unlock --passwordfile "$BW_MASTER_PASSWORD_FILE" --raw 2>/dev/null) || true
if [ -n "${BW_SESSION:-}" ]; then
PGPASS=$(bw get item "NEXTCLOUD" 2>/dev/null | jq -r '.fields[] | select(.name=="dbpassword") | .value')
[ -z "$PGPASS" ] && PGPASS=$(bw get password "NEXTCLOUD" 2>/dev/null)
[ -n "$PGPASS" ] && PG_ENV_ARGS="-e PGPASSWORD=$PGPASS"
fi
fi
pct exec $CT_ID -- docker exec $PG_ENV_ARGS "$PG_CONTAINER" pg_dump -U nextcloud nextcloud 2>"$ERR" | gzip > "$OUTPUT"
if [ -s "$OUTPUT" ]; then
echo "Создан: $OUTPUT ($(du -h "$OUTPUT" | cut -f1))"
SIZE_BYTES=$(stat -c%s "$OUTPUT" 2>/dev/null || echo 0)
if [ -s "$OUTPUT" ] && [ "$SIZE_BYTES" -ge "$MIN_BACKUP_BYTES" ]; then
echo "Создан: $OUTPUT ($(du --apparent-size -h "$OUTPUT" | cut -f1))"
# Проверка: несжатый размер дампа (2GB БД на диске → 200600MB SQL нормально: индексы не в дампе, потом gzip)
if [ "${VERIFY_BACKUP:-0}" = "1" ]; then
UNCOMPRESSED=$(gunzip -c "$OUTPUT" 2>/dev/null | wc -c)
UNCOMPRESSED_MB=$(( UNCOMPRESSED / 1024 / 1024 ))
echo "Несжатый размер дампа: ${UNCOMPRESSED_MB} MB (${UNCOMPRESSED} B)"
TABLES_IN_DUMP=$(gunzip -c "$OUTPUT" 2>/dev/null | grep -c '^CREATE TABLE ' || true)
echo "Таблиц в дампе: $TABLES_IN_DUMP"
fi
else
echo "Ошибка: дамп пустой или контейнер недоступен."
echo "Ошибка: дамп пустой или слишком мал (${SIZE_BYTES} байт). Проверьте контейнер $PG_CONTAINER и пароль БД в Vaultwarden (NEXTCLOUD)."
[ -s "$ERR" ] && cat "$ERR" >&2
rm -f "$OUTPUT"
exit 1
fi
find "$BACKUP_DIR" -name 'nextcloud-db-*.sql.gz' -mtime +$RETENTION_DAYS -delete
NOTIFY_SCRIPT="${NOTIFY_SCRIPT:-/root/scripts/notify-telegram.sh}"
if [ -x "$NOTIFY_SCRIPT" ]; then
SIZE=$(du --apparent-size -h "$OUTPUT" | cut -f1)
SIZE_BYTES=$(stat -c%s "$OUTPUT" 2>/dev/null || echo 0)
BODY="Резервное копирование завершено.
Объекты: дамп БД Nextcloud (PostgreSQL).
Размер копии: ${SIZE}."
[ "$SIZE_BYTES" -lt 10240 ] 2>/dev/null && BODY="${BODY}
⚠️ Подозрительно малый размер — проверьте контейнер nextcloud-db-1 и наличие данных в БД."
"$NOTIFY_SCRIPT" "🗄️ Nextcloud (БД)" "$BODY" || true
fi

View File

@@ -3,6 +3,7 @@
# Запускать на хосте Proxmox под root. Использует pct exec.
# Результат: /mnt/backup/databases/ct103-gitea/gitea-db-YYYYMMDD-HHMM.sql.gz
set -e
export PATH="/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:${PATH:-}"
CT_ID=103
BACKUP_DIR="/mnt/backup/databases/ct103-gitea"
@@ -14,18 +15,47 @@ if [ "$(id -u)" -ne 0 ]; then
exit 1
fi
# Минимальный размер дампа (байт). Пустой gzip ≈ 20 байт — значит pg_dump не отдал данные.
MIN_BACKUP_BYTES=512
mkdir -p "$BACKUP_DIR"
DATE=$(date +%Y%m%d-%H%M)
OUTPUT="$BACKUP_DIR/gitea-db-$DATE.sql.gz"
ERR=$(mktemp)
trap "rm -f '$ERR'" EXIT
pct exec $CT_ID -- docker exec "$PG_CONTAINER" pg_dump -U gitea gitea 2>/dev/null | gzip > "$OUTPUT"
# PGPASSWORD из Vaultwarden (объект GITEA, пароль). См. docs/backup/proxmox-phase1-backup.md
PG_ENV_ARGS=""
BW_MASTER_PASSWORD_FILE="${BW_MASTER_PASSWORD_FILE:-/root/.bw-master}"
if [ -f "$BW_MASTER_PASSWORD_FILE" ] && command -v bw >/dev/null 2>&1; then
export BW_SESSION=$(bw unlock --passwordfile "$BW_MASTER_PASSWORD_FILE" --raw 2>/dev/null) || true
if [ -n "${BW_SESSION:-}" ]; then
PGPASS=$(bw get password "GITEA" 2>/dev/null)
[ -n "$PGPASS" ] && PG_ENV_ARGS="-e PGPASSWORD=$PGPASS"
fi
fi
pct exec $CT_ID -- docker exec $PG_ENV_ARGS "$PG_CONTAINER" pg_dump -U gitea gitea 2>"$ERR" | gzip > "$OUTPUT"
if [ -s "$OUTPUT" ]; then
echo "Создан: $OUTPUT ($(du -h "$OUTPUT" | cut -f1))"
SIZE_BYTES=$(stat -c%s "$OUTPUT" 2>/dev/null || echo 0)
if [ -s "$OUTPUT" ] && [ "$SIZE_BYTES" -ge "$MIN_BACKUP_BYTES" ]; then
echo "Создан: $OUTPUT ($(du --apparent-size -h "$OUTPUT" | cut -f1))"
else
echo "Ошибка: дамп пустой или контейнер недоступен."
echo "Ошибка: дамп пустой или слишком мал (${SIZE_BYTES} байт). Проверьте контейнер $PG_CONTAINER и пароль БД в Vaultwarden (GITEA)."
[ -s "$ERR" ] && cat "$ERR" >&2
rm -f "$OUTPUT"
exit 1
fi
find "$BACKUP_DIR" -name 'gitea-db-*.sql.gz' -mtime +$RETENTION_DAYS -delete
NOTIFY_SCRIPT="${NOTIFY_SCRIPT:-/root/scripts/notify-telegram.sh}"
if [ -x "$NOTIFY_SCRIPT" ]; then
SIZE=$(du --apparent-size -h "$OUTPUT" | cut -f1)
SIZE_BYTES=$(stat -c%s "$OUTPUT" 2>/dev/null || echo 0)
BODY="Резервное копирование завершено.
Объекты: дамп БД Gitea (PostgreSQL).
Размер копии: ${SIZE}."
[ "$SIZE_BYTES" -lt 10240 ] 2>/dev/null && BODY="${BODY}
⚠️ Подозрительно малый размер — проверьте контейнер gitea-db-1 и наличие данных в БД."
"$NOTIFY_SCRIPT" "🗄️ Gitea (БД)" "$BODY" || true
fi

View File

@@ -3,6 +3,7 @@
# Запускать на хосте Proxmox под root. Использует pct exec.
# Результат: /mnt/backup/databases/ct104-paperless/paperless-db-YYYYMMDD-HHMM.sql.gz
set -e
export PATH="/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:${PATH:-}"
CT_ID=104
BACKUP_DIR="/mnt/backup/databases/ct104-paperless"
@@ -14,18 +15,47 @@ if [ "$(id -u)" -ne 0 ]; then
exit 1
fi
# Минимальный размер дампа (байт). Пустой gzip ≈ 20 байт — значит pg_dump не отдал данные.
MIN_BACKUP_BYTES=512
mkdir -p "$BACKUP_DIR"
DATE=$(date +%Y%m%d-%H%M)
OUTPUT="$BACKUP_DIR/paperless-db-$DATE.sql.gz"
ERR=$(mktemp)
trap "rm -f '$ERR'" EXIT
pct exec $CT_ID -- docker exec "$PG_CONTAINER" pg_dump -U paperless paperless 2>/dev/null | gzip > "$OUTPUT"
# PGPASSWORD из Vaultwarden (объект PAPERLESS, пароль). См. docs/backup/proxmox-phase1-backup.md
PG_ENV_ARGS=""
BW_MASTER_PASSWORD_FILE="${BW_MASTER_PASSWORD_FILE:-/root/.bw-master}"
if [ -f "$BW_MASTER_PASSWORD_FILE" ] && command -v bw >/dev/null 2>&1; then
export BW_SESSION=$(bw unlock --passwordfile "$BW_MASTER_PASSWORD_FILE" --raw 2>/dev/null) || true
if [ -n "${BW_SESSION:-}" ]; then
PGPASS=$(bw get password "PAPERLESS" 2>/dev/null)
[ -n "$PGPASS" ] && PG_ENV_ARGS="-e PGPASSWORD=$PGPASS"
fi
fi
pct exec $CT_ID -- docker exec $PG_ENV_ARGS "$PG_CONTAINER" pg_dump -U paperless paperless 2>"$ERR" | gzip > "$OUTPUT"
if [ -s "$OUTPUT" ]; then
echo "Создан: $OUTPUT ($(du -h "$OUTPUT" | cut -f1))"
SIZE_BYTES=$(stat -c%s "$OUTPUT" 2>/dev/null || echo 0)
if [ -s "$OUTPUT" ] && [ "$SIZE_BYTES" -ge "$MIN_BACKUP_BYTES" ]; then
echo "Создан: $OUTPUT ($(du --apparent-size -h "$OUTPUT" | cut -f1))"
else
echo "Ошибка: дамп пустой или контейнер недоступен."
echo "Ошибка: дамп пустой или слишком мал (${SIZE_BYTES} байт). Проверьте контейнер $PG_CONTAINER и пароль БД в Vaultwarden (PAPERLESS)."
[ -s "$ERR" ] && cat "$ERR" >&2
rm -f "$OUTPUT"
exit 1
fi
find "$BACKUP_DIR" -name 'paperless-db-*.sql.gz' -mtime +$RETENTION_DAYS -delete
NOTIFY_SCRIPT="${NOTIFY_SCRIPT:-/root/scripts/notify-telegram.sh}"
if [ -x "$NOTIFY_SCRIPT" ]; then
SIZE=$(du --apparent-size -h "$OUTPUT" | cut -f1)
SIZE_BYTES=$(stat -c%s "$OUTPUT" 2>/dev/null || echo 0)
BODY="Резервное копирование завершено.
Объекты: дамп БД Paperless (PostgreSQL).
Размер копии: ${SIZE}."
[ "$SIZE_BYTES" -lt 10240 ] 2>/dev/null && BODY="${BODY}
⚠️ Подозрительно малый размер — проверьте контейнер paperless-db-1 и наличие данных в БД."
"$NOTIFY_SCRIPT" "🗄️ Paperless (БД)" "$BODY" || true
fi

View File

@@ -3,12 +3,15 @@
# Запускать на хосте Proxmox под root. Использует pct exec.
# Результат: /mnt/backup/other/ct105-vectors/vectors-YYYYMMDD-HHMM.tar.gz
set -e
export PATH="/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:${PATH:-}"
CT_ID=105
REMOTE_PATH="/home/rag-service/data/vectors"
BACKUP_DIR="/mnt/backup/other/ct105-vectors"
RETENTION_DAYS=14
# Минимальный размер архива (байт). Пустой gzip ≈ 20 байт — каталог пуст или путь неверный.
MIN_BACKUP_BYTES=512
if [ "$(id -u)" -ne 0 ]; then
echo "Запускайте под root."
exit 1
@@ -17,15 +20,31 @@ fi
mkdir -p "$BACKUP_DIR"
DATE=$(date +%Y%m%d-%H%M)
OUTPUT="$BACKUP_DIR/vectors-$DATE.tar.gz"
ERR=$(mktemp)
trap "rm -f '$ERR'" EXIT
pct exec $CT_ID -- tar cf - -C /home/rag-service/data vectors 2>/dev/null | gzip > "$OUTPUT"
pct exec $CT_ID -- tar cf - -C /home/rag-service/data vectors 2>"$ERR" | gzip > "$OUTPUT"
if [ -s "$OUTPUT" ]; then
echo "Создан: $OUTPUT ($(du -h "$OUTPUT" | cut -f1))"
SIZE_BYTES=$(stat -c%s "$OUTPUT" 2>/dev/null || echo 0)
if [ -s "$OUTPUT" ] && [ "$SIZE_BYTES" -ge "$MIN_BACKUP_BYTES" ]; then
echo "Создан: $OUTPUT ($(du --apparent-size -h "$OUTPUT" | cut -f1))"
else
echo "Ошибка: архив пустой или каталог недоступен."
echo "Ошибка: архив пустой или слишком мал (${SIZE_BYTES} байт). Проверьте /home/rag-service/data/vectors в CT $CT_ID."
[ -s "$ERR" ] && cat "$ERR" >&2
rm -f "$OUTPUT"
exit 1
fi
find "$BACKUP_DIR" -name 'vectors-*.tar.gz' -mtime +$RETENTION_DAYS -delete
NOTIFY_SCRIPT="${NOTIFY_SCRIPT:-/root/scripts/notify-telegram.sh}"
if [ -x "$NOTIFY_SCRIPT" ]; then
SIZE=$(du --apparent-size -h "$OUTPUT" | cut -f1)
SIZE_BYTES=$(stat -c%s "$OUTPUT" 2>/dev/null || echo 0)
BODY="Резервное копирование завершено.
Объекты: архив векторов RAG (CT 105, vectors.npz).
Размер копии: ${SIZE}."
[ "$SIZE_BYTES" -lt 10240 ] 2>/dev/null && BODY="${BODY}
⚠️ Подозрительно малый размер — проверьте /home/rag-service/data/vectors в CT 105."
"$NOTIFY_SCRIPT" "📐 Векторы RAG" "$BODY" || true
fi

View File

@@ -21,3 +21,12 @@ chmod 600 "$BACKUP_ROOT"/etc-pve-*.tar.gz "$BACKUP_ROOT"/etc-host-configs-*.tar.
find "$BACKUP_ROOT" -name 'etc-pve-*.tar.gz' -mtime +$RETENTION_DAYS -delete
find "$BACKUP_ROOT" -name 'etc-host-configs-*.tar.gz' -mtime +$RETENTION_DAYS -delete
NOTIFY_SCRIPT="${NOTIFY_SCRIPT:-/root/scripts/notify-telegram.sh}"
if [ -x "$NOTIFY_SCRIPT" ]; then
SIZE=$(du -sh "$BACKUP_ROOT" 2>/dev/null | cut -f1) || true
BODY="Резервное копирование завершено.
Объекты: архивы /etc/pve, конфиги сети (interfaces, hosts, resolv.conf).
Размер копии: ${SIZE:-}."
"$NOTIFY_SCRIPT" "⚙️ Конфиги хоста" "$BODY" || true
fi

View File

@@ -19,3 +19,12 @@ mkdir -p "$BACKUP_PATH"
rsync -az --timeout=3600 \
--exclude=".stfolder" \
"$VM_SSH:$REMOTE_PATH/" "$BACKUP_PATH/"
NOTIFY_SCRIPT="${NOTIFY_SCRIPT:-/root/scripts/notify-telegram.sh}"
if [ -x "$NOTIFY_SCRIPT" ]; then
SIZE=$(du -sh "$BACKUP_PATH" 2>/dev/null | cut -f1) || true
BODY="Резервное копирование завершено.
Объекты: библиотека фото Immich (rsync с VM 200).
Размер копии: ${SIZE:-}."
"$NOTIFY_SCRIPT" "📷 Фото Immich (rsync)" "$BODY" || true
fi

View File

@@ -1,46 +1,54 @@
#!/bin/bash
# Выгрузка только /mnt/backup/photos в Yandex Object Storage (S3) через restic.
# Тот же репозиторий, что и backup-restic-yandex.sh; фото вынесены в отдельный снимок (больше всего данных).
# Запускать на хосте Proxmox под root. Требуется тот же /root/.restic-yandex.env и /root/.restic-password.
# Cron: 0 1 * * * (01:00).
# Запускать на хосте Proxmox под root. Секреты из Vaultwarden (объект RESTIC), файл /root/.bw-master (chmod 600).
# Cron: 10 4 * * * (04:10, после основного restic в 04:00).
set -e
ENV_FILE="/root/.restic-yandex.env"
BACKUP_PATH="/mnt/backup/photos"
# Время запуска (для логов и уведомлений)
START_TS=$(date +%s)
START_HUMAN=$(date '+%Y-%m-%d %H:%M:%S')
if [ "$(id -u)" -ne 0 ]; then
echo "Запускайте под root."
exit 1
fi
if [ ! -f "$ENV_FILE" ]; then
echo "Нет файла $ENV_FILE. Скопируйте restic-yandex-env.example и задайте ключи."
# Секреты из 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/backup/proxmox-phase1-backup.md"
exit 1
fi
# shellcheck source=/dev/null
source "$ENV_FILE"
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."; 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 "В $ENV_FILE не задано: $var"
echo "В Vaultwarden (RESTIC) не задано поле для $var"
exit 1
fi
done
if [ -z "$RESTIC_PASSWORD_FILE" ]; then
RESTIC_PASSWORD_FILE="/root/.restic-password"
fi
if [ ! -f "$RESTIC_PASSWORD_FILE" ]; then
echo "Файл с паролем репозитория не найден: $RESTIC_PASSWORD_FILE"
exit 1
fi
export AWS_ACCESS_KEY_ID
export AWS_SECRET_ACCESS_KEY
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
export RESTIC_REPOSITORY
export AWS_DEFAULT_REGION="${AWS_DEFAULT_REGION:-ru-central1}"
if ! command -v restic >/dev/null 2>&1; then
echo "restic не установлен. Установите: apt install restic."
@@ -53,7 +61,8 @@ if [ ! -d "$BACKUP_PATH" ]; then
fi
echo "Restic backup (photos): $BACKUP_PATH -> $RESTIC_REPOSITORY"
restic backup "$BACKUP_PATH" --quiet
# Показываем прогресс restic (без --quiet), чтобы был виден ход бэкапа
restic backup "$BACKUP_PATH"
echo "Restic forget (retention 3 daily, 2 weekly, 2 monthly)..."
restic forget --keep-daily 3 --keep-weekly 2 --keep-monthly 2 --prune --quiet
@@ -61,4 +70,42 @@ 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 photos 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/photos в Yandex. Файлов в снимке: $FILES.
Размер копии: ${SIZE}.
Время запуска: ${START_HUMAN}.
Время завершения: ${END_HUMAN}.
Длительность: ${DUR_MIN} мин ${DUR_SEC} сек."
"$NOTIFY_SCRIPT" "📷 Restic Yandex (photos)" "$BODY" || true
else
BODY="Резервное копирование завершено.
Объекты: снимок /mnt/backup/photos в Yandex.
Размер копии: — (stats недоступны).
Время запуска: ${START_HUMAN}.
Время завершения: ${END_HUMAN}.
Длительность: ${DUR_MIN} мин ${DUR_SEC} сек."
"$NOTIFY_SCRIPT" "📷 Restic Yandex (photos)" "$BODY" || true
fi
fi

View File

@@ -2,12 +2,15 @@
# Выгрузка /mnt/backup в Yandex Object Storage (S3) через restic (без каталога photos).
# Фото бэкапятся отдельно: backup-restic-yandex-photos.sh.
# Запускать на хосте Proxmox под root.
# Перед первым запуском: установить restic, создать /root/.restic-yandex.env и /root/.restic-password, выполнить restic init.
# Секреты: из 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:0003:30; 05:00 зарезервировано под перезагрузку).
set -e
ENV_FILE="/root/.restic-yandex.env"
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")
@@ -16,34 +19,40 @@ if [ "$(id -u)" -ne 0 ]; then
exit 1
fi
if [ ! -f "$ENV_FILE" ]; then
echo "Нет файла $ENV_FILE. Скопируйте restic-yandex-env.example и задайте ключи."
# Секреты из 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/backup/proxmox-phase1-backup.md"
exit 1
fi
# shellcheck source=/dev/null
source "$ENV_FILE"
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 "В $ENV_FILE не задано: $var"
echo "В Vaultwarden (RESTIC) не задано поле для $var"
exit 1
fi
done
if [ -z "$RESTIC_PASSWORD_FILE" ]; then
RESTIC_PASSWORD_FILE="/root/.restic-password"
fi
if [ ! -f "$RESTIC_PASSWORD_FILE" ]; then
echo "Файл с паролем репозитория не найден: $RESTIC_PASSWORD_FILE. Создайте его и выполните restic init."
exit 1
fi
export AWS_ACCESS_KEY_ID
export AWS_SECRET_ACCESS_KEY
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
export RESTIC_REPOSITORY
export AWS_DEFAULT_REGION="${AWS_DEFAULT_REGION:-ru-central1}"
if ! command -v restic >/dev/null 2>&1; then
echo "restic не установлен. Установите: apt install restic."
@@ -51,7 +60,8 @@ if ! command -v restic >/dev/null 2>&1; then
fi
echo "Restic backup: $BACKUP_PATH (excl. photos) -> $RESTIC_REPOSITORY"
restic backup "$BACKUP_PATH" "${EXCLUDE_OPTS[@]}" --quiet
# Показываем прогресс 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
@@ -59,4 +69,42 @@ 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

View File

@@ -4,6 +4,7 @@
# Запускать на хосте Proxmox под root. Использует pct exec.
# Результат: /mnt/backup/other/vaultwarden/vaultwarden-data-YYYYMMDD-HHMM.tar.gz
set -e
export PATH="/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:${PATH:-}"
CT_ID=103
REMOTE_PATH="/opt/docker/vaultwarden/data"
@@ -15,19 +16,38 @@ if [ "$(id -u)" -ne 0 ]; then
exit 1
fi
# Минимальный размер архива (байт). Пустой tar.gz ≈ 20 байт — значит каталог пуст или путь неверный.
MIN_BACKUP_BYTES=512
mkdir -p "$BACKUP_DIR"
DATE=$(date +%Y%m%d-%H%M)
OUTPUT="$BACKUP_DIR/vaultwarden-data-$DATE.tar.gz"
ERR=$(mktemp)
trap "rm -f '$ERR'" EXIT
pct exec $CT_ID -- tar cf - -C /opt/docker/vaultwarden data 2>/dev/null | gzip > "$OUTPUT"
pct exec $CT_ID -- tar cf - -C /opt/docker/vaultwarden data 2>"$ERR" | gzip > "$OUTPUT"
if [ -s "$OUTPUT" ]; then
SIZE_BYTES=$(stat -c%s "$OUTPUT" 2>/dev/null || echo 0)
if [ -s "$OUTPUT" ] && [ "$SIZE_BYTES" -ge "$MIN_BACKUP_BYTES" ]; then
chmod 600 "$OUTPUT"
echo "Создан: $OUTPUT ($(du -h "$OUTPUT" | cut -f1))"
echo "Создан: $OUTPUT ($(du --apparent-size -h "$OUTPUT" | cut -f1))"
else
echo "Ошибка: архив пустой или каталог недоступен."
echo "Ошибка: архив пустой или слишком мал (${SIZE_BYTES} байт). Проверьте путь /opt/docker/vaultwarden/data в CT $CT_ID."
[ -s "$ERR" ] && cat "$ERR" >&2
rm -f "$OUTPUT"
exit 1
fi
find "$BACKUP_DIR" -name 'vaultwarden-data-*.tar.gz' -mtime +$RETENTION_DAYS -delete
NOTIFY_SCRIPT="${NOTIFY_SCRIPT:-/root/scripts/notify-telegram.sh}"
if [ -x "$NOTIFY_SCRIPT" ]; then
SIZE=$(du --apparent-size -h "$OUTPUT" | cut -f1)
SIZE_BYTES=$(stat -c%s "$OUTPUT" 2>/dev/null || echo 0)
BODY="Резервное копирование завершено.
Объекты: данные Vaultwarden (пароли).
Размер копии: ${SIZE}."
[ "$SIZE_BYTES" -lt 10240 ] 2>/dev/null && BODY="${BODY}
⚠️ Подозрительно малый размер — проверьте /opt/docker/vaultwarden/data в CT 103."
"$NOTIFY_SCRIPT" "🔐 Vaultwarden" "$BODY" || true
fi

View File

@@ -30,7 +30,7 @@ else
fi
if [ -s "$OUTPUT" ]; then
echo "Создан: $OUTPUT ($(du -h "$OUTPUT" | cut -f1))"
echo "Создан: $OUTPUT ($(du --apparent-size -h "$OUTPUT" | cut -f1))"
else
echo "Ошибка: дамп пустой или не создан."
rm -f "$OUTPUT"
@@ -39,3 +39,15 @@ fi
# Удалить дампы старше RETENTION_DAYS
find "$BACKUP_DIR" -name 'immich-db-*.sql.gz' -mtime +$RETENTION_DAYS -delete
NOTIFY_SCRIPT="${NOTIFY_SCRIPT:-/root/scripts/notify-telegram.sh}"
if [ -x "$NOTIFY_SCRIPT" ]; then
SIZE=$(du --apparent-size -h "$OUTPUT" | cut -f1)
SIZE_BYTES=$(stat -c%s "$OUTPUT" 2>/dev/null || echo 0)
BODY="Резервное копирование завершено.
Объекты: дамп БД Immich (PostgreSQL, VM 200).
Размер копии: ${SIZE}."
[ "$SIZE_BYTES" -lt 10240 ] 2>/dev/null && BODY="${BODY}
⚠️ Подозрительно малый размер — проверьте скрипт на VM 200 и наличие данных в БД."
"$NOTIFY_SCRIPT" "🗄️ Immich (БД)" "$BODY" || true
fi

View File

@@ -71,3 +71,12 @@ if [ -f "$S3_ENV" ]; then
else
echo "Подсказка: для бэкапа S3 создайте $S3_ENV с S3_ACCESS_KEY, S3_SECRET_KEY, S3_BUCKET_NAME."
fi
NOTIFY_SCRIPT="${NOTIFY_SCRIPT:-/root/scripts/notify-telegram.sh}"
if [ -x "$NOTIFY_SCRIPT" ]; then
SIZE=$(du -sh "$BACKUP_ROOT" 2>/dev/null | cut -f1) || true
BODY="Резервное копирование завершено.
Объекты: БД, voice_users, S3 (telegram-helper-bot).
Размер копии: ${SIZE:-}."
"$NOTIFY_SCRIPT" "🖥️ VPS Миран" "$BODY" || true
fi

View File

@@ -37,6 +37,18 @@ ssh "${SSH_OPTS[@]}" "${VPS_USER}@${VPS_HOST}" "tar -chzf - -C / \
var/www/katykhin.store" > "$ARCHIVE"
chmod 600 "$ARCHIVE"
echo "Бэкап MTProto (VPS DE): $ARCHIVE ($(du -h "$ARCHIVE" | cut -f1))"
echo "Бэкап MTProto (VPS DE): $ARCHIVE ($(du --apparent-size -h "$ARCHIVE" | cut -f1))"
find "$BACKUP_ROOT" -name 'mtproto-config-*.tar.gz' -mtime +$RETENTION_DAYS -delete
NOTIFY_SCRIPT="${NOTIFY_SCRIPT:-/root/scripts/notify-telegram.sh}"
if [ -x "$NOTIFY_SCRIPT" ]; then
SIZE=$(du --apparent-size -h "$ARCHIVE" | cut -f1)
SIZE_BYTES=$(stat -c%s "$ARCHIVE" 2>/dev/null || echo 0)
BODY="Резервное копирование завершено.
Объекты: конфиги MTProto, nginx, Let's Encrypt, сайт (VPS DE).
Размер копии: ${SIZE}."
[ "$SIZE_BYTES" -lt 1024 ] 2>/dev/null && BODY="${BODY}
⚠️ Подозрительно малый размер — проверьте SSH и наличие файлов на VPS."
"$NOTIFY_SCRIPT" "🌐 VPS MTProto (DE)" "$BODY" || true
fi

View File

@@ -0,0 +1,63 @@
#!/bin/bash
# Единая точка отправки уведомлений в Telegram (шлюз).
# Вызывают скрипты бэкапов на хосте Proxmox. Позже тот же шлюз можно вызывать с VM 200 / VPS по SSH.
# Использование: notify-telegram.sh "Заголовок" "Текст сообщения"
# Секреты: из Vaultwarden (токен — пароль объекта HOME_BOT_TOKEN, chat_id — поле TELEGRAM_SELF_CHAT_ID объекта RESTIC).
# Файл с мастер-паролем: /root/.bw-master (chmod 600). Если его нет — тихо выходим с 0, не ломаем вызывающий скрипт.
set -e
TITLE="${1:-Notification}"
BODY="${2:-}"
# Креды из Vaultwarden или из старого конфига (fallback)
TELEGRAM_BOT_TOKEN=""
TELEGRAM_CHAT_ID=""
BW_MASTER_PASSWORD_FILE="${BW_MASTER_PASSWORD_FILE:-/root/.bw-master}"
if [ -f "$BW_MASTER_PASSWORD_FILE" ] && command -v bw >/dev/null 2>&1 && command -v jq >/dev/null 2>&1; then
BW_SESSION=$(bw unlock --passwordfile "$BW_MASTER_PASSWORD_FILE" --raw 2>/dev/null) || true
if [ -n "$BW_SESSION" ]; then
export BW_SESSION
TELEGRAM_BOT_TOKEN=$(bw get password "HOME_BOT_TOKEN" 2>/dev/null) || true
RESTIC_ITEM=$(bw get item "RESTIC" 2>/dev/null) || true
if [ -n "$RESTIC_ITEM" ]; then
TELEGRAM_CHAT_ID=$(echo "$RESTIC_ITEM" | jq -r '.fields[] | select(.name=="TELEGRAM_SELF_CHAT_ID") | .value' 2>/dev/null) || true
fi
fi
fi
if [ -z "$TELEGRAM_BOT_TOKEN" ] || [ -z "$TELEGRAM_CHAT_ID" ]; then
ENV_FILE="${TELEGRAM_NOTIFY_ENV:-/root/.telegram-notify.env}"
if [ -f "$ENV_FILE" ]; then
# shellcheck source=/dev/null
source "$ENV_FILE"
fi
fi
if [ -z "$TELEGRAM_BOT_TOKEN" ] || [ -z "$TELEGRAM_CHAT_ID" ]; then
exit 0
fi
if [ -z "$BODY" ]; then
TEXT="$TITLE"
else
TEXT="$TITLE
$BODY"
fi
URL="https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage"
if [ -n "${TELEGRAM_DEBUG:-}" ]; then
curl -s -w "\nHTTP_CODE:%{http_code}\n" -X POST "$URL" \
--data-urlencode "chat_id=$TELEGRAM_CHAT_ID" \
--data-urlencode "text=$TEXT" \
-d "disable_web_page_preview=true" \
--max-time 10
else
curl -sf -X POST "$URL" \
--data-urlencode "chat_id=$TELEGRAM_CHAT_ID" \
--data-urlencode "text=$TEXT" \
-d "disable_web_page_preview=true" \
--max-time 10 \
>/dev/null 2>&1 || true
fi
exit 0

View File

@@ -0,0 +1,48 @@
#!/bin/bash
# Проверяет каталог локальных vzdump за последние 2 часа и отправляет в Telegram сводку.
# Задание Proxmox Backup выполняется в 02:00; этот скрипт запускают по cron в 03:00.
# Использование: notify-vzdump-success.sh [путь_к_dump]
# По умолчанию: /mnt/backup/proxmox/dump/dump/
DUMP_DIR="${1:-/mnt/backup/proxmox/dump/dump}"
NOTIFY_SCRIPT="${NOTIFY_SCRIPT:-/root/scripts/notify-telegram.sh}"
# Файлы, изменённые за последние 120 минут (2 часа)
MAX_AGE_MIN=120
if [ ! -d "$DUMP_DIR" ]; then
exit 0
fi
if [ ! -x "$NOTIFY_SCRIPT" ]; then
exit 0
fi
# Список файлов vzdump, изменённых за последние MAX_AGE_MIN минут
RECENT=$(find "$DUMP_DIR" -maxdepth 1 -type f \( -name 'vzdump-*.tar.zst' -o -name 'vzdump-*.vma.zst' -o -name 'vzdump-*.vma' \) -mmin "-$MAX_AGE_MIN" 2>/dev/null)
if [ -z "$RECENT" ]; then
exit 0
fi
COUNT=$(echo "$RECENT" | grep -c . 2>/dev/null || echo 0)
[ "$COUNT" -eq 0 ] && exit 0
TOTAL_BYTES=$(echo "$RECENT" | while read -r f; do stat -c %s "$f" 2>/dev/null; done | awk '{s+=$1} END {print s+0}')
[ -z "$TOTAL_BYTES" ] && TOTAL_BYTES=0
# Размер в ГБ (округление до 2 знаков; если bc нет — целое число)
TOTAL_GB=$(echo "scale=2; $TOTAL_BYTES / 1024 / 1024 / 1024" | bc 2>/dev/null)
[ -z "$TOTAL_GB" ] && TOTAL_GB="$((TOTAL_BYTES / 1024 / 1024 / 1024))"
# Время последнего изменения (последний записанный файл = время завершения бэкапа)
LATEST_MTIME=$(echo "$RECENT" | while read -r f; do stat -c %Y "$f" 2>/dev/null; done | sort -n | tail -1)
FINISH_TIME=""
[ -n "$LATEST_MTIME" ] && FINISH_TIME=$(date -d "@$LATEST_MTIME" +%H:%M 2>/dev/null) || true
BODY="Резервное копирование завершено.
Объекты: локальный vzdump (LXC/VM). Контейнеров/ВМ: $COUNT.
Размер копии: ${TOTAL_GB} ГБ."
[ -n "$FINISH_TIME" ] && BODY="${BODY}
Время завершения: ${FINISH_TIME}."
"$NOTIFY_SCRIPT" "💾 Backup local" "$BODY" || true
exit 0

View File

@@ -2,6 +2,7 @@
# Восстановление одного файла vzdump из restic (Yandex S3) через mount.
# Не выкачивает весь репозиторий — подгружаются только нужные данные для выбранного файла.
# Запускать на хосте Proxmox под root. Требуется FUSE (restic mount).
# Секреты из Vaultwarden (объект RESTIC), файл /root/.bw-master (chmod 600).
#
# Использование:
# restore-one-vzdump-from-restic.sh [SNAPSHOT] [ПУТЬ_В_СНИМКЕ] [КУДА_СОХРАНИТЬ]
@@ -14,9 +15,7 @@
# Список файлов в снимке: restic ls SNAPSHOT
set -e
ENV_FILE="${ENV_FILE:-/root/.restic-yandex.env}"
MOUNT_DIR="${MOUNT_DIR:-/mnt/backup/restic-mount}"
RESTIC_PASSWORD_FILE="${RESTIC_PASSWORD_FILE:-/root/.restic-password}"
SNAPSHOT="${1:-latest}"
# Путь к файлу внутри снимка (как в restic ls) — бэкапим /mnt/backup, пути вида /mnt/backup/proxmox/dump/dump/vzdump-lxc-107-...
@@ -28,22 +27,40 @@ if [ "$(id -u)" -ne 0 ]; then
exit 1
fi
if [ ! -f "$ENV_FILE" ]; then
echo "Нет файла $ENV_FILE. Скопируйте restic-yandex-env.example и задайте ключи."
# Секреты из 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/backup/proxmox-phase1-backup.md"
exit 1
fi
# shellcheck source=/dev/null
source "$ENV_FILE"
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."; 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 "В $ENV_FILE не задано: $var"
echo "В Vaultwarden (RESTIC) не задано поле для $var"
exit 1
fi
done
export AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY RESTIC_REPOSITORY
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
export AWS_DEFAULT_REGION="${AWS_DEFAULT_REGION:-ru-central1}"
if ! command -v restic >/dev/null 2>&1; then
echo "restic не установлен. Установите: apt install restic."
@@ -80,7 +97,7 @@ fi
echo "Монтируем репозиторий в $MOUNT_DIR ..."
restic mount "$MOUNT_DIR" &
MOUNT_PID=$!
trap 'kill $MOUNT_PID 2>/dev/null; fusermount -u "$MOUNT_DIR" 2>/dev/null; exit' EXIT INT TERM
trap 'rm -f "$RESTIC_PASSWORD_FILE"; kill $MOUNT_PID 2>/dev/null; fusermount -u "$MOUNT_DIR" 2>/dev/null; exit' EXIT INT TERM
# В смонтированном репо файлы снимка лежат в ids/<short_id>/<путь>
SOURCE_FILE="$MOUNT_DIR/ids/$SNAPSHOT_ID/$FILE_IN_SNAPSHOT"

View File

@@ -0,0 +1,13 @@
# Пример конфига для уведомлений в Telegram (скрипт notify-telegram.sh).
# Скопируйте на хост Proxmox в /root/.telegram-notify.env и подставьте свои значения.
#
# Как получить:
# 1. Создать бота: в Telegram написать @BotFather, команда /newbot, получить токен.
# 2. Узнать chat_id: написать боту любое сообщение, затем открыть в браузере:
# https://api.telegram.org/bot<TOKEN>/getUpdates
# В ответе в updates[].message.chat.id — ваш chat_id (число или отрицательное для групп).
#
# На хосте: cp telegram-notify.env.example /root/.telegram-notify.env && chmod 600 /root/.telegram-notify.env
TELEGRAM_BOT_TOKEN=123456789:ABCdefGHIjklMNOpqrsTUVwxyz
TELEGRAM_CHAT_ID=123456789