Update backup and logging documentation for Proxmox and containers. Adjust retention policies for Yandex Object Storage and enhance log rotation settings across various containers. Include detailed instructions for VPS backup processes and configurations for AmneziaVPN.
This commit is contained in:
33
scripts/backup-ct101-pgdump.sh
Normal file
33
scripts/backup-ct101-pgdump.sh
Normal file
@@ -0,0 +1,33 @@
|
||||
#!/bin/bash
|
||||
# Логический бэкап PostgreSQL (Nextcloud) из контейнера 101.
|
||||
# Запускать на хосте Proxmox под root. Использует pct exec (SSH не нужен).
|
||||
# Результат: /mnt/backup/databases/ct101-nextcloud/nextcloud-db-YYYYMMDD-HHMM.sql.gz
|
||||
set -e
|
||||
|
||||
CT_ID=101
|
||||
BACKUP_DIR="/mnt/backup/databases/ct101-nextcloud"
|
||||
RETENTION_DAYS=14
|
||||
|
||||
# Имя контейнера БД в compose-проекте nextcloud (при смене имени в compose — поправить)
|
||||
PG_CONTAINER="nextcloud-db-1"
|
||||
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
echo "Запускайте под root."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
DATE=$(date +%Y%m%d-%H%M)
|
||||
OUTPUT="$BACKUP_DIR/nextcloud-db-$DATE.sql.gz"
|
||||
|
||||
pct exec $CT_ID -- docker exec "$PG_CONTAINER" pg_dump -U nextcloud nextcloud 2>/dev/null | gzip > "$OUTPUT"
|
||||
|
||||
if [ -s "$OUTPUT" ]; then
|
||||
echo "Создан: $OUTPUT ($(du -h "$OUTPUT" | cut -f1))"
|
||||
else
|
||||
echo "Ошибка: дамп пустой или контейнер недоступен."
|
||||
rm -f "$OUTPUT"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
find "$BACKUP_DIR" -name 'nextcloud-db-*.sql.gz' -mtime +$RETENTION_DAYS -delete
|
||||
31
scripts/backup-ct103-gitea-pgdump.sh
Normal file
31
scripts/backup-ct103-gitea-pgdump.sh
Normal file
@@ -0,0 +1,31 @@
|
||||
#!/bin/bash
|
||||
# Логический бэкап PostgreSQL (Gitea) из контейнера 103.
|
||||
# Запускать на хосте Proxmox под root. Использует pct exec.
|
||||
# Результат: /mnt/backup/databases/ct103-gitea/gitea-db-YYYYMMDD-HHMM.sql.gz
|
||||
set -e
|
||||
|
||||
CT_ID=103
|
||||
BACKUP_DIR="/mnt/backup/databases/ct103-gitea"
|
||||
RETENTION_DAYS=14
|
||||
PG_CONTAINER="gitea-db-1"
|
||||
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
echo "Запускайте под root."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
DATE=$(date +%Y%m%d-%H%M)
|
||||
OUTPUT="$BACKUP_DIR/gitea-db-$DATE.sql.gz"
|
||||
|
||||
pct exec $CT_ID -- docker exec "$PG_CONTAINER" pg_dump -U gitea gitea 2>/dev/null | gzip > "$OUTPUT"
|
||||
|
||||
if [ -s "$OUTPUT" ]; then
|
||||
echo "Создан: $OUTPUT ($(du -h "$OUTPUT" | cut -f1))"
|
||||
else
|
||||
echo "Ошибка: дамп пустой или контейнер недоступен."
|
||||
rm -f "$OUTPUT"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
find "$BACKUP_DIR" -name 'gitea-db-*.sql.gz' -mtime +$RETENTION_DAYS -delete
|
||||
31
scripts/backup-ct104-pgdump.sh
Normal file
31
scripts/backup-ct104-pgdump.sh
Normal file
@@ -0,0 +1,31 @@
|
||||
#!/bin/bash
|
||||
# Логический бэкап PostgreSQL (Paperless) из контейнера 104.
|
||||
# Запускать на хосте Proxmox под root. Использует pct exec.
|
||||
# Результат: /mnt/backup/databases/ct104-paperless/paperless-db-YYYYMMDD-HHMM.sql.gz
|
||||
set -e
|
||||
|
||||
CT_ID=104
|
||||
BACKUP_DIR="/mnt/backup/databases/ct104-paperless"
|
||||
RETENTION_DAYS=14
|
||||
PG_CONTAINER="paperless-db-1"
|
||||
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
echo "Запускайте под root."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
DATE=$(date +%Y%m%d-%H%M)
|
||||
OUTPUT="$BACKUP_DIR/paperless-db-$DATE.sql.gz"
|
||||
|
||||
pct exec $CT_ID -- docker exec "$PG_CONTAINER" pg_dump -U paperless paperless 2>/dev/null | gzip > "$OUTPUT"
|
||||
|
||||
if [ -s "$OUTPUT" ]; then
|
||||
echo "Создан: $OUTPUT ($(du -h "$OUTPUT" | cut -f1))"
|
||||
else
|
||||
echo "Ошибка: дамп пустой или контейнер недоступен."
|
||||
rm -f "$OUTPUT"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
find "$BACKUP_DIR" -name 'paperless-db-*.sql.gz' -mtime +$RETENTION_DAYS -delete
|
||||
31
scripts/backup-ct105-vectors.sh
Normal file
31
scripts/backup-ct105-vectors.sh
Normal file
@@ -0,0 +1,31 @@
|
||||
#!/bin/bash
|
||||
# Копирование векторов RAG (vectors.npz и др.) из контейнера 105.
|
||||
# Запускать на хосте Proxmox под root. Использует pct exec.
|
||||
# Результат: /mnt/backup/other/ct105-vectors/vectors-YYYYMMDD-HHMM.tar.gz
|
||||
set -e
|
||||
|
||||
CT_ID=105
|
||||
REMOTE_PATH="/home/rag-service/data/vectors"
|
||||
BACKUP_DIR="/mnt/backup/other/ct105-vectors"
|
||||
RETENTION_DAYS=14
|
||||
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
echo "Запускайте под root."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
DATE=$(date +%Y%m%d-%H%M)
|
||||
OUTPUT="$BACKUP_DIR/vectors-$DATE.tar.gz"
|
||||
|
||||
pct exec $CT_ID -- tar cf - -C /home/rag-service/data vectors 2>/dev/null | gzip > "$OUTPUT"
|
||||
|
||||
if [ -s "$OUTPUT" ]; then
|
||||
echo "Создан: $OUTPUT ($(du -h "$OUTPUT" | cut -f1))"
|
||||
else
|
||||
echo "Ошибка: архив пустой или каталог недоступен."
|
||||
rm -f "$OUTPUT"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
find "$BACKUP_DIR" -name 'vectors-*.tar.gz' -mtime +$RETENTION_DAYS -delete
|
||||
23
scripts/backup-etc-pve.sh
Normal file
23
scripts/backup-etc-pve.sh
Normal file
@@ -0,0 +1,23 @@
|
||||
#!/bin/bash
|
||||
# Бэкап /etc/pve и конфигов хоста (interfaces, hosts, resolv.conf).
|
||||
# Запускать на хосте Proxmox под root. Cron: 0 3 * * * (после основного backup job в 02:00).
|
||||
set -e
|
||||
|
||||
BACKUP_ROOT="/mnt/backup/proxmox/etc-pve"
|
||||
RETENTION_DAYS=30
|
||||
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
echo "Запускайте под root."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$BACKUP_ROOT"
|
||||
DATE=$(date +%Y%m%d-%H%M)
|
||||
|
||||
tar -czf "$BACKUP_ROOT/etc-pve-$DATE.tar.gz" -C / etc/pve
|
||||
tar -czf "$BACKUP_ROOT/etc-host-configs-$DATE.tar.gz" -C / etc/network/interfaces etc/hosts etc/resolv.conf 2>/dev/null || true
|
||||
|
||||
chmod 600 "$BACKUP_ROOT"/etc-pve-*.tar.gz "$BACKUP_ROOT"/etc-host-configs-*.tar.gz 2>/dev/null || true
|
||||
|
||||
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
|
||||
21
scripts/backup-immich-photos.sh
Normal file
21
scripts/backup-immich-photos.sh
Normal file
@@ -0,0 +1,21 @@
|
||||
#!/bin/bash
|
||||
# Копирование библиотеки фото Immich (оригиналы) с VM 200 на диск бэкапов хоста.
|
||||
# Запускать на хосте Proxmox под root. Требуется SSH без пароля: root → admin@192.168.1.200.
|
||||
# Результат: /mnt/backup/photos/library/ (зеркало /mnt/data/library с VM 200).
|
||||
# Без --delete: удалённые в Immich фото в бэкапе остаются (страховка).
|
||||
set -e
|
||||
|
||||
VM_SSH="admin@192.168.1.200"
|
||||
REMOTE_PATH="/mnt/data/library"
|
||||
BACKUP_PATH="/mnt/backup/photos/library"
|
||||
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
echo "Запускайте под root."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$BACKUP_PATH"
|
||||
|
||||
rsync -az --timeout=3600 \
|
||||
--exclude=".stfolder" \
|
||||
"$VM_SSH:$REMOTE_PATH/" "$BACKUP_PATH/"
|
||||
61
scripts/backup-restic-yandex.sh
Normal file
61
scripts/backup-restic-yandex.sh
Normal file
@@ -0,0 +1,61 @@
|
||||
#!/bin/bash
|
||||
# Выгрузка /mnt/backup в Yandex Object Storage (S3) через restic.
|
||||
# Запускать на хосте Proxmox под root.
|
||||
# Перед первым запуском: установить restic, создать /root/.restic-yandex.env и /root/.restic-password, выполнить restic init.
|
||||
# Cron: 0 4 * * * (04:00, после окна 01:00–03:30; 05:00 зарезервировано под перезагрузку).
|
||||
set -e
|
||||
|
||||
ENV_FILE="/root/.restic-yandex.env"
|
||||
BACKUP_PATH="/mnt/backup"
|
||||
# Исключаем служебные каталоги
|
||||
EXCLUDE_OPTS=(--exclude="$BACKUP_PATH/lost+found")
|
||||
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
echo "Запускайте под root."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -f "$ENV_FILE" ]; then
|
||||
echo "Нет файла $ENV_FILE. Скопируйте restic-yandex-env.example и задайте ключи."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# shellcheck source=/dev/null
|
||||
source "$ENV_FILE"
|
||||
|
||||
for var in RESTIC_REPOSITORY AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY; do
|
||||
if [ -z "${!var}" ]; then
|
||||
echo "В $ENV_FILE не задано: $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
|
||||
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."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Restic backup: $BACKUP_PATH -> $RESTIC_REPOSITORY"
|
||||
restic backup "$BACKUP_PATH" "${EXCLUDE_OPTS[@]}" --quiet
|
||||
|
||||
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
|
||||
|
||||
echo "Restic backup done."
|
||||
130
scripts/backup-setup-sdb1-mount.sh
Normal file
130
scripts/backup-setup-sdb1-mount.sh
Normal file
@@ -0,0 +1,130 @@
|
||||
#!/bin/bash
|
||||
# Шаг 1 бэкапов: разметка 1 ТБ на /dev/sdb1, ФС ext4, монтирование в /mnt/backup, структура каталогов.
|
||||
# Запускать на хосте Proxmox под root.
|
||||
#
|
||||
# Использование:
|
||||
# ./backup-setup-sdb1-mount.sh --yes # разметить (если нужно), смонтировать, создать каталоги
|
||||
# ./backup-setup-sdb1-mount.sh --structure-only # только fstab, mount и каталоги (раздел уже есть)
|
||||
# ./backup-setup-sdb1-mount.sh # вывод действий без выполнения (без --yes)
|
||||
#
|
||||
set -e
|
||||
|
||||
BACKUP_MOUNT="/mnt/backup"
|
||||
DEV_DISK="/dev/sdb"
|
||||
DEV_PART="/dev/sdb1"
|
||||
# 1 ТБ на диске 2 ТБ — первая половина
|
||||
PART_END="50%"
|
||||
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
echo "Запускайте под root."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$(uname -s)" != "Linux" ]; then
|
||||
echo "Скрипт предназначен для запуска на хосте Proxmox (Linux)."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
DO_IT=false
|
||||
STRUCTURE_ONLY=false
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--yes) DO_IT=true ;;
|
||||
--structure-only) STRUCTURE_ONLY=true ;;
|
||||
esac
|
||||
done
|
||||
|
||||
echo "=== Шаг 1: хранилище бэкапов (sdb1 → ${BACKUP_MOUNT}) ==="
|
||||
echo ""
|
||||
|
||||
# Проверка наличия диска
|
||||
if [ ! -b "$DEV_DISK" ]; then
|
||||
echo "Ошибка: диск $DEV_DISK не найден."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Проверка: раздел уже есть и примонтирован?
|
||||
if mountpoint -q "$BACKUP_MOUNT" 2>/dev/null; then
|
||||
echo "$BACKUP_MOUNT уже примонтирован. Создаём только структуру каталогов (если нет)."
|
||||
DO_PART=false
|
||||
DO_MOUNT=false
|
||||
elif [ -b "$DEV_PART" ]; then
|
||||
echo "Раздел $DEV_PART уже существует."
|
||||
if [ "$STRUCTURE_ONLY" = true ]; then
|
||||
DO_PART=false
|
||||
DO_MOUNT=true
|
||||
else
|
||||
echo "Пропускаем разметку. Добавим fstab и монтирование."
|
||||
DO_PART=false
|
||||
DO_MOUNT=true
|
||||
fi
|
||||
else
|
||||
DO_PART=true
|
||||
DO_MOUNT=true
|
||||
fi
|
||||
|
||||
# --- Разметка (при необходимости) ---
|
||||
if [ "$DO_PART" = true ]; then
|
||||
echo ""
|
||||
echo "Будет выполнено:"
|
||||
echo " 1. Создание GPT на $DEV_DISK"
|
||||
echo " 2. Создание раздела $DEV_PART до $PART_END (1 ТБ)"
|
||||
echo " 3. Создание ФС ext4 на $DEV_PART"
|
||||
if [ "$DO_IT" != true ]; then
|
||||
echo ""
|
||||
echo "Для выполнения добавьте флаг: $0 --yes"
|
||||
exit 0
|
||||
fi
|
||||
echo ""
|
||||
parted -s "$DEV_DISK" mklabel gpt
|
||||
parted -s "$DEV_DISK" mkpart primary ext4 0% "$PART_END"
|
||||
partprobe "$DEV_DISK" || true
|
||||
sleep 1
|
||||
mkfs.ext4 -L backup "$DEV_PART"
|
||||
echo "Разметка и ФС созданы."
|
||||
fi
|
||||
|
||||
# --- Монтирование ---
|
||||
mkdir -p "$BACKUP_MOUNT"
|
||||
|
||||
if [ "$DO_MOUNT" = true ] && ! mountpoint -q "$BACKUP_MOUNT" 2>/dev/null; then
|
||||
UUID=$(blkid -s UUID -o value "$DEV_PART")
|
||||
if [ -z "$UUID" ]; then
|
||||
echo "Ошибка: не удалось получить UUID для $DEV_PART"
|
||||
exit 1
|
||||
fi
|
||||
if ! grep -q "UUID=$UUID" /etc/fstab 2>/dev/null; then
|
||||
if [ "$DO_IT" != true ] && [ "$STRUCTURE_ONLY" != true ]; then
|
||||
echo ""
|
||||
echo "Добавьте в /etc/fstab:"
|
||||
echo " UUID=$UUID $BACKUP_MOUNT ext4 defaults,nofail 0 2"
|
||||
echo ""
|
||||
echo "Затем: mount $BACKUP_MOUNT"
|
||||
echo "Для автоматического выполнения: $0 --yes или $0 --structure-only"
|
||||
exit 0
|
||||
fi
|
||||
echo "UUID=$UUID $BACKUP_MOUNT ext4 defaults,nofail 0 2" >> /etc/fstab
|
||||
echo "Добавлена запись в /etc/fstab."
|
||||
fi
|
||||
mount "$BACKUP_MOUNT"
|
||||
echo "Смонтировано: $BACKUP_MOUNT"
|
||||
fi
|
||||
|
||||
# --- Структура каталогов (всегда, если примонтировано) ---
|
||||
if ! mountpoint -q "$BACKUP_MOUNT" 2>/dev/null; then
|
||||
echo "Ошибка: $BACKUP_MOUNT не примонтирован. Сначала смонтируйте раздел."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$BACKUP_MOUNT/proxmox/dump"
|
||||
mkdir -p "$BACKUP_MOUNT/proxmox/etc-pve"
|
||||
mkdir -p "$BACKUP_MOUNT/restic/local"
|
||||
mkdir -p "$BACKUP_MOUNT/photos"
|
||||
mkdir -p "$BACKUP_MOUNT/vps"
|
||||
mkdir -p "$BACKUP_MOUNT/other"
|
||||
|
||||
echo ""
|
||||
echo "Структура каталогов создана:"
|
||||
ls -la "$BACKUP_MOUNT"
|
||||
echo ""
|
||||
echo "Готово. Точка монтирования: $BACKUP_MOUNT"
|
||||
33
scripts/backup-vaultwarden-data.sh
Normal file
33
scripts/backup-vaultwarden-data.sh
Normal file
@@ -0,0 +1,33 @@
|
||||
#!/bin/bash
|
||||
# Копирование данных Vaultwarden (пароли) из контейнера 103 на диск бэкапов.
|
||||
# Для выгрузки в Yandex — включить каталог в источники restic.
|
||||
# Запускать на хосте Proxmox под root. Использует pct exec.
|
||||
# Результат: /mnt/backup/other/vaultwarden/vaultwarden-data-YYYYMMDD-HHMM.tar.gz
|
||||
set -e
|
||||
|
||||
CT_ID=103
|
||||
REMOTE_PATH="/opt/docker/vaultwarden/data"
|
||||
BACKUP_DIR="/mnt/backup/other/vaultwarden"
|
||||
RETENTION_DAYS=14
|
||||
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
echo "Запускайте под root."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
DATE=$(date +%Y%m%d-%H%M)
|
||||
OUTPUT="$BACKUP_DIR/vaultwarden-data-$DATE.tar.gz"
|
||||
|
||||
pct exec $CT_ID -- tar cf - -C /opt/docker/vaultwarden data 2>/dev/null | gzip > "$OUTPUT"
|
||||
|
||||
if [ -s "$OUTPUT" ]; then
|
||||
chmod 600 "$OUTPUT"
|
||||
echo "Создан: $OUTPUT ($(du -h "$OUTPUT" | cut -f1))"
|
||||
else
|
||||
echo "Ошибка: архив пустой или каталог недоступен."
|
||||
rm -f "$OUTPUT"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
find "$BACKUP_DIR" -name 'vaultwarden-data-*.tar.gz' -mtime +$RETENTION_DAYS -delete
|
||||
41
scripts/backup-vm200-pgdump.sh
Normal file
41
scripts/backup-vm200-pgdump.sh
Normal file
@@ -0,0 +1,41 @@
|
||||
#!/bin/bash
|
||||
# Логический бэкап PostgreSQL (Immich) с VM 200.
|
||||
# Запускать на хосте Proxmox под root. Требуется SSH без пароля: root@host → admin@192.168.1.200
|
||||
# (ssh-copy-id или ключ в ~/.ssh).
|
||||
#
|
||||
# На VM 200 должен быть установлен скрипт /opt/immich/scripts/backup-db.sh (см. immich-pgdump-remote.sh).
|
||||
# Результат: /mnt/backup/databases/vm200-immich/immich-db-YYYYMMDD-HHMM.sql.gz
|
||||
#
|
||||
set -e
|
||||
|
||||
VM_SSH="admin@192.168.1.200"
|
||||
REMOTE_SCRIPT="/opt/immich/scripts/backup-db.sh"
|
||||
BACKUP_DIR="/mnt/backup/databases/vm200-immich"
|
||||
RETENTION_DAYS=14
|
||||
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
echo "Запускайте под root."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
DATE=$(date +%Y%m%d-%H%M)
|
||||
OUTPUT="$BACKUP_DIR/immich-db-$DATE.sql.gz"
|
||||
|
||||
if ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new "$VM_SSH" "test -x $REMOTE_SCRIPT"; then
|
||||
ssh -o ConnectTimeout=30 "$VM_SSH" "sudo $REMOTE_SCRIPT" | gzip > "$OUTPUT"
|
||||
else
|
||||
echo "На VM 200 не найден скрипт $REMOTE_SCRIPT. Скопируйте туда scripts/immich-pgdump-remote.sh и сделайте исполняемым."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -s "$OUTPUT" ]; then
|
||||
echo "Создан: $OUTPUT ($(du -h "$OUTPUT" | cut -f1))"
|
||||
else
|
||||
echo "Ошибка: дамп пустой или не создан."
|
||||
rm -f "$OUTPUT"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Удалить дампы старше RETENTION_DAYS
|
||||
find "$BACKUP_DIR" -name 'immich-db-*.sql.gz' -mtime +$RETENTION_DAYS -delete
|
||||
73
scripts/backup-vps-miran.sh
Normal file
73
scripts/backup-vps-miran.sh
Normal file
@@ -0,0 +1,73 @@
|
||||
#!/bin/bash
|
||||
# Бэкап VPS Миран (telegram-helper-bot): БД, voice_users и контент из S3 (Miran).
|
||||
# Запускать на хосте Proxmox под root.
|
||||
# Требуется: SSH без пароля с хоста к deploy@185.147.80.190 -p 15722 (ключ в ~/.ssh).
|
||||
# Для S3: установить aws cli и задать креды в /root/.vps-miran-s3.env (см. ниже).
|
||||
# Результат: /mnt/backup/vps/miran/{db,voice_users,s3}.
|
||||
set -e
|
||||
|
||||
VPS_HOST="185.147.80.190"
|
||||
VPS_USER="deploy"
|
||||
VPS_PORT="15722"
|
||||
VPS_SSH="${VPS_USER}@${VPS_HOST}"
|
||||
SSH_OPTS=(-o ConnectTimeout=15 -o BatchMode=yes -p "$VPS_PORT")
|
||||
RSYNC_SSH="ssh -p $VPS_PORT"
|
||||
|
||||
REMOTE_DB="/home/prod/bots/telegram-helper-bot/database/tg-bot-database.db"
|
||||
REMOTE_VOICE="/home/prod/bots/telegram-helper-bot/voice_users"
|
||||
BACKUP_ROOT="/mnt/backup/vps/miran"
|
||||
RETENTION_DAYS=14
|
||||
|
||||
# S3 (опционально): задать в /root/.vps-miran-s3.env:
|
||||
# S3_ACCESS_KEY=...
|
||||
# S3_SECRET_KEY=...
|
||||
# S3_BUCKET_NAME=telegram-helper-bot
|
||||
S3_ENV="/root/.vps-miran-s3.env"
|
||||
S3_ENDPOINT="https://api.s3.miran.ru"
|
||||
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
echo "Запускайте под root."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$BACKUP_ROOT/db" "$BACKUP_ROOT/voice_users" "$BACKUP_ROOT/s3"
|
||||
|
||||
# ---- 1) Копирование БД с VPS ----
|
||||
DATE=$(date +%Y%m%d)
|
||||
DB_DEST="$BACKUP_ROOT/db/tg-bot-database-$DATE.db"
|
||||
if rsync -az -e "$RSYNC_SSH" --timeout=60 "${VPS_SSH}:${REMOTE_DB}" "$DB_DEST" 2>/dev/null; then
|
||||
echo "БД скопирована: $DB_DEST ($(du -h "$DB_DEST" | cut -f1))"
|
||||
else
|
||||
echo "Предупреждение: не удалось скопировать БД с VPS (проверьте SSH и путь)."
|
||||
fi
|
||||
find "$BACKUP_ROOT/db" -name 'tg-bot-database-*.db' -mtime +$RETENTION_DAYS -delete
|
||||
|
||||
# ---- 2) Копирование voice_users с VPS ----
|
||||
if rsync -az -e "$RSYNC_SSH" --timeout=600 "${VPS_SSH}:${REMOTE_VOICE}/" "$BACKUP_ROOT/voice_users/" 2>/dev/null; then
|
||||
echo "voice_users обновлён: $BACKUP_ROOT/voice_users/"
|
||||
else
|
||||
echo "Предупреждение: не удалось скопировать voice_users с VPS."
|
||||
fi
|
||||
|
||||
# ---- 3) Выгрузка S3 (Miran) в локальную копию ----
|
||||
if [ -f "$S3_ENV" ]; then
|
||||
# shellcheck source=/dev/null
|
||||
source "$S3_ENV"
|
||||
if [ -n "$S3_ACCESS_KEY" ] && [ -n "$S3_SECRET_KEY" ] && [ -n "$S3_BUCKET_NAME" ]; then
|
||||
if command -v aws >/dev/null 2>&1; then
|
||||
export AWS_ACCESS_KEY_ID="$S3_ACCESS_KEY"
|
||||
export AWS_SECRET_ACCESS_KEY="$S3_SECRET_KEY"
|
||||
if aws s3 sync "s3://${S3_BUCKET_NAME}" "$BACKUP_ROOT/s3/" --endpoint-url "$S3_ENDPOINT" --no-progress 2>/dev/null; then
|
||||
echo "S3 синхронизирован: $BACKUP_ROOT/s3/"
|
||||
else
|
||||
echo "Предупреждение: aws s3 sync завершился с ошибкой."
|
||||
fi
|
||||
else
|
||||
echo "Предупреждение: aws cli не установлен — S3 не бэкапится. Установите: apt install awscli."
|
||||
fi
|
||||
else
|
||||
echo "Предупреждение: в $S3_ENV заданы не все переменные (S3_ACCESS_KEY, S3_SECRET_KEY, S3_BUCKET_NAME)."
|
||||
fi
|
||||
else
|
||||
echo "Подсказка: для бэкапа S3 создайте $S3_ENV с S3_ACCESS_KEY, S3_SECRET_KEY, S3_BUCKET_NAME."
|
||||
fi
|
||||
14
scripts/immich-pgdump-remote.sh
Normal file
14
scripts/immich-pgdump-remote.sh
Normal file
@@ -0,0 +1,14 @@
|
||||
#!/bin/bash
|
||||
# Логический дамп БД Immich (PostgreSQL). Запускать на VM 200 (admin или root).
|
||||
# Установить на VM: /opt/immich/scripts/backup-db.sh
|
||||
# Использует .env из /opt/immich (DB_USERNAME, DB_DATABASE_NAME).
|
||||
set -e
|
||||
|
||||
cd /opt/immich
|
||||
[ -f .env ] || { echo "No .env in /opt/immich" >&2; exit 1; }
|
||||
set -a
|
||||
source .env
|
||||
set +a
|
||||
|
||||
# В compose сервис БД называется "database", не "immich_postgres"
|
||||
docker compose exec -T database pg_dump -U "${DB_USERNAME}" "${DB_DATABASE_NAME}"
|
||||
74
scripts/setup-vps-miran-backup-on-proxmox.sh
Normal file
74
scripts/setup-vps-miran-backup-on-proxmox.sh
Normal file
@@ -0,0 +1,74 @@
|
||||
#!/bin/bash
|
||||
# Однократная настройка бэкапа VPS Миран на хосте Proxmox.
|
||||
# Запускать на Proxmox под root. Перед запуском скопировать сюда backup-vps-miran.sh в /root/scripts/.
|
||||
# Делает: SSH-ключ (если нет), awscli, /root/.vps-miran-s3.env, cron.
|
||||
set -e
|
||||
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
echo "Запускайте под root."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SCRIPT_DIR="/root/scripts"
|
||||
BACKUP_SCRIPT="$SCRIPT_DIR/backup-vps-miran.sh"
|
||||
S3_ENV="/root/.vps-miran-s3.env"
|
||||
CRON_ENTRY="30 1 * * * $BACKUP_SCRIPT"
|
||||
VPS_DEPLOY="deploy@185.147.80.190"
|
||||
VPS_PORT="15722"
|
||||
|
||||
# ---- 1) SSH-ключ для доступа к VPS ----
|
||||
if [ ! -f /root/.ssh/id_ed25519 ] && [ ! -f /root/.ssh/id_rsa ]; then
|
||||
mkdir -p /root/.ssh
|
||||
ssh-keygen -t ed25519 -f /root/.ssh/id_ed25519 -N "" -q
|
||||
echo "Создан новый SSH-ключ. Добавьте публичный ключ на VPS (пользователь deploy):"
|
||||
echo ""
|
||||
cat /root/.ssh/id_ed25519.pub
|
||||
echo ""
|
||||
echo "На VPS выполните (или вставьте вывод выше в ~/.ssh/authorized_keys):"
|
||||
echo " ssh -p $VPS_PORT $VPS_DEPLOY 'mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys' < /root/.ssh/id_ed25519.pub"
|
||||
else
|
||||
echo "SSH-ключ уже есть. Публичный ключ (должен быть в authorized_keys на VPS):"
|
||||
[ -f /root/.ssh/id_ed25519.pub ] && cat /root/.ssh/id_ed25519.pub || cat /root/.ssh/id_rsa.pub
|
||||
fi
|
||||
|
||||
# ---- 2) awscli для S3 ----
|
||||
if ! command -v aws >/dev/null 2>&1; then
|
||||
echo "Установка awscli..."
|
||||
apt-get update -qq && apt-get install -y -qq awscli
|
||||
echo "awscli установлен."
|
||||
else
|
||||
echo "awscli уже установлен."
|
||||
fi
|
||||
|
||||
# ---- 3) Креды S3 (Miran) ----
|
||||
if [ ! -f "$S3_ENV" ]; then
|
||||
cat > "$S3_ENV" << 'ENVFILE'
|
||||
S3_ACCESS_KEY=j3tears100@gmail.com
|
||||
S3_SECRET_KEY=wQ1-6sZEPs92sbZTSf96
|
||||
S3_BUCKET_NAME=9829-telegram-helper-bot
|
||||
ENVFILE
|
||||
chmod 600 "$S3_ENV"
|
||||
echo "Создан $S3_ENV с кредами S3 Miran."
|
||||
else
|
||||
echo "Файл $S3_ENV уже существует, не перезаписываю."
|
||||
fi
|
||||
|
||||
# ---- 4) Скрипт бэкапа и права ----
|
||||
mkdir -p "$SCRIPT_DIR"
|
||||
if [ -f "$BACKUP_SCRIPT" ]; then
|
||||
chmod +x "$BACKUP_SCRIPT"
|
||||
echo "Скрипт бэкапа: $BACKUP_SCRIPT (исполняемый)."
|
||||
else
|
||||
echo "Внимание: $BACKUP_SCRIPT не найден. Скопируйте backup-vps-miran.sh в $SCRIPT_DIR/ и снова запустите этот setup."
|
||||
fi
|
||||
|
||||
# ---- 5) Cron ----
|
||||
if crontab -l 2>/dev/null | grep -qF "backup-vps-miran.sh"; then
|
||||
echo "Запись cron для backup-vps-miran.sh уже есть."
|
||||
else
|
||||
(crontab -l 2>/dev/null || true; echo "$CRON_ENTRY") | crontab -
|
||||
echo "Добавлен cron: $CRON_ENTRY"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Готово. Проверка SSH на VPS: ssh -p $VPS_PORT $VPS_DEPLOY 'echo ok'"
|
||||
94
scripts/vps-metrics-api.py
Normal file
94
scripts/vps-metrics-api.py
Normal file
@@ -0,0 +1,94 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Скрипт метрик для VPS: CPU, RAM, диск.
|
||||
Запуск: python3 vps-metrics-api.py [--port 3497]
|
||||
Установи на каждый VPS и открой порт в файрволе (3497/tcp).
|
||||
|
||||
Systemd (опционально):
|
||||
sudo cp vps-metrics-api.py /usr/local/bin/
|
||||
sudo chmod +x /usr/local/bin/vps-metrics-api.py
|
||||
Создай юнит /etc/systemd/system/vps-metrics.service с ExecStart=/usr/bin/python3 /usr/local/bin/vps-metrics-api.py
|
||||
sudo systemctl enable --now vps-metrics
|
||||
"""
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
from http.server import HTTPServer, BaseHTTPRequestHandler
|
||||
|
||||
PORT = 3497
|
||||
|
||||
|
||||
def get_metrics():
|
||||
"""Собирает метрики из /proc и df. Только stdlib."""
|
||||
out = {}
|
||||
try:
|
||||
# Load average (1 min)
|
||||
with open("/proc/loadavg") as f:
|
||||
parts = f.read().strip().split()
|
||||
out["load_1"] = round(float(parts[0]), 2)
|
||||
# Memory: MemTotal, MemAvailable
|
||||
mem = {}
|
||||
with open("/proc/meminfo") as f:
|
||||
for line in f:
|
||||
if ":" in line:
|
||||
k, v = line.strip().split(":", 1)
|
||||
mem[k.strip()] = int(v.strip().split()[0]) # kB
|
||||
total_kb = mem.get("MemTotal", 0)
|
||||
avail_kb = mem.get("MemAvailable", mem.get("MemFree", 0))
|
||||
used_kb = total_kb - avail_kb
|
||||
out["memory_total_gb"] = round(total_kb / 1024 / 1024, 2)
|
||||
out["memory_used_gb"] = round(used_kb / 1024 / 1024, 2)
|
||||
out["memory_percent"] = round(100 * used_kb / total_kb, 1) if total_kb else 0
|
||||
out["memory.status"] = f"{out['memory_used_gb']:.1f} / {out['memory_total_gb']:.1f} GB"
|
||||
# Disk: root /
|
||||
result = subprocess.run(
|
||||
["df", "-k", "/"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=5,
|
||||
)
|
||||
if result.returncode == 0 and result.stdout:
|
||||
lines = result.stdout.strip().split("\n")
|
||||
if len(lines) >= 2:
|
||||
parts = lines[1].split()
|
||||
if len(parts) >= 4:
|
||||
total_kb = int(parts[1])
|
||||
used_kb = int(parts[2])
|
||||
avail_kb = int(parts[3])
|
||||
total_gb = total_kb / 1024 / 1024
|
||||
used_gb = used_kb / 1024 / 1024
|
||||
avail_gb = avail_kb / 1024 / 1024
|
||||
out["disk_total_gb"] = round(total_gb, 1)
|
||||
out["disk_used_gb"] = round(used_gb, 1)
|
||||
out["disk_free_gb"] = round(avail_gb, 1)
|
||||
out["disk_percent"] = round(100 * used_kb / total_kb, 1) if total_kb else 0
|
||||
out["disk.status"] = f"{out['disk_free_gb']:.1f} / {out['disk_total_gb']:.1f} GB свободно"
|
||||
# Uptime
|
||||
with open("/proc/uptime") as f:
|
||||
out["uptime_seconds"] = int(float(f.read().split()[0]))
|
||||
except Exception as e:
|
||||
out["error"] = str(e)
|
||||
return out
|
||||
|
||||
|
||||
class Handler(BaseHTTPRequestHandler):
|
||||
def do_GET(self):
|
||||
self.send_response(200)
|
||||
self.send_header("Content-Type", "application/json")
|
||||
self.end_headers()
|
||||
self.wfile.write(json.dumps(get_metrics(), ensure_ascii=False).encode())
|
||||
|
||||
def log_message(self, *args):
|
||||
pass
|
||||
|
||||
|
||||
def main():
|
||||
port = PORT
|
||||
if len(sys.argv) > 1 and sys.argv[1] == "--port" and len(sys.argv) > 2:
|
||||
port = int(sys.argv[2])
|
||||
print(f"VPS metrics API: http://0.0.0.0:{port}", file=sys.stderr)
|
||||
HTTPServer(("0.0.0.0", port), Handler).serve_forever()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user