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:
2026-02-26 20:14:30 +03:00
parent cf212cefc5
commit feaa31f702
36 changed files with 1272 additions and 41 deletions

View 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

View 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

View 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

View 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
View 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

View 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/"

View 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:0003: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."

View 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"

View 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

View 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

View 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

View 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}"

View 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'"

View 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()