diff --git a/docs/11-backup.md b/docs/11-backup.md index e525941..f514617 100644 --- a/docs/11-backup.md +++ b/docs/11-backup.md @@ -28,7 +28,7 @@ | **Расписание** | ✅ | 2 раза в день (02:30 и 22:30) | | **Retention** | ✅ | 14 последних, 7 ежедневных, 4 недельных, 4 месячных | | **Режим** | ✅ | Snapshot (без остановки сервисов) | -| **Уведомления** | ✅ | Telegram-бот через прокси (XRay + Redsocks) | +| **Уведомления** | ✅ | Telegram-бот через Python-реле (порт 8085) | ### 📊 Архитектура @@ -39,7 +39,8 @@ graph TD A -->|Network| D["Proxmox Backup Server
Olimpbs 192.168.1.199"] D --> E["Datastore: olimpbkp
1 TB LVM"] E --> F["/var/lib/proxmox-backup/olimpbkp"] - D -->|HTTPS via proxy| G["Telegram API
api.telegram.org"] + D -->|Python Relay 8085| G["Telegram API
api.telegram.org"] + G -->|SOCKS5 1080| H["XRay Proxy
2.27.50.20:2054"] ``` --- @@ -278,248 +279,357 @@ proxmox-backup-client verify ct/201/2026-04-11T13:19:03Z \ --- -## 📦 Уведомления в Telegram +## 📦 Уведомления в Telegram (через Python-реле) ### 🎯 Цель -Получать уведомления в Telegram об успешных/неудачных бэкапах с Proxmox Backup Server, без изменений на гипервизоре. +Получать красивые уведомления в Telegram о статусе бэкапов с форматированием времени и эмодзи. ### 📋 Предварительные требования -- PBS сервер (192.168.1.199) с доступом в интернет через прокси +- Сервер с доступом в интернет через прокси (XRay SOCKS5) - Telegram бот: токен и chat_id -- Прокси: VLESS+Reality на `2.27.50.20:2054` +- Python 3 установлен -### 🔧 Настройка прокси (XRay + Redsocks + iptables) +--- + +### 🔧 Настройка Python-реле (универсальный способ) + +**1. Создай скрипт реле:** -**1. Установка XRay клиента** ```bash -# Установка зависимостей -apt update && apt install unzip curl iptables -y +cat > /usr/local/bin/telegram-relay.py << 'EOF' +#!/usr/bin/env python3 +import socket +import subprocess +import sys +import re -# Скачиваем XRay -cd /tmp -curl -L -o xray.zip https://github.com/XTLS/Xray-core/releases/latest/download/Xray-linux-64.zip -unzip xray.zip -chmod +x xray -mv xray geoip.dat geosite.dat /usr/local/bin/ +PORT = 8085 +BOT_TOKEN = "7657027552:AAHQ6OGDRbm6wtqh_4GOQr7jd6C0BAQMyF4" +CHAT_ID = "292909723" +PROXY = "socks5://127.0.0.1:1080" -# Проверка -xray version -``` +def format_duration(seconds): + try: + sec = float(seconds) + if sec < 60: + return f"{int(sec)} сек" + elif sec < 3600: + return f"{int(sec // 60)} мин" + elif sec < 86400: + hours = int(sec // 3600) + mins = int((sec % 3600) // 60) + return f"{hours} ч {mins} мин" if mins > 0 else f"{hours} ч" + else: + return f"{int(sec // 86400)} дн" + except: + return str(seconds) -**2. Конфигурация XRay** -```bash -mkdir -p /usr/local/etc/xray +def get_icon(backup): + return "💻" if backup.startswith('vm-') else "📦" if backup.startswith('ct-') else "🗄️" -cat > /usr/local/etc/xray/config.json << 'EOF' -{ - "log": {"loglevel": "warning"}, - "inbounds": [{"port": 1080, "listen": "127.0.0.1", "protocol": "socks", "settings": {"auth": "noauth", "udp": true}}], - "outbounds": [ - { - "protocol": "vless", - "settings": { - "vnext": [{ - "address": "2.27.50.20", - "port": 2054, - "users": [{"id": "68f44a38-396d-48da-b832-79b5dc5716ab", "encryption": "none", "level": 8}] - }] - }, - "streamSettings": { - "network": "xhttp", - "security": "reality", - "realitySettings": { - "serverName": "cloud.zailon.ru", - "publicKey": "TOyddQCTdSpycmO20uiLOqMABuKabpwVhw57tWmvJws", - "shortId": "174fc0", - "spiderX": "/" - }, - "xhttpSettings": { - "host": "", - "path": "/remote.php/dav/upload", - "mode": "stream-one" - } - }, - "tag": "proxy" - }, - {"protocol": "freedom", "tag": "direct"} - ], - "routing": { - "rules": [ - {"type": "field", "ip": ["geoip:private"], "outboundTag": "direct"}, - {"type": "field", "domain": ["api.telegram.org"], "outboundTag": "proxy"} - ] - } -} +def send_telegram(text): + try: + cmd = [ + 'curl', '-sx', PROXY, '-s', + f'https://api.telegram.org/bot{BOT_TOKEN}/sendMessage', + '-H', 'Content-Type: application/json', + '-d', f'{{"chat_id":"{CHAT_ID}","text":"{text}","parse_mode":"Markdown"}}' + ] + subprocess.run(cmd, timeout=10, capture_output=True) + return True + except: + return False + +def handle_client(client_socket, addr): + try: + data = b"" + client_socket.settimeout(2) + while True: + try: + chunk = client_socket.recv(4096) + if not chunk: + break + data += chunk + except socket.timeout: + break + + data = data.decode('utf-8', errors='ignore') + body = data.split('\r\n\r\n', 1)[1] if '\r\n\r\n' in data else data + + backups = re.findall(r'"backup"\s*:\s*"([^"]+)"', body) + statuses = re.findall(r'"status"\s*:\s*"([^"]+)"', body) + values = re.findall(r'value=([\d.]+)', body) + + if not backups: + client_socket.send(b"HTTP/1.1 200 OK\r\n\r\nOK") + return + + # Отправляем порциями по 5 (чтобы влезло в лимит Telegram) + CHUNK = 5 + for i in range(0, len(backups), CHUNK): + chunk_backups = backups[i:i+CHUNK] + text = "🚨 *Backup Alert*\n\n" + + for j, backup in enumerate(chunk_backups): + idx = i + j + status = statuses[idx] if idx < len(statuses) else "unknown" + icon = get_icon(backup) + + if len(values) >= (idx * 2 + 1): + seconds = float(values[idx * 2]) + human_time = format_duration(seconds) + desc = f"Последний бэкап: {human_time} назад" + else: + desc = "Нет данных" + + status_emoji = "🔥" if status == "firing" else "✅" + text += f"{icon} *{backup}*\n⏱ {desc}\nStatus: {status_emoji} *{status}*\n\n" + + send_telegram(text) + + client_socket.send(b"HTTP/1.1 200 OK\r\nContent-Length: 2\r\nConnection: close\r\n\r\nOK") + + except Exception as e: + print(f"Error: {e}", file=sys.stderr) + try: + client_socket.send(b"HTTP/1.1 500 Error\r\n\r\n") + except: + pass + finally: + try: + client_socket.close() + except: + pass + +server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) +server.bind(('0.0.0.0', PORT)) +server.listen(10) +print(f"▶ Relay on {PORT}", file=sys.stderr) + +while True: + client, addr = server.accept() + handle_client(client, addr) EOF +chmod +x /usr/local/bin/telegram-relay.py ``` -**3. Запуск XRay как сервис** +**2. Создай systemd-сервис:** + ```bash -cat > /etc/systemd/system/xray-client.service << 'EOF' +cat > /etc/systemd/system/telegram-relay.service << 'EOF' [Unit] -Description=XRay Client -After=network.target +Description=Telegram Relay via Xray (Python+curl) +After=network.target xray-client.service +Requires=xray-client.service [Service] Type=simple -ExecStart=/usr/local/bin/xray run -config /usr/local/etc/xray/config.json -Restart=on-failure -RestartSec=5 +ExecStart=/usr/bin/python3 /usr/local/bin/telegram-relay.py +Restart=always +RestartSec=3 +User=root +StandardOutput=journal +StandardError=journal [Install] WantedBy=multi-user.target EOF systemctl daemon-reload -systemctl enable --now xray-client -systemctl status xray-client +systemctl enable --now telegram-relay ``` -**4. Установка и настройка Redsocks** +**3. Проверь что работает:** + ```bash -# Установка -apt install redsocks -y +# Статус сервиса +systemctl status telegram-relay -# Конфигурация -cat > /etc/redsocks.conf << 'EOF' -base { - log_debug = off; - log_info = off; - log = stderr; - daemon = on; - redirector = iptables; -} +# Проверка порта +ss -tlnp | grep 8085 -redsocks { - bind = "0.0.0.0:12345"; - relay = "127.0.0.1:1080"; - type = socks5; - autoproxy = 0; - timeout = 10; -} -EOF - -# Запуск -systemctl enable --now redsocks +# Тест +curl -X POST http://127.0.0.1:8085 -d "🎉 Test message" ``` -**5. Настройка iptables для Telegram** +Должно прийти сообщение в Telegram! + +--- + +### 🔧 Настройка в Grafana (если используешь мониторинг) + +**1. Contact Points → Add new:** + +| Поле | Значение | +|------|----------| +| **Name** | `Telegram Relay` | +| **Type** | `Webhook` | +| **URL** | `http://192.168.1.208:8085` | +| **HTTP Method** | `POST` | +| **Max Alerts** | `0` (без лимита) | + +**2. Сообщение (Message):** ```bash -cat > /usr/local/bin/telegram-proxy.sh << 'EOF' -#!/bin/bash -iptables -t nat -F TELEGRAM_PROXY 2>/dev/null -iptables -t nat -X TELEGRAM_PROXY 2>/dev/null -iptables -t nat -N TELEGRAM_PROXY - -# Telegram IP ranges -iptables -t nat -A TELEGRAM_PROXY -p tcp -d 149.154.160.0/20 -j REDIRECT --to-ports 12345 -iptables -t nat -A TELEGRAM_PROXY -p tcp -d 91.108.4.0/22 -j REDIRECT --to-ports 12345 - -iptables -t nat -A OUTPUT -p tcp --dport 443 -j TELEGRAM_PROXY -EOF - -chmod +x /usr/local/bin/telegram-proxy.sh -/usr/local/bin/telegram-proxy.sh +{{ range .Alerts }} +📦 {{ .Labels.backup }} +⏱ {{ .Annotations.description }} +Status: {{ if eq .Status "firing" }}🔥{{ else }}✅{{ end }} {{ .Status }} +{{ end }} ``` -**6. Проверка соединения** +**3. В Alert Rule → Annotations → Description:** ```bash -curl -s "https://api.telegram.org/bot/getMe" +{{ .Value }} ``` -*Ожидаемый результат*: JSON с информацией о боте. -### 🔧 Скрипт уведомлений +*(Python-скрипт сам превратит секунды в "11 ч 35 мин")* + +**4. Протестируй:** +- Contact Point → **Test** +- Должно прийти красивое уведомление + +--- + +### 🔧 Настройка в Proxmox Backup Server (прямые уведомления) + +Если хочешь уведомления напрямую из PBS (без Grafana): + +**1. Создай endpoint (webhook):** -**7. Создание скрипта** ```bash -cat > /usr/local/bin/pbs-backup-notify.sh << 'EOF' -#!/bin/bash - -TOKEN="YOUR_BOT_TOKEN" -CHAT="YOUR_CHAT_ID" -ARCHIVE="/var/log/proxmox-backup/tasks/archive" -STATE="/var/run/pbs-notify-lastline" - -# Получаем последнюю обработанную строку -LAST_LINE=$(cat $STATE 2>/dev/null || echo 0) -CURR_LINE=$(wc -l < $ARCHIVE) - -# Если файл уменьшился - начинаем сначала -if [ "$CURR_LINE" -lt "$LAST_LINE" ]; then - LAST_LINE=0 -fi - -# Читаем новые строки -if [ "$CURR_LINE" -gt "$LAST_LINE" ]; then - tail -n +$((LAST_LINE + 1)) $ARCHIVE | while IFS= read -r line; do - # Только backup задачи - if echo "$line" | grep -q ":backup:"; then - # Извлекаем имя бэкапа (конвертируем \x3a в :) - BACKUP=$(echo "$line" | grep -oP 'backup:\K[^:]+' | sed 's/\\x3a/:/g') - STATUS=$(echo "$line" | grep -oP 'OK|FAILED|ERROR') - TIME=$(date '+%Y-%m-%d %H:%M:%S') - - if [ "$STATUS" = "OK" ]; then - curl -s -X POST "https://api.telegram.org/bot$TOKEN/sendMessage" \ - -H "Content-Type: application/json" \ - -d "{\"chat_id\":\"$CHAT\",\"text\":\"✅ *Backup Success*\\n📦 $BACKUP\\n⏰ $TIME\",\"parse_mode\":\"Markdown\"}" > /dev/null - else - curl -s -X POST "https://api.telegram.org/bot$TOKEN/sendMessage" \ - -H "Content-Type: application/json" \ - -d "{\"chat_id\":\"$CHAT\",\"text\":\"❌ *Backup Failed*\\n📦 $BACKUP\\n⏰ $TIME\\n📝 Status: $STATUS\",\"parse_mode\":\"Markdown\"}" > /dev/null - fi - fi - done -fi - -# Сохраняем позицию -echo $CURR_LINE > $STATE -EOF - -chmod +x /usr/local/bin/pbs-backup-notify.sh +proxmox-backup-manager notification endpoint webhook create telegram \ + --url "http://192.168.1.208:8085" \ + --method post \ + --header "Content-Type: application/json" ``` -**8. Настройка Cron (каждые 5 минут)** +**2. Создай matcher для ошибок:** + ```bash -echo "*/5 * * * * root /usr/local/bin/pbs-backup-notify.sh" >> /etc/cron.d/pbs-notify +proxmox-backup-manager notification matcher create backup-error \ + --endpoint telegram \ + --cal-filter backup \ + --severity error ``` -**9. Инициализация и тест** +**3. Создай matcher для успехов (опционально):** + ```bash -# Сброс состояния для теста -echo "0" > /var/run/pbs-notify-lastline - -# Запуск вручную -/usr/local/bin/pbs-backup-notify.sh - -# Проверка состояния -cat /var/run/pbs-notify-lastline -wc -l < /var/log/proxmox-backup/tasks/archive +proxmox-backup-manager notification matcher create backup-success \ + --endpoint telegram \ + --cal-filter backup \ + --severity info ``` +**4. Протестируй:** + +```bash +proxmox-backup-manager notification target test telegram +``` + +--- + ### 📝 Переменные для замены | Переменная | Описание | Пример | |------------|----------|--------| -| `YOUR_BOT_TOKEN` | Токен Telegram бота | `7657027552:AAHQ6OGDRbm6wtqh_4GOQr7jd6C0BAQMyF4` | -| `YOUR_CHAT_ID` | ID чата для уведомлений | `292909723` | -| `address` | Адрес прокси-сервера | `2.27.50.20` | -| `port` | Порт прокси | `2054` | -| `id` | UUID пользователя VLESS | `68f44a38-396d-48da-b832-79b5dc5716ab` | -| `publicKey` | Публичный ключ Reality | `TOyddQCTdSpycmO20uiLOqMABuKabpwVhw57tWmvJws` | -| `serverName` | Домен для Reality | `cloud.zailon.ru` | +| `BOT_TOKEN` | Токен Telegram бота | `7657027552:AAHQ6OGDRbm6wtqh_4GOQr7jd6C0BAQMyF4` | +| `CHAT_ID` | ID чата для уведомлений | `292909723` | +| `PROXY` | SOCKS5 прокси (XRay) | `socks5://127.0.0.1:1080` | +| `PORT` | Порт реле (локальный) | `8085` | +| `GRAFANA_HOST` | IP сервера с Grafana | `192.168.1.208` | + +--- ### 📁 Файлы конфигурации | Файл | Назначение | |------|------------| -| `/usr/local/etc/xray/config.json` | Конфиг XRay прокси | -| `/etc/redsocks.conf` | Конфиг Redsocks | -| `/usr/local/bin/telegram-proxy.sh` | Правила iptables для Telegram | -| `/usr/local/bin/pbs-backup-notify.sh` | Скрипт уведомлений | -| `/var/run/pbs-notify-lastline` | Позиция последнего прочитанного лога | -| `/etc/cron.d/pbs-notify` | Cron задача | +| `/usr/local/bin/telegram-relay.py` | Python-скрипт реле | +| `/etc/systemd/system/telegram-relay.service` | systemd-юнит | +| `/var/log/syslog` | Логи (через journalctl) | +--- + +### 🧪 Тестирование + +**1. Проверка соединения с Telegram:** + +```bash +curl -sx socks5://127.0.0.1:1080 \ + https://api.telegram.org/bot$BOT_TOKEN/getMe +``` + +**2. Проверка реле:** + +```bash +curl -X POST http://127.0.0.1:8085 \ + -H "Content-Type: application/json" \ + -d '{"text":"✅ Relay is working!"}' +``` + +**3. Просмотр логов:** + +```bash +journalctl -u telegram-relay -f +``` + +--- + +### 🔍 Troubleshooting + +| Проблема | Решение | +|----------|---------| +| Не приходит сообщение | Проверь `journalctl -u telegram-relay -n 20` | +| Ошибка 400 от Telegram | Убери `parse_mode` или экранируй спецсимволы | +| Реле не слушает порт | `systemctl restart telegram-relay` | +| XRay не подключается | Проверь `systemctl status xray-client` | +| Сообщение обрезается | Скрипт разбивает на части по 5 бэкапов — это норма | + +--- + +### 🎨 Формат уведомления + +Пример того, что придёт в Telegram: +```mermaid +🚨 Backup Alert +📦 ct-201 +⏱ Последний бэкап: 11 ч 35 мин назад +Status: 🔥 firing +📦 ct-202 +⏱ Последний бэкап: 11 ч 34 мин назад +Status: 🔥 firing +💻 vm-205 +⏱ Последний бэкап: 11 ч 30 мин назад +Status: ✅ resolved +``` + +**Эмодзи:** +- 💻 — виртуальные машины (vm-*) +- 📦 — LXC контейнеры (ct-*) +- 🔥 — статус firing (проблема) +- ✅ — статус resolved (всё ок) + +--- + +### 🔄 Обновление скрипта + +Если нужно изменить формат или логику: + +```bash +# Останови сервис +systemctl stop telegram-relay + +# Отредактируй скрипт +nano /usr/local/bin/telegram-relay.py + +# Запусти снова +systemctl start telegram-relay + +# Проверь статус +systemctl status telegram-relay +``` --- @@ -536,7 +646,7 @@ wc -l < /var/log/proxmox-backup/tasks/archive - **Доступ**: только из локальной сети (192.168.1.0/24) ### Рекомендации -- ✅ Включить **2FA** для `root@pam` +- ✅ Включить **2FA** для доступа к PBS - ✅ Создать **отдельного пользователя** для бэкапов (не root) - ✅ Настроить **firewall** на PBS сервере - ✅ Регулярно **обновлять** систему (unattended-upgrades) @@ -605,9 +715,9 @@ pvesm update olimpbkp --password <новый_токен> | Проблема | Решение | |----------|---------| | Не приходят уведомления | Проверить `curl -s https://api.telegram.org/bot/getMe` | -| Ошибка iptables | Перезапустить `/usr/local/bin/telegram-proxy.sh` | -| XRay не запускается | `journalctl -u xray-client -n 20 --no-pager` | -| Дубликат уведомлений | Удалить `/var/run/pbs-notify-lastline` и пересоздать | +| Реле не работает | `journalctl -u telegram-relay -n 20 --no-pager` | +| XRay не запускается | `systemctl status xray-client` | +| Дубликат уведомлений | Проверить Max Alerts в Contact Point | ---