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