Update architecture and backup documentation to include Healthchecks integration

Add Healthchecks service details to architecture and backup documentation, including its role as a Dead man's switch for backups. Update backup scripts to utilize systemd timers instead of cron for improved scheduling. Enhance network topology documentation to reflect Healthchecks integration in the VPS Miran setup. This update clarifies backup processes and enhances overall system reliability.
This commit is contained in:
2026-02-28 15:43:39 +03:00
parent 16c254510a
commit 53769e6832
61 changed files with 1697 additions and 39 deletions

View File

@@ -24,6 +24,7 @@
| cloud.katykhin.ru | — | | cloud.katykhin.ru | — |
| docs.katykhin.ru | — | | docs.katykhin.ru | — |
| git.katykhin.ru | — | | git.katykhin.ru | — |
| healthchecks.katykhin.ru | Healthchecks (Dead man's switch для бэкапов; на VPS Миран) |
| home.katykhin.ru | Homepage | | home.katykhin.ru | Homepage |
| immich.katykhin.ru | — | | immich.katykhin.ru | — |
| mini-lm.katykhin.ru | — | | mini-lm.katykhin.ru | — |
@@ -94,8 +95,9 @@ pct create 105 local:vztmpl/debian-12-standard_12.2-1_amd64.tar.zst \
## Дополнительно ## Дополнительно
- **Хост Proxmox:** скрипты, таймеры, пути — [host-proxmox.md](../containers/host-proxmox.md).
- **Схема сети и зависимости:** полная топология (роутер, Proxmox, контейнеры, VPS), таблица IP/доменов, маршруты NPM, кто от кого зависит, единые точки отказа (SPOF). → [Схема сети и зависимости](../network/network-topology.md). - **Схема сети и зависимости:** полная топология (роутер, Proxmox, контейнеры, VPS), таблица IP/доменов, маршруты NPM, кто от кого зависит, единые точки отказа (SPOF). → [Схема сети и зависимости](../network/network-topology.md).
- **Homepage:** на контейнере 103, конфиг сервисов в `/opt/docker/homepage/config/services.yaml` (ссылки на NPM, Invidious, AdGuard, Immich, Galene, Vaultwarden и т.д.). - **Homepage:** на контейнере 103, конфиг сервисов в `/opt/docker/homepage/config/services.yaml` (ссылки на NPM, Invidious, AdGuard, Immich, Galene, Vaultwarden, Healthchecks, Netdata и т.д.).
- **VPN (VPS):** отдельный сервер 185.103.253.99, AmneziaWG для обхода блокировок. → [VPN-сервер (VPS, AmneziaWG)](../vps/vpn-vps-amneziawg.md). - **VPN (VPS):** отдельный сервер 185.103.253.99, AmneziaWG для обхода блокировок. → [VPN-сервер (VPS, AmneziaWG)](../vps/vpn-vps-amneziawg.md).
- **Роутер:** Netcraze Speedster, два WireGuard/AmneziaWG (Германия / США), маршрутизация части трафика через VPN. → [Роутер Netcraze Speedster](../network/router-netcraze-speedster.md). - **Роутер:** Netcraze Speedster, два WireGuard/AmneziaWG (Германия / США), маршрутизация части трафика через VPN. → [Роутер Netcraze Speedster](../network/router-netcraze-speedster.md).
- **VPS Миран (СПБ):** боты (telegram-helper-bot, anonBot), prod-инфраструктура, STUN/TURN для Galene. → [VPS Миран: боты и STUN/TURN](../vps/vps-miran-bots.md). - **VPS Миран (СПБ):** боты (telegram-helper-bot, anonBot), prod-инфраструктура, STUN/TURN для Galene. → [VPS Миран: боты и STUN/TURN](../vps/vps-miran-bots.md).

View File

@@ -8,6 +8,16 @@
Все локальные бэкапы лежат на отдельном диске хоста Proxmox: **/dev/sdb1**, смонтирован в **/mnt/backup**. Все локальные бэкапы лежат на отдельном диске хоста Proxmox: **/dev/sdb1**, смонтирован в **/mnt/backup**.
### Карта дисков (Proxmox host)
| Устройство | Тип | Размер | Использование |
|--------------|------|--------|----------------------------------|
| /dev/nvme0n1 | NVMe | 256 GB | LVM (система, local-lvm) |
| /dev/sda | HDD | 2 TB | ZFS |
| /dev/sdb | SSD | 2 TB | ext4, /mnt/backup |
| /dev/sdc | HDD | 2 TB | ZFS (RAID1 с sda) |
| /dev/sdd | HDD | 8 TB | ext4 (внешний) |
``` ```
/mnt/backup/ /mnt/backup/
├── proxmox/ ├── proxmox/
@@ -36,21 +46,21 @@
| Что | Откуда | Куда (локально) | Когда | Хранение | Уведомление | | Что | Откуда | Куда (локально) | Когда | Хранение | Уведомление |
|-----|--------|------------------|------|----------|--------------| |-----|--------|------------------|------|----------|--------------|
| **VPS Миран (telegram-helper-bot)** | VPS 185.147.80.190: БД `tg-bot-database.db`, каталог `voice_users`, бакет S3 (Miran) | `/mnt/backup/vps/miran/` (db/, voice_users/, s3/) | **01:00** (cron: `backup-vps-miran.sh`) | БД: 14 дней; voice_users и S3 — перезапись | 🖥️ VPS Миран | | **VPS Миран (telegram-helper-bot)** | VPS 185.147.80.190: БД `tg-bot-database.db`, каталог `voice_users`, бакет S3 (Miran) | `/mnt/backup/vps/miran/` (db/, voice_users/, s3/) | **01:00** (timer: `backup-vps-miran`) | БД: 14 дней; voice_users и S3 — перезапись | 🖥️ VPS Миран |
| **БД Nextcloud (PostgreSQL)** | CT 101, контейнер `nextcloud-db-1` в `/opt/nextcloud` | `/mnt/backup/databases/ct101-nextcloud/` | **01:15** (cron: `backup-ct101-pgdump.sh`) | 14 дней | 🗄️ Nextcloud (БД) | | **БД Nextcloud (PostgreSQL)** | CT 101, контейнер `nextcloud-db-1` в `/opt/nextcloud` | `/mnt/backup/databases/ct101-nextcloud/` | **01:15** (timer: `backup-ct101-pgdump`) | 14 дней | 🗄️ Nextcloud (БД) |
| **Оригиналы фото Immich** | VM 200, `/mnt/data/library/` | `/mnt/backup/photos/library/` | **01:30** (cron: `backup-immich-photos.sh`, rsync) | Все копии (без автоудаления) | 📷 Фото Immich (rsync) | | **Оригиналы фото Immich** | VM 200, `/mnt/data/library/` | `/mnt/backup/photos/library/` | **01:30** (timer: `backup-immich-photos`) | Все копии (без автоудаления) | 📷 Фото Immich (rsync) |
| **Конфиги MTProto (VPS Германия)** | VPS 185.103.253.99: mtg.service, nginx (katykhin.store), Let's Encrypt, `/var/www/katykhin.store` | `/mnt/backup/vps/mtproto-germany/` (архивы mtproto-config-*.tar.gz) | **01:45** (cron: `backup-vps-mtproto.sh`) | 14 дней | 🌐 VPS MTProto (DE) | | **Конфиги MTProto (VPS Германия)** | VPS 185.103.253.99: mtg.service, nginx (katykhin.store), Let's Encrypt, `/var/www/katykhin.store` | `/mnt/backup/vps/mtproto-germany/` (архивы mtproto-config-*.tar.gz) | **01:45** (timer: `backup-vps-mtproto`) | 14 дней | 🌐 VPS MTProto (DE) |
| **LXC и VM** | Все выбранные контейнеры (100109) и VM 200 | `/mnt/backup/proxmox/dump/` | **02:00** (задание в Proxmox UI) | По настройкам задания (7 daily, 4 weekly, 6 monthly) | 💾 Backup local (cron 03:00) | | **LXC и VM** | Все выбранные контейнеры (100109) и VM 200 | `/mnt/backup/proxmox/dump/` | **02:00** (задание в Proxmox UI) | По настройкам задания (7 daily, 4 weekly, 6 monthly) | 💾 Backup local (timer 03:00) |
| **Конфиги хоста** | `/etc/pve`, `/etc/network/interfaces`, `/etc/hosts`, `/etc/resolv.conf` | `/mnt/backup/proxmox/etc-pve/` | **02:15** (cron: `backup-etc-pve.sh`) | 30 дней | ⚙️ Конфиги хоста | | **Конфиги хоста** | `/etc/pve`, `/etc/network/interfaces`, `/etc/hosts`, `/etc/resolv.conf` | `/mnt/backup/proxmox/etc-pve/` | **02:15** (timer: `backup-etc-pve`) | 30 дней | ⚙️ Конфиги хоста |
| **БД Paperless (PostgreSQL)** | CT 104, контейнер `paperless-db-1` в `/opt/paperless` | `/mnt/backup/databases/ct104-paperless/` | **02:30** (cron: `backup-ct104-pgdump.sh`) | 14 дней | 🗄️ Paperless (БД) | | **БД Paperless (PostgreSQL)** | CT 104, контейнер `paperless-db-1` в `/opt/paperless` | `/mnt/backup/databases/ct104-paperless/` | **02:30** (timer: `backup-ct104-pgdump`) | 14 дней | 🗄️ Paperless (БД) |
| **Данные Vaultwarden (пароли)** | CT 103, `/opt/docker/vaultwarden/data` | `/mnt/backup/other/vaultwarden/`; каталог в restic → Yandex | **02:45** (cron: `backup-vaultwarden-data.sh`) | 14 дней | 🔐 Vaultwarden | | **Данные Vaultwarden (пароли)** | CT 103, `/opt/docker/vaultwarden/data` | `/mnt/backup/other/vaultwarden/`; каталог в restic → Yandex | **02:45** (timer: `backup-vaultwarden-data`) | 14 дней | 🔐 Vaultwarden |
| **БД Gitea (PostgreSQL)** | CT 103, контейнер `gitea-db-1` в `/opt/gitea` | `/mnt/backup/databases/ct103-gitea/` | **03:00** (cron: `backup-ct103-gitea-pgdump.sh`) | 14 дней | 🗄️ Gitea (БД) | | **БД Gitea (PostgreSQL)** | CT 103, контейнер `gitea-db-1` в `/opt/gitea` | `/mnt/backup/databases/ct103-gitea/` | **03:00** (timer: `backup-ct103-gitea-pgdump`) | 14 дней | 🗄️ Gitea (БД) |
| **БД Immich (PostgreSQL)** | VM 200, контейнер `database` в `/opt/immich` | `/mnt/backup/databases/vm200-immich/` | **03:15** (cron: `backup-vm200-pgdump.sh`) | 14 дней | 🗄️ Immich (БД) | | **БД Immich (PostgreSQL)** | VM 200, контейнер `database` в `/opt/immich` | `/mnt/backup/databases/vm200-immich/` | **03:15** (timer: `backup-vm200-pgdump`) | 14 дней | 🗄️ Immich (БД) |
| **Векторы RAG (CT 105)** | CT 105, `/home/rag-service/data/vectors/` (vectors.npz) | `/mnt/backup/other/ct105-vectors/` | **03:30** (cron: `backup-ct105-vectors.sh`) | 14 дней | 📐 Векторы RAG | | **Векторы RAG (CT 105)** | CT 105, `/home/rag-service/data/vectors/` (vectors.npz) | `/mnt/backup/other/ct105-vectors/` | **03:30** (timer: `backup-ct105-vectors`) | 14 дней | 📐 Векторы RAG |
| **Выгрузка в Yandex (restic)** | `/mnt/backup` без photos | Yandex Object Storage | **04:00** (cron: `backup-restic-yandex.sh`) | 3 daily, 2 weekly, 2 monthly | ☁️ Restic Yandex | | **Выгрузка в Yandex (restic)** | `/mnt/backup` без photos | Yandex Object Storage | **04:00** (timer: `backup-restic-yandex`) | 3 daily, 2 weekly, 2 monthly | ☁️ Restic Yandex |
| **Выгрузка в Yandex (restic, фото)** | `/mnt/backup/photos` | Yandex Object Storage (тот же репо) | **04:10** (cron: `backup-restic-yandex-photos.sh`) | 3 daily, 2 weekly, 2 monthly | 📷 Restic Yandex (photos) | | **Выгрузка в Yandex (restic, фото)** | `/mnt/backup/photos` | Yandex Object Storage (тот же репо) | **04:10** (timer: `backup-restic-yandex-photos`) | 3 daily, 2 weekly, 2 monthly | 📷 Restic Yandex (photos) |
**Окно бэкапов:** внутренние копии — **01:0003:30**; выгрузка в облако — **04:00** (основной restic), **04:10** (restic фото). **05:00** зарезервировано под плановую перезагрузку сервера. Задание vzdump — из веб-интерфейса Proxmox (Центр обработки данных → Резервная копия). **Окно бэкапов:** внутренние копии — **01:0003:30**; выгрузка в облако — **04:00** (основной restic), **04:10** (restic фото). **04:35** — ping Healthchecks (Dead man's switch). **05:00** зарезервировано под плановую перезагрузку сервера. Задание vzdump — из веб-интерфейса Proxmox (Центр обработки данных → Резервная копия).
--- ---
@@ -406,26 +416,31 @@ restic restore SNAPSHOT_ID --target /mnt/backup/restore-db --path /mnt/backup/da
--- ---
## Скрипты на хосте Proxmox ## Скрипты и systemd timers на хосте Proxmox
Бэкапы запускаются через **systemd timers** (миграция с cron). Unit-файлы: `scripts/systemd/`. Копировать на хост: `cp scripts/systemd/*.service scripts/systemd/*.timer /etc/systemd/system/`, затем `systemctl daemon-reload` и `systemctl enable --now <timer>`.
| Скрипт | Назначение | Cron | | Скрипт | Timer | Расписание |
|--------|------------|------| |--------|-------|------------|
| `/root/scripts/backup-vps-miran.sh` | Бэкап VPS Миран: БД бота, voice_users, S3 (Miran) | 0 1 * * * | | `backup-vps-miran.sh` | backup-vps-miran.timer | 01:00 |
| `/root/scripts/backup-ct101-pgdump.sh` | Логический дамп БД Nextcloud из CT 101 | 15 1 * * * | | `backup-ct101-pgdump.sh` | backup-ct101-pgdump.timer | 01:15 |
| `/root/scripts/backup-immich-photos.sh` | Копирование библиотеки фото Immich (rsync с VM 200) | 30 1 * * * | | `backup-immich-photos.sh` | backup-immich-photos.timer | 01:30 |
| `/root/scripts/backup-vps-mtproto.sh` | Копирование конфигов MTProto + сайт с VPS Германия (185.103.253.99) | 45 1 * * * | | `backup-vps-mtproto.sh` | backup-vps-mtproto.timer | 01:45 |
| `/root/scripts/backup-etc-pve.sh` | Бэкап /etc/pve и конфигов хоста | 15 2 * * * | | `backup-etc-pve.sh` | backup-etc-pve.timer | 02:15 |
| `/root/scripts/backup-ct104-pgdump.sh` | Логический дамп БД Paperless из CT 104 | 30 2 * * * | | `backup-ct104-pgdump.sh` | backup-ct104-pgdump.timer | 02:30 |
| `/root/scripts/backup-vaultwarden-data.sh` | Копирование данных Vaultwarden (пароли) из CT 103 | 45 2 * * * | | `backup-vaultwarden-data.sh` | backup-vaultwarden-data.timer | 02:45 |
| `/root/scripts/backup-ct103-gitea-pgdump.sh` | Логический дамп БД Gitea из CT 103 | 0 3 * * * | | `backup-ct103-gitea-pgdump.sh` | backup-ct103-gitea-pgdump.timer | 03:00 |
| `/root/scripts/notify-vzdump-success.sh` | Проверка локального vzdump за последние 2 ч, отправка сводки в Telegram | 0 3 * * * | | `notify-vzdump-success.sh` | notify-vzdump-success.timer | 03:00 |
| `/root/scripts/backup-vm200-pgdump.sh` | Логический дамп БД Immich с VM 200 | 15 3 * * * | | `backup-vm200-pgdump.sh` | backup-vm200-pgdump.timer | 03:15 |
| `/root/scripts/backup-ct105-vectors.sh` | Копирование векторов RAG (vectors.npz) из CT 105 | 30 3 * * * | | `backup-ct105-vectors.sh` | backup-ct105-vectors.timer | 03:30 |
| `/root/scripts/backup-restic-yandex.sh` | Выгрузка /mnt/backup (без photos) в Yandex S3 (restic), retention 3/2/2 | 0 4 * * * | | `backup-restic-yandex.sh` | backup-restic-yandex.timer | 04:00 |
| `/root/scripts/backup-restic-yandex-photos.sh` | Выгрузка только /mnt/backup/photos в Yandex S3 (тот же репо), retention 3/2/2 | 10 4 * * * | | `backup-restic-yandex-photos.sh` | backup-restic-yandex-photos.timer | 04:10 |
| `/root/scripts/notify-telegram.sh` | Шлюз отправки уведомлений в Telegram (вызывают скрипты бэкапов) | — | | `healthcheck-ping.sh` | backup-healthcheck-ping.timer | 04:35 (Healthchecks) |
| `watchdog-timers.sh` | backup-watchdog-timers.timer | 12:00 (проверка failed timers, .ok) |
**Healthcheck-файлы:** при успешном завершении каждый скрипт бэкапа пишет `echo $(date -Iseconds) > /var/run/backup-<name>.ok`. Watchdog проверяет раз в день: если файл старше 24 ч — алерт в Telegram.
**Тест восстановления:** см. [restore-test-manual.md](restore-test-manual.md). Автоматические скрипты: `verify-restore-level1.sh` (restic check, дамп Nextcloud), `verify-vzdump-level2.sh` (vzdump CT 107). Таймеры: `verify-restore-level1-weekly`, `-monthly-check`, `-full-check`, `-monthly-dump`, `verify-vzdump-level2`.
Задание vzdump (LXC/VM) настраивается в Proxmox UI (расписание 02:00). **05:00** оставлено свободным для плановой перезагрузки сервера. Задание vzdump (LXC/VM) настраивается в Proxmox UI (расписание 02:00). **05:00** оставлено свободным для плановой перезагрузки сервера.
@@ -452,7 +467,7 @@ pct exec 103 -- ls -la /opt/docker/vaultwarden/data
pct exec 103 -- tar cf - -C /opt/docker/vaultwarden data | wc -c pct exec 103 -- tar cf - -C /opt/docker/vaultwarden data | wc -c
``` ```
**Запуск из cron и доступ к Vaultwarden (bw):** В cron окружение ограничено: часто `PATH` не содержит `/usr/local/bin`, где обычно установлен `bw`. Скрипты дампов БД (ct101, ct104, ct103) в начале задают `export PATH="/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:${PATH}"`, поэтому при запуске из cron `bw` и `jq` находятся без правки crontab. Нужно: 1) файл с мастер-паролем, например `/root/.bw-master` (chmod 600), и при необходимости переменная `BW_MASTER_PASSWORD_FILE=/root/.bw-master`; 2) один раз с интерактивной сессии: `bw config server https://vault.katykhin.ru`, `bw login` (сохранит сессию в конфиг); 3) при каждом запуске скрипт делает `bw unlock --passwordfile "$BW_MASTER_PASSWORD_FILE" --raw` и подставляет пароль БД. Если вручную дамп идёт, а из cron — нет, проверьте: наличие `/root/.bw-master`, права доступа, что `bw` доступен по этому PATH (запустите скрипт через `env -i PATH=/usr/local/bin:/usr/bin:/bin /root/scripts/backup-ct103-gitea-pgdump.sh` для имитации cron). **Запуск из systemd timer и доступ к Vaultwarden (bw):** В окружении systemd timer `PATH` может не содержать `/usr/local/bin`, где обычно установлен `bw`. Скрипты дампов БД задают `export PATH="/usr/local/bin:..."`, поэтому `bw` и `jq` находятся. Нужно: 1) файл `/root/.bw-master` (chmod 600) с мастер-паролем; 2) один раз: `bw config server https://vault.katykhin.ru`, `bw login`; 3) при каждом запуске скрипт делает `bw unlock --passwordfile /root/.bw-master --raw`. Если вручную дамп идёт, а из таймера — нет, проверьте `/root/.bw-master` и PATH.
### Почему размер дампа меньше размера БД на диске ### Почему размер дампа меньше размера БД на диске
@@ -477,7 +492,7 @@ pct exec 101 -- docker exec nextcloud-db-1 psql -U nextcloud -d nextcloud -t -c
## Уведомления в Telegram ## Уведомления в Telegram
После **успешного** выполнения каждого бэкапа в Telegram отправляется короткое сообщение (заголовок с эмодзи + краткая сводка). Уведомления приходят по завершении соответствующего скрипта; для локального vzdump — по cron в **03:00** (проверка файлов за последние 2 часа). После **успешного** выполнения каждого бэкапа в Telegram отправляется короткое сообщение (заголовок с эмодзи + краткая сводка). Уведомления приходят по завершении соответствующего скрипта; для локального vzdump — таймер `notify-vzdump-success` в **03:00** (проверка файлов за последние 2 часа).
| Заголовок | Когда | Тело сообщения | | Заголовок | Когда | Тело сообщения |
@@ -516,8 +531,6 @@ chmod 600 /root/.telegram-notify.env
Если конфига или кредов нет, шлюз тихо выходит с 0 и не ломает вызывающие скрипты. Если конфига или кредов нет, шлюз тихо выходит с 0 и не ломает вызывающие скрипты.
**Позже** тот же шлюз можно вызывать с VM 200 или с VPS (например по SSH на хост Proxmox) — отдельно не реализовано, архитектура это допускает.
--- ---
## Связанные документы ## Связанные документы
@@ -525,4 +538,8 @@ chmod 600 /root/.telegram-notify.env
- [Vaultwarden и секреты](../vaultwarden-secrets.md) — получение паролей через `bw` для скриптов бэкапов. - [Vaultwarden и секреты](../vaultwarden-secrets.md) — получение паролей через `bw` для скриптов бэкапов.
- [Архитектура](../architecture/architecture.md) — хост, IP, доступ. - [Архитектура](../architecture/architecture.md) — хост, IP, доступ.
- [VM 200 (Immich)](../containers/container-200.md) — сервисы, пути, .env. - [VM 200 (Immich)](../containers/container-200.md) — сервисы, пути, .env.
- [Ручной тест восстановления](restore-test-manual.md) — пошаговые команды для полной проверки restore.
- [Healthchecks на VPS Миран](../vps/healthchecks-miran-setup.md) — Dead man's switch, ping после бэкапов.
- [Netdata на Proxmox](../monitoring/netdata-proxmox-setup.md) — мониторинг CPU, RAM, дисков, алерты в Telegram.
- [SMART и smartd](../monitoring/smartd-setup.md) — мониторинг дисков, уведомления при отклонениях.

View File

@@ -0,0 +1,140 @@
# Ручной тест восстановления (уровень 3)
Пошаговые команды для полной проверки восстановления после потери данных или миграции. Выполнять периодически (раз в 612 месяцев) или после значительных изменений инфраструктуры.
---
## 1. Полный restore на отдельный диск
**Когда нужно:** проверка, что все бэкапы доступны и можно восстановить систему на новом диске.
### Подготовка
1. Подключить диск с достаточным объёмом (например 2 TB) или использовать временный раздел.
2. Смонтировать в `/mnt/restore-test` (или аналогичный путь).
3. Убедиться, что есть креды restic: `/root/.restic-yandex.env`, `/root/.restic-password` или Vaultwarden (объект RESTIC).
### Восстановление из restic (Yandex)
```bash
# Список снимков
set -a; source /root/.restic-yandex.env; set +a
export RESTIC_PASSWORD_FILE=/root/.restic-password
export AWS_DEFAULT_REGION=${AWS_DEFAULT_REGION:-ru-central1}
restic snapshots
# Восстановить основной снимок (без photos) в каталог
restic restore latest --target /mnt/restore-test --path /mnt/backup
# Восстановить фото (отдельный снимок)
restic snapshots | grep photos
restic restore <SNAPSHOT_ID> --target /mnt/restore-test --path /mnt/backup/photos
```
Файлы появятся в `/mnt/restore-test/mnt/backup/`. Проверить наличие:
- `proxmox/dump/dump/` — vzdump
- `proxmox/etc-pve/` — конфиги хоста
- `databases/` — дампы БД
- `other/vaultwarden/` — архив Vaultwarden
- `photos/library/` — фото Immich
---
## 2. Проверка Immich (веб, загрузка фото)
**Цель:** убедиться, что Immich работает, веб-интерфейс доступен, загрузка фото проходит.
### Подготовка
- Immich доступен по https://immich.katykhin.ru (через NPM).
- ВМ 200: `ssh admin@192.168.1.200`.
### Шаги
1. **Открыть:** https://immich.katykhin.ru
2. **Войти** под учётной записью (логин/пароль из менеджера паролей).
3. **Загрузить тестовое фото:**
- Нажать «Upload» (или загрузить через drag-and-drop).
- Выбрать небольшое изображение (например 12 MB).
- Дождаться завершения загрузки и появления в библиотеке.
4. **Проверить:** фото появилось в галерее, превью отображается, метаданные доступны.
### Если Immich не загружается
- Проверить: `ssh admin@192.168.1.200 "cd /opt/immich && docker compose ps"` — все контейнеры running.
- Логи: `docker logs immich_server` (или `immich_upload_optimizer`).
- NPM: прокси на 192.168.1.200:2283.
---
## 3. Проверка Nextcloud (веб, загрузка файла)
**Цель:** убедиться, что Nextcloud доступен и загрузка файлов работает.
### Подготовка
- Nextcloud: https://cloud.katykhin.ru
- Контейнер 101: `ssh root@192.168.1.101` или `pct exec 101 -- bash`.
### Шаги
1. **Открыть:** https://cloud.katykhin.ru
2. **Войти** под учётной записью (логин/пароль из менеджера паролей).
3. **Загрузить тестовый файл:**
- Перейти в «Files» (или «Файлы»).
- Нажать «Upload» или перетащить файл (например .txt или .pdf).
- Дождаться завершения загрузки.
4. **Проверить:** файл отображается в списке, можно скачать.
### Если Nextcloud не работает
- Проверить: `pct exec 101 -- docker ps` — контейнеры nextcloud и nextcloud-db-1 running.
- Логи: `docker logs nextcloud-app-1` (или имя контейнера из compose).
---
## 4. Проверка GPU passthrough на VM 200
**Цель:** убедиться, что GPU проброшена в Immich ML и распознавание работает.
### Подготовка
- VM 200: `ssh admin@192.168.1.200`
- В Immich: включить ML (Settings → Machine Learning).
### Шаги
1. **Проверить GPU в контейнере ML:**
```bash
ssh admin@192.168.1.200
cd /opt/immich
docker exec immich_machine_learning nvidia-smi
```
Ожидаемый вывод: информация о GPU (модель, память, драйвер).
2. **Проверить распознавание в Immich:**
- Загрузить фото с лицами или объектами.
- Дождаться обработки ML (иконка «Scan» в интерфейсе).
- Проверить: объекты/лица распознаны, теги добавлены.
3. **Если nvidia-smi не работает:**
- На хосте Proxmox: проверить `hostpci0` в конфиге VM 200: `cat /etc/pve/qemu-server/200.conf`
- Убедиться, что PCI-устройство GPU передано в ВМ (`hostpci0: 0000:xx:00.0` и т.п.).
- Перезапустить ВМ при необходимости: `qm stop 200 && qm start 200`.
---
## 5. Дополнительные проверки (по желанию)
- **Vaultwarden:** https://vault.katykhin.ru — вход, синхронизация.
- **Gitea:** https://git.katykhin.ru — вход, список репозиториев.
- **Paperless:** https://docs.katykhin.ru — вход, поиск документов.
- **Galene:** https://call.katykhin.ru — вход в комнату.
---
## Связанные документы
- [backup-howto](backup-howto.md) — восстановление из vzdump, restic, дампов БД, расписание таймеров.
- [container-200](../containers/container-200.md) — VM 200 (Immich), GPU, пути.
- [architecture](../architecture/architecture.md) — хост, IP, доступ.

View File

@@ -0,0 +1,146 @@
# Хост Proxmox (192.168.1.150)
Описание хоста Proxmox VE: скрипты, systemd-сервисы, пути и демоны. Контейнеры и ВМ описаны в отдельных статьях (container-100.md и т.д.).
---
## Общие сведения
- **IP:** 192.168.1.150/24
- **Доступ:** `ssh root@192.168.1.150`
- **Роль:** гипервизор (LXC + KVM), точка запуска бэкапов, деплой секретов в контейнеры
---
## Диски
| Устройство | Тип | Размер | Использование |
|--------------|------|--------|----------------------------------|
| /dev/nvme0n1 | NVMe | 256 GB | LVM (система, local-lvm) |
| /dev/sda | HDD | 2 TB | ZFS |
| /dev/sdb | SSD | 2 TB | ext4, /mnt/backup |
| /dev/sdc | HDD | 2 TB | ZFS (RAID1 с sda) |
| /dev/sdd | HDD | 8 TB | ext4 (внешний) |
---
## Каталог скриптов: /root/scripts/
Скрипты копируются из репозитория в `/root/scripts/` на хосте.
### Бэкапы (backup-*)
| Скрипт | Таймер | Назначение |
|--------|--------|------------|
| backup-vps-miran.sh | 01:00 | VPS Миран: БД бота, voice_users, S3 |
| backup-ct101-pgdump.sh | 01:15 | Nextcloud PostgreSQL |
| backup-immich-photos.sh | 01:30 | rsync фото Immich с VM 200 |
| backup-vps-mtproto.sh | 01:45 | Конфиги MTProto (VPS DE) |
| backup-etc-pve.sh | 02:15 | /etc/pve, interfaces, hosts, resolv.conf |
| backup-ct104-pgdump.sh | 02:30 | Paperless PostgreSQL |
| backup-vaultwarden-data.sh | 02:45 | Данные Vaultwarden |
| backup-ct103-gitea-pgdump.sh | 03:00 | Gitea PostgreSQL |
| backup-vm200-pgdump.sh | 03:15 | Immich PostgreSQL (SSH на VM 200) |
| backup-ct105-vectors.sh | 03:30 | Векторы RAG (vectors.npz) |
| backup-restic-yandex.sh | 04:00 | restic → Yandex (без photos) |
| backup-restic-yandex-photos.sh | 04:10 | restic → Yandex (только photos) |
### Мониторинг и уведомления
| Скрипт | Назначение |
|--------|------------|
| healthcheck-ping.sh | Ping Healthchecks (04:35) — Dead man's switch |
| watchdog-timers.sh | Проверка .ok файлов (12:00), алерт в Telegram при отсутствии |
| smartd-notify.sh | Вызывается smartd при проблемах с дисками |
| notify-telegram.sh | Общий шлюз уведомлений в Telegram |
| notify-vzdump-success.sh | Уведомление после успешного vzdump (по systemd path) |
### Деплой (deploy-*)
Скрипты разворачивают секреты из Vaultwarden в контейнеры и на VPS:
| Скрипт | Куда |
|--------|------|
| deploy-beget-credentials.sh | CT 100 (certbot) |
| deploy-nextcloud-credentials.sh | CT 101 |
| deploy-gitea-credentials.sh | CT 103 |
| deploy-paperless-credentials.sh | CT 104 |
| deploy-rag-credentials.sh | CT 105 |
| deploy-invidious-credentials.sh | CT 107 |
| deploy-galene-credentials.sh | CT 108 |
| deploy-wireguard-credentials.sh | CT 109 |
| deploy-immich-credentials.sh | VM 200 |
| deploy-vpn-route-check.sh | CT 100 (vpn-route-check) |
### Прочее
| Скрипт | Назначение |
|--------|------------|
| immich-pgdump-remote.sh | Копируется на VM 200, вызывается backup-vm200-pgdump.sh по SSH |
| restore-one-vzdump-from-restic.sh | Восстановление одного vzdump из Yandex |
| verify-restore-level1.sh, verify-vzdump-level2.sh | Проверка восстановления |
| backup-setup-sdb1-mount.sh | Монтирование /dev/sdb1 в /mnt/backup |
| setup-vps-miran-backup-on-proxmox.sh | Настройка бэкапа VPS Миран на хосте |
| npm-add-proxy.sh, npm-add-proxy-vault.sh, npm-cert-cloud.sh | Вспомогательные для NPM |
---
## Systemd: таймеры и сервисы
Unit-файлы лежат в репозитории в `scripts/systemd/`, на хосте — в `/etc/systemd/system/`.
### Бэкапы (backup-*.timer)
Все бэкапы запускаются через systemd timers (cron не используется). Расписание: [backup-howto.md](../backup/backup-howto.md).
После успешного выполнения сервис создаёт файл `/var/run/backup-<name>.ok` с timestamp.
### Watchdog (backup-watchdog-timers.timer)
Ежедневно в **12:00** запускается `watchdog-timers.sh`: проверяет, что все `.ok` файлы свежие (не старше 24 ч). При отсутствии или устаревании — уведомление в Telegram.
### Healthcheck ping (backup-healthcheck-ping.timer)
Ежедневно в **04:35** — ping в Healthchecks (Dead man's switch). Если бэкапы не прошли и ping не отправился, Healthchecks шлёт алерт.
### Vzdump (notify-vzdump-success)
Задание vzdump настраивается в Proxmox UI. После успешного выполнения срабатывает path-юнит `notify-vzdump-success.service` → уведомление в Telegram.
### Проверка восстановления (verify-*)
Таймеры для периодической проверки restic и vzdump: `verify-restore-level1-*`, `verify-vzdump-level2`.
---
## Ключевые пути
| Путь | Назначение |
|------|------------|
| /mnt/backup/ | Локальные бэкапы (см. [backup-howto](../backup/backup-howto.md)) |
| /var/run/backup-*.ok | Healthcheck-файлы для watchdog (timestamp последнего успешного запуска) |
| /root/.healthchecks.env | URL и UUID для healthcheck-ping |
| /root/.bw-master | Мастер-пароль Bitwarden CLI (chmod 600; для restic, pg_dump) |
| /root/.restic-yandex.env | Переменные restic (репозиторий, ключи) |
| /etc/smartd.conf | Конфигурация smartd |
| /etc/pve/ | Конфиги Proxmox (бэкапятся в backup-etc-pve) |
---
## Демоны и сервисы
| Сервис | Назначение |
|--------|------------|
| smartd | Мониторинг SMART дисков, при проблемах — smartd-notify.sh → Telegram |
| pveproxy, pvedaemon | Proxmox API и веб-интерфейс |
| corosync, pve-cluster | Кластер Proxmox (при одномузловой установке — локально) |
---
## Связанные документы
- [Архитектура](../architecture/architecture.md) — обзор, контейнеры, поток запросов
- [Бэкапы](../backup/backup-howto.md) — что, куда, когда, восстановление
- [Схема сети](../network/network-topology.md) — топология, зависимости
- [smartd](../monitoring/smartd-setup.md) — мониторинг дисков
- [Healthchecks](../vps/healthchecks-miran-setup.md) — Dead man's switch на VPS Миран

View File

@@ -0,0 +1,202 @@
# Netdata на хосте Proxmox
Мониторинг CPU, RAM, дисков, load average, swap. Алерты в Telegram.
---
## Доступ
| Параметр | Значение |
|----------|----------|
| **URL** | http://192.168.1.150:19999 |
| **Режим** | Локальный, анонимный (Cloud отключён) |
---
## Установка
На хосте Proxmox (root):
```bash
# Официальный установщик
wget -O /tmp/netdata-kickstart.sh https://get.netdata.cloud/kickstart.sh
sh /tmp/netdata-kickstart.sh --stable-channel --disable-telemetry
```
Или через пакетный менеджер (если доступен):
```bash
apt update && apt install -y netdata
```
---
## Конфигурация Telegram
Редактировать: `/etc/netdata/health_alarm_notify.conf`
```bash
cd /etc/netdata
./edit-config health_alarm_notify.conf
```
Добавить/изменить:
```
SEND_TELEGRAM="YES"
TELEGRAM_BOT_TOKEN="<токен из Vaultwarden: HOME_BOT_TOKEN>"
DEFAULT_RECIPIENT_TELEGRAM="<chat_id из Vaultwarden: RESTIC.TELEGRAM_SELF_CHAT_ID>"
```
Креды можно взять из Vaultwarden (объекты HOME_BOT_TOKEN, RESTIC) или из `/root/.telegram-notify.env`.
---
## Алерты (health.d)
Файлы в `/etc/netdata/health.d/`. Создать или переопределить:
### cpu.conf — CPU > 90% более 10 минут
```conf
# Переопределение: CPU > 90% = warning
template: cpu_usage
on: system.cpu
lookup: average -10m percentage of usage
warn: $this > 90
crit: $this > 95
```
### ram.conf — RAM > 90%
```conf
template: ram_usage
on: system.ram
lookup: average -10m percentage of used
warn: $this > 90
crit: $this > 95
```
### load.conf — Load average > cores × 2
```conf
# Load average: warn если load > 2 × число ядер
# Число ядер: nproc или lscpu
template: load_average
on: system.load
lookup: average -10m of load15
# Порог задаётся вручную под хост (cores × 2). Пример для 8 ядер: 16
warn: $this > 16
crit: $this > 24
```
**Важно:** заменить `16` и `24` на `cores × 2` и `cores × 3` для вашего хоста. Узнать ядра: `nproc`.
### swap.conf — Swap > 0 стабильно
```conf
template: swap_usage
on: system.swap
lookup: average -10m percentage of used
warn: $this > 0
crit: $this > 10
```
### disk.conf — Диск > 80% (avail < 20%)
Мониторить: `/` (root, NVMe), `/mnt/backup` (sdb), внешний диск (sdd). Netdata использует `percentage of avail` — warn при avail < 20% (т.е. used > 80%).
```conf
# Шаблон для важных дисков: warn при avail < 20%, crit при avail < 10%
template: disk_space_critical
on: disk.space
lookup: max -1m percentage of avail
warn: $this < 20
crit: $this < 10
```
Или для конкретных путей (chart ID: disk_space._ с подчёркиваниями вместо слешей):
```conf
# / (root, NVMe)
alarm: disk_space_root
on: disk_space._
lookup: max -1m percentage of avail
chart labels: mount_point=/
warn: $this < 20
crit: $this < 10
# /mnt/backup (sdb)
alarm: disk_space_backup
on: disk_space._mnt_backup
lookup: max -1m percentage of avail
warn: $this < 20
crit: $this < 10
```
Узнать точные chart ID: `curl -s "http://localhost:19999/api/v1/charts" | grep disk_space`
---
## SMART (smartmontools)
Для мониторинга SMART через Netdata:
```bash
apt install -y smartmontools
```
Плагин `smartd` в Netdata автоматически обнаруживает диски. Дополнительно см. [smartd-setup.md](smartd-setup.md).
---
## Применение изменений
```bash
netdatacli reload-health
# или
systemctl restart netdata
```
Тест алертов:
```bash
sudo su -s /bin/bash netdata
export NETDATA_ALARM_NOTIFY_DEBUG=1
/usr/libexec/netdata/plugins.d/alarm-notify.sh test
```
---
## Мониторинг контейнеров и VM
Netdata на хосте видит общие метрики (CPU, RAM, диск хоста). Для детального мониторинга каждого LXC/VM:
- **Вариант 1:** Netdata parent/child — установить агент в каждый CT/VM, связать с родителем.
- **Вариант 2:** Один Netdata на хосте — мониторит хост и агрегирует по контейнерам через cgroups (если включено).
Для homelab обычно достаточно мониторинга хоста. При необходимости — см. [Netdata Parent-Child](https://learn.netdata.cloud/docs/agent/streaming).
---
## Отключение Netdata Cloud
Если не нужен trial/Cloud:
1. **Полное отключение:** удалить `/var/lib/netdata/cloud.d/` (токены, ключи) и создать заново только `cloud.conf`:
```bash
rm -rf /var/lib/netdata/cloud.d
mkdir -p /var/lib/netdata/cloud.d
echo -e "[global]\nenabled = no" > /var/lib/netdata/cloud.d/cloud.conf
chown -R netdata:netdata /var/lib/netdata/cloud.d
systemctl restart netdata
```
2. **Локальный дашборд** — http://host:19999 (анонимный доступ, без Cloud). Не использовать app.netdata.cloud — иначе снова появится claim/Cloud.
---
## Связанные документы
- [smartd-setup.md](smartd-setup.md) — SMART и диски
- [backup-howto](../backup/backup-howto.md) — бэкапы

View File

@@ -0,0 +1,109 @@
# SMART и smartd — мониторинг дисков
Настройка `smartd` для мониторинга дисков Proxmox: NVMe, HDD, SSD. При отклонениях — уведомление в Telegram через `notify-telegram.sh`.
---
## Диски (из контекста homelab)
| Устройство | Тип | Размер | Использование |
|--------------|------|--------|----------------------------------|
| /dev/nvme0n1 | NVMe | 256 GB | LVM (система, local-lvm) |
| /dev/sda | HDD | 2 TB | ZFS |
| /dev/sdb | SSD | 2 TB | ext4, /mnt/backup |
| /dev/sdc | HDD | 2 TB | ZFS (RAID1 с sda) |
| /dev/sdd | HDD | 8 TB | ext4 (внешний) |
---
## Установка
```bash
apt install -y smartmontools
```
---
## Конфигурация smartd
Файл: `/etc/smartd.conf`. Текущая конфигурация на хосте:
```conf
# NVMe (система)
/dev/nvme0n1 -a -W 4,45,55 -m root -M exec /root/scripts/smartd-notify.sh
# HDD sda (ZFS)
/dev/sda -a -d ata -R 5 -R 197 -R 198 -W 4,45,55 -m root -M exec /root/scripts/smartd-notify.sh
# SSD sdb (backup)
/dev/sdb -a -d ata -R 5 -R 197 -R 198 -W 4,45,55 -m root -M exec /root/scripts/smartd-notify.sh
# HDD sdc (ZFS)
/dev/sdc -a -d ata -R 5 -R 197 -R 198 -W 4,45,55 -m root -M exec /root/scripts/smartd-notify.sh
# HDD sdd (внешний, USB/SAT)
/dev/sdd -a -d sat -R 5 -R 197 -R 198 -W 4,45,55 -m root -M exec /root/scripts/smartd-notify.sh
```
**Параметры:**
- `-a` — мониторить все атрибуты
- `-d ata` / `-d sat` — тип устройства (ata для SATA, sat для USB/SAT)
- `-R 5` — Reallocated_Sector_Ct
- `-R 197` — Current_Pending_Sector
- `-R 198` — Offline_Uncorrectable
- `-W 4,45,55` — температура: delta 4°C, warn 45°C, crit 55°C
- `-M exec` — выполнить скрипт при проблеме
---
## Скрипт уведомления в Telegram
Создать `/root/scripts/smartd-notify.sh`:
```bash
#!/bin/bash
# Вызывается smartd при обнаружении проблемы.
# Аргументы: device, type (health/usage/fail), message
# См. man smartd.conf (-M exec)
NOTIFY_SCRIPT="${NOTIFY_SCRIPT:-/root/scripts/notify-telegram.sh}"
DEVICE="${1:-unknown}"
# smartd передаёт полный вывод в stdin
MSG=$(cat)
SUMMARY="${2:-SMART problem}"
if [ -x "$NOTIFY_SCRIPT" ]; then
"$NOTIFY_SCRIPT" "⚠️ SMART" "Диск $DEVICE: $SUMMARY
$MSG" || true
fi
# Передать дальше в mail (если настроен)
exit 0
```
Сделать исполняемым: `chmod +x /root/scripts/smartd-notify.sh`
**Примечание:** smartd при `-M exec` передаёт в скрипт до 3 аргументов и stdin. Точный формат см. в `man smartd.conf` (раздел -M exec).
---
## Запуск smartd
```bash
systemctl enable --now smartd
systemctl status smartd
```
Проверка вручную:
```bash
smartctl -a /dev/sda
smartctl -a /dev/nvme0n1
```
---
## Интеграция с Netdata
Netdata имеет плагин smartmontools. После установки smartmontools и настройки smartd Netdata может отображать метрики SMART на дашборде. См. [netdata-proxmox-setup.md](netdata-proxmox-setup.md).

View File

@@ -132,7 +132,7 @@ flowchart TB
│ VPS DE │ │ VPS US │ │ VPS Миран (СПБ) │ │ VPS DE │ │ VPS US │ │ VPS Миран (СПБ) │
│ 185.103.253.99 │ │ 147.45.124.117 │ │ 185.147.80.190 │ │ 185.103.253.99 │ │ 147.45.124.117 │ │ 185.147.80.190 │
│ AmneziaWG │ │ AmneziaWG │ │ coTURN (Galene), │ │ AmneziaWG │ │ AmneziaWG │ │ coTURN (Galene), │
│ (обход блок.) │ │ (обход блок.) │ │ боты, Prometheus │ (обход блок.) │ │ (обход блок.) │ │ Healthchecks, боты
└──────────────────┘ └──────────────────┘ └──────────────────────┘ └──────────────────┘ └──────────────────┘ └──────────────────────┘
``` ```
@@ -160,7 +160,7 @@ flowchart TB
| **VM 200** | 192.168.1.200 | Immich, PostgreSQL, Redis, ML, deduper, Power Tools, Public Share | immich.katykhin.ru, immich-pt.katykhin.ru, share.katykhin.ru | | **VM 200** | 192.168.1.200 | Immich, PostgreSQL, Redis, ML, deduper, Power Tools, Public Share | immich.katykhin.ru, immich-pt.katykhin.ru, share.katykhin.ru |
| **VPS DE** | 185.103.253.99 | AmneziaWG (обход блокировок) | Туннель с роутера (10.8.1.x) | | **VPS DE** | 185.103.253.99 | AmneziaWG (обход блокировок) | Туннель с роутера (10.8.1.x) |
| **VPS US** | 147.45.124.117 | AmneziaWG (второй выход) | Туннель с роутера | | **VPS US** | 147.45.124.117 | AmneziaWG (второй выход) | Туннель с роутера |
| **VPS Миран** | 185.147.80.190 | coTURN (STUN/TURN для Galene), боты, prod | call.katykhin.ru использует STUN/TURN | | **VPS Миран** | 185.147.80.190 | coTURN (STUN/TURN для Galene), Healthchecks, боты, prod | call.katykhin.ru (STUN/TURN), healthchecks.katykhin.ru |
| **DNS** | Beget.com | Домен katykhin.ru, поддомены, API для DNS-01 | Все *.katykhin.ru | | **DNS** | Beget.com | Домен katykhin.ru, поддомены, API для DNS-01 | Все *.katykhin.ru |
--- ---
@@ -241,6 +241,7 @@ flowchart TB
## Связь с другими документами ## Связь с другими документами
- [Архитектура и подключение](../architecture/architecture.md) — общее описание, таблица контейнеров, поток запросов. - [Архитектура и подключение](../architecture/architecture.md) — общее описание, таблица контейнеров, поток запросов.
- [Хост Proxmox](../containers/host-proxmox.md) — скрипты, таймеры, пути на 192.168.1.150.
- [Контейнер 100](../containers/container-100.md) — NPM, AdGuard, Homepage, порядок запуска. - [Контейнер 100](../containers/container-100.md) — NPM, AdGuard, Homepage, порядок запуска.
- [Контейнер 109](../containers/container-109.md) — WireGuard VPN (local-vpn), доступ к vault и LAN. - [Контейнер 109](../containers/container-109.md) — WireGuard VPN (local-vpn), доступ к vault и LAN.
- [Генерация .mobileconfig для WireGuard (On-Demand)](vpn-mobileconfig-wireguard.md) — как собрать профиль для iOS/macOS с автоматическим подключением вне дома. - [Генерация .mobileconfig для WireGuard (On-Demand)](vpn-mobileconfig-wireguard.md) — как собрать профиль для iOS/macOS с автоматическим подключением вне дома.

View File

@@ -0,0 +1,112 @@
# Healthchecks на VPS Миран
Self-hosted [Healthchecks.io](https://healthchecks.io/) на VPS 185.147.80.190 — Dead man's switch для homelab. Если Proxmox не отправляет ping после окна бэкапов, Healthchecks шлёт алерт в Telegram.
---
## Доступ
| Параметр | Значение |
|----------|----------|
| **URL** | https://healthchecks.katykhin.ru/healthchecks/ |
| **Логин** | admin@katykhin.ru |
| **Пароль** | в Vaultwarden (Healthchecks admin) |
Доступ настроен по домену. Telegram webhook требует валидный SSL — без домена с Let's Encrypt бот не отвечает на `/start`.
---
## Развёртывание (для переустановки)
### 1. Подготовка
```bash
ssh -p 15722 deploy@185.147.80.190
mkdir -p /home/prod/healthchecks
cd /home/prod/healthchecks
```
Скопировать из репозитория: `scripts/healthchecks-docker/docker-compose.yml`, `scripts/healthchecks-docker/.env.example``.env`
### 2. Конфигурация .env
```env
SITE_ROOT=https://healthchecks.katykhin.ru/healthchecks
SECRET_KEY=<openssl rand -hex 32>
ALLOWED_HOSTS=healthchecks.katykhin.ru,185.147.80.190,localhost
DB_HOST=db
DB_NAME=hc
DB_USER=postgres
DB_PASSWORD=<надёжный пароль>
TELEGRAM_TOKEN=<токен из Vaultwarden: HOME_BOT_TOKEN>
TELEGRAM_BOT_NAME=<username бота из @BotFather, напр. Katykhinhomebot>
REGISTRATION_OPEN=False
DEFAULT_FROM_EMAIL=healthchecks@katykhin.ru
```
### 3. Запуск
```bash
docker-compose up -d
docker-compose run web /opt/healthchecks/manage.py createsuperuser --email admin@katykhin.ru --password <password>
docker-compose run web python /opt/healthchecks/manage.py settelegramwebhook
```
### 4. Nginx
Отдельный server block для `healthchecks.katykhin.ru` с Let's Encrypt. Референс: `scripts/healthchecks-nginx-server.conf`. Proxy на 127.0.0.1:8000; нужны location для `/healthchecks/`, `/static/`, `/projects/`, `/accounts/`, `/integrations/`, `/ping/` и др. (Django редиректы без префикса).
### 5. DNS
A-запись: `healthchecks.katykhin.ru``185.147.80.190`. Сертификат: `certbot --nginx -d healthchecks.katykhin.ru`.
---
## Привязка Telegram к check
1. Войти в Healthchecks → **Integrations****Add Integration****Telegram**
2. Писать **своему** боту (из TELEGRAM_TOKEN), не @HealthchecksBot
3. В Telegram: `/start` боту → перейти по ссылке → **Connect** в веб-интерфейсе
Check **homelab-backups** (UUID: 9451b52b-89f5-4a6c-b922-247a775bbf45).
---
## Ping с Proxmox
Скрипт `/root/scripts/healthcheck-ping.sh`, таймер `backup-healthcheck-ping.timer` — 04:35 ежедневно.
Конфиг `/root/.healthchecks.env`:
```env
HEALTHCHECKS_URL=https://healthchecks.katykhin.ru/healthchecks
HEALTHCHECKS_HOMELAB_UUID=<uuid из Healthchecks>
```
---
## Смена пароля без SMTP
Healthchecks требует SMTP для смены пароля через веб. Без SMTP — через Django:
```bash
cd /home/prod/healthchecks
docker-compose run web python /opt/healthchecks/manage.py shell -c "
from django.contrib.auth import get_user_model
User = get_user_model()
u = User.objects.get(email='admin@katykhin.ru')
u.set_password('NEW_PASSWORD')
u.save()
print('OK')
"
```
---
## Связанные документы
- [vps-miran-bots](vps-miran-bots.md) — VPS Миран, порты
- [backup-howto](../backup/backup-howto.md) — бэкапы, расписание

View File

@@ -81,6 +81,9 @@ VPS в ЦОД Миран (Санкт-Петербург). Развёрнуты
| 9100 | node-exporter | TCP | | 9100 | node-exporter | TCP |
| 3000 | Grafana | TCP | | 3000 | Grafana | TCP |
| 3001 | Uptime Kuma | TCP | | 3001 | Uptime Kuma | TCP |
| 8000 | Healthchecks (внутр.) | TCP |
**Healthchecks** — self-hosted Dead man's switch для homelab. Развёртывание: [healthchecks-miran-setup.md](healthchecks-miran-setup.md). Доступ через nginx (healthchecks.katykhin.ru).
--- ---
@@ -110,7 +113,7 @@ docker compose logs -f telegram-bot
## Бэкап VPS (telegram-helper-bot) ## Бэкап VPS (telegram-helper-bot)
Бэкап выполняется **с хоста Proxmox** скриптом `backup-vps-miran.sh` (cron 01:00). Копируются: Бэкап выполняется **с хоста Proxmox** скриптом `backup-vps-miran.sh` (systemd timer 01:00). Копируются:
1. **БД:** `/home/prod/bots/telegram-helper-bot/database/tg-bot-database.db``/mnt/backup/vps/miran/db/` (с датой в имени, хранение 14 дней). 1. **БД:** `/home/prod/bots/telegram-helper-bot/database/tg-bot-database.db``/mnt/backup/vps/miran/db/` (с датой в имени, хранение 14 дней).
2. **Голосовые сообщения:** `/home/prod/bots/telegram-helper-bot/voice_users/``/mnt/backup/vps/miran/voice_users/` (rsync). 2. **Голосовые сообщения:** `/home/prod/bots/telegram-helper-bot/voice_users/``/mnt/backup/vps/miran/voice_users/` (rsync).

View File

@@ -4,6 +4,8 @@
# Запускать на хосте Proxmox под root. Секреты из Vaultwarden (объект RESTIC), файл /root/.bw-master (chmod 600). # Запускать на хосте Proxmox под root. Секреты из Vaultwarden (объект RESTIC), файл /root/.bw-master (chmod 600).
# Cron: 10 4 * * * (04:10, после основного restic в 04:00). # Cron: 10 4 * * * (04:10, после основного restic в 04:00).
set -e set -e
export PATH="/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:${PATH:-}"
export HOME="${HOME:-/root}"
BACKUP_PATH="/mnt/backup/photos" BACKUP_PATH="/mnt/backup/photos"
# Время запуска (для логов и уведомлений) # Время запуска (для логов и уведомлений)

View File

@@ -6,6 +6,9 @@
# Перед первым запуском: установить restic, bw (Bitwarden CLI), jq; bw config server https://vault.katykhin.ru; restic init. # Перед первым запуском: установить restic, bw (Bitwarden CLI), jq; bw config server https://vault.katykhin.ru; restic init.
# Cron: 0 4 * * * (04:00, после окна 01:0003:30; 05:00 зарезервировано под перезагрузку). # Cron: 0 4 * * * (04:00, после окна 01:0003:30; 05:00 зарезервировано под перезагрузку).
set -e set -e
# При запуске из systemd PATH и HOME могут быть пустыми
export PATH="/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:${PATH:-}"
export HOME="${HOME:-/root}"
BACKUP_PATH="/mnt/backup" BACKUP_PATH="/mnt/backup"
# Время запуска (для логов и уведомлений) # Время запуска (для логов и уведомлений)

20
scripts/healthcheck-ping.sh Executable file
View File

@@ -0,0 +1,20 @@
#!/bin/bash
# Ping Healthchecks после успешного окна бэкапов (Dead man's switch).
# Если ping не пришёл — Healthchecks шлёт алерт в Telegram.
# Конфиг: /root/.healthchecks.env (HEALTHCHECKS_URL, HEALTHCHECKS_HOMELAB_UUID)
CONFIG="${HEALTHCHECKS_CONFIG:-/root/.healthchecks.env}"
if [ -f "$CONFIG" ]; then
set -a
# shellcheck source=/dev/null
source "$CONFIG"
set +a
fi
HC_URL="${HEALTHCHECKS_URL:-https://healthchecks.katykhin.ru}"
HC_UUID="${HEALTHCHECKS_HOMELAB_UUID:-}"
[ -z "$HC_UUID" ] && exit 0
curl -fsS --retry 3 --max-time 10 "${HC_URL}/ping/${HC_UUID}" >/dev/null 2>&1 || true
exit 0

View File

@@ -0,0 +1,23 @@
# Healthchecks на VPS Миран
# Копировать: cp .env.example .env
SITE_ROOT=https://healthchecks.katykhin.ru/healthchecks
SECRET_KEY=CHANGE_ME_openssl_rand_hex_32
ALLOWED_HOSTS=healthchecks.katykhin.ru,185.147.80.190,localhost
DB=postgres
DB_HOST=db
DB_NAME=hc
DB_USER=postgres
DB_PASSWORD=CHANGE_ME_secure_password
# Свой бот (не @HealthchecksBot!) — создать через @BotFather, username бота
TELEGRAM_TOKEN=
TELEGRAM_BOT_NAME=YourBotUsername
REGISTRATION_OPEN=False
EMAIL_HOST=
EMAIL_HOST_USER=
EMAIL_HOST_PASSWORD=
DEFAULT_FROM_EMAIL=healthchecks@katykhin.ru

View File

@@ -0,0 +1,31 @@
# Healthchecks на VPS Миран
# Копировать: cp -r scripts/healthchecks-docker /home/prod/healthchecks
# cd /home/prod/healthchecks && cp .env.example .env && редактировать .env
volumes:
db-data:
services:
db:
image: postgres:16
volumes:
- db-data:/var/lib/postgresql/data
environment:
- POSTGRES_DB=${DB_NAME:-hc}
- POSTGRES_PASSWORD=${DB_PASSWORD}
restart: unless-stopped
web:
image: healthchecks/healthchecks:latest
env_file:
- .env
environment:
- DB_HOST=db
- DB_NAME=${DB_NAME:-hc}
- DB_USER=postgres
- DB_PASSWORD=${DB_PASSWORD}
ports:
- "127.0.0.1:8000:8000"
depends_on:
- db
restart: unless-stopped

View File

@@ -0,0 +1,25 @@
# Референс: server block для healthchecks.katykhin.ru (Let's Encrypt, Telegram webhook)
# Вставить в nginx.conf после HTTP redirect server block
server {
listen 443 ssl http2;
server_name healthchecks.katykhin.ru;
ssl_certificate /etc/letsencrypt/live/healthchecks.katykhin.ru/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/healthchecks.katykhin.ru/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
location = / { return 302 /healthchecks/; }
location /static/ { proxy_pass http://127.0.0.1:8000/static/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; }
location /projects/ { proxy_pass http://127.0.0.1:8000/projects/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; }
location /accounts/ { proxy_pass http://127.0.0.1:8000/accounts/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; }
location /integrations/ { proxy_pass http://127.0.0.1:8000/integrations/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; }
location /ping/ { proxy_pass http://127.0.0.1:8000/ping/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; }
location /admin/ { proxy_pass http://127.0.0.1:8000/admin/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; }
location /badge/ { proxy_pass http://127.0.0.1:8000/badge/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; }
location /checks/ { proxy_pass http://127.0.0.1:8000/checks/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; }
location /docs/ { proxy_pass http://127.0.0.1:8000/docs/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; }
location /tv/ { proxy_pass http://127.0.0.1:8000/tv/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; }
location = /healthchecks/ { return 302 /healthchecks/accounts/login/; }
location = /healthchecks { return 302 /healthchecks/accounts/login/; }
location /healthchecks/ { proxy_pass http://127.0.0.1:8000/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; }
}

View File

@@ -0,0 +1,6 @@
# Конфиг для healthcheck-ping.sh (Proxmox)
# Копировать: cp healthchecks.env.example /root/.healthchecks.env
# UUID — из веб-интерфейса Healthchecks после создания check "homelab-backups"
HEALTHCHECKS_URL=https://healthchecks.katykhin.ru
HEALTHCHECKS_HOMELAB_UUID=

30
scripts/smartd-notify.sh Executable file
View File

@@ -0,0 +1,30 @@
#!/bin/bash
# Вызывается smartd при обнаружении проблемы (-M exec).
# Аргументы: $1 = device, $2 = type (1=health, 2=usage, 3=fail), $3 = message
# См. man smartd.conf
NOTIFY_SCRIPT="${NOTIFY_SCRIPT:-/root/scripts/notify-telegram.sh}"
DEVICE="${1:-unknown}"
TYPE="${2:-}"
MSG="${3:-}"
# Дополнительный вывод smartd может быть в stdin
EXTRA=$(cat 2>/dev/null || true)
case "$TYPE" in
1) SUMMARY="Health check failed" ;;
2) SUMMARY="Usage attribute warning" ;;
3) SUMMARY="Usage attribute failure" ;;
*) SUMMARY="SMART problem" ;;
esac
if [ -x "$NOTIFY_SCRIPT" ]; then
BODY="Диск $DEVICE: $SUMMARY"
[ -n "$MSG" ] && BODY="${BODY}
$MSG"
[ -n "$EXTRA" ] && BODY="${BODY}
$EXTRA"
"$NOTIFY_SCRIPT" "⚠️ SMART" "$BODY" || true
fi
exit 0

24
scripts/systemd/README.md Normal file
View File

@@ -0,0 +1,24 @@
# Systemd unit-файлы для бэкапов и мониторинга
Копировать на хост Proxmox в `/etc/systemd/system/`:
```bash
cp *.service *.timer /etc/systemd/system/
systemctl daemon-reload
```
Включить все таймеры:
```bash
for t in backup-*.timer notify-vzdump-success.timer verify-*.timer backup-watchdog-timers.timer backup-healthcheck-ping.timer; do
systemctl enable --now "$t" 2>/dev/null || true
done
```
Проверка:
```bash
systemctl list-timers --all | grep backup
```
Перед миграцией с cron — отключить задания в crontab (`crontab -e`).

View File

@@ -0,0 +1,14 @@
# Бэкап БД Nextcloud (CT 101)
[Unit]
Description=Backup Nextcloud PostgreSQL (CT 101)
After=network-online.target
[Service]
Type=oneshot
ExecStart=/bin/sh -c '/root/scripts/backup-ct101-pgdump.sh && echo $(date -Iseconds) > /var/run/backup-ct101-pgdump.ok'
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,9 @@
[Unit]
Description=Backup Nextcloud DB daily at 01:15
[Timer]
OnCalendar=*-*-* 01:15:00
Persistent=yes
[Install]
WantedBy=timers.target

View File

@@ -0,0 +1,14 @@
# Бэкап БД Gitea (CT 103)
[Unit]
Description=Backup Gitea PostgreSQL (CT 103)
After=network-online.target
[Service]
Type=oneshot
ExecStart=/bin/sh -c '/root/scripts/backup-ct103-gitea-pgdump.sh && echo $(date -Iseconds) > /var/run/backup-ct103-gitea-pgdump.ok'
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,9 @@
[Unit]
Description=Backup Gitea DB daily at 03:00
[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=yes
[Install]
WantedBy=timers.target

View File

@@ -0,0 +1,14 @@
# Бэкап БД Paperless (CT 104)
[Unit]
Description=Backup Paperless PostgreSQL (CT 104)
After=network-online.target
[Service]
Type=oneshot
ExecStart=/bin/sh -c '/root/scripts/backup-ct104-pgdump.sh && echo $(date -Iseconds) > /var/run/backup-ct104-pgdump.ok'
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,9 @@
[Unit]
Description=Backup Paperless DB daily at 02:30
[Timer]
OnCalendar=*-*-* 02:30:00
Persistent=yes
[Install]
WantedBy=timers.target

View File

@@ -0,0 +1,14 @@
# Бэкап векторов RAG (CT 105)
[Unit]
Description=Backup RAG vectors (CT 105)
After=network-online.target
[Service]
Type=oneshot
ExecStart=/bin/sh -c '/root/scripts/backup-ct105-vectors.sh && echo $(date -Iseconds) > /var/run/backup-ct105-vectors.ok'
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,9 @@
[Unit]
Description=Backup RAG vectors daily at 03:30
[Timer]
OnCalendar=*-*-* 03:30:00
Persistent=yes
[Install]
WantedBy=timers.target

View File

@@ -0,0 +1,14 @@
# Бэкап /etc/pve и конфигов хоста
[Unit]
Description=Backup Proxmox host config (/etc/pve, interfaces, hosts)
After=network-online.target
[Service]
Type=oneshot
ExecStart=/bin/sh -c '/root/scripts/backup-etc-pve.sh && echo $(date -Iseconds) > /var/run/backup-etc-pve.ok'
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,9 @@
[Unit]
Description=Backup etc-pve daily at 02:15
[Timer]
OnCalendar=*-*-* 02:15:00
Persistent=yes
[Install]
WantedBy=timers.target

View File

@@ -0,0 +1,14 @@
# Ping Healthchecks после окна бэкапов (Dead man's switch)
[Unit]
Description=Ping Healthchecks (homelab backups)
After=network-online.target
[Service]
Type=oneshot
ExecStart=/root/scripts/healthcheck-ping.sh
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,9 @@
[Unit]
Description=Ping Healthchecks daily at 04:35 (after backup window)
[Timer]
OnCalendar=*-*-* 04:35:00
Persistent=yes
[Install]
WantedBy=timers.target

View File

@@ -0,0 +1,14 @@
# Бэкап библиотеки фото Immich (rsync с VM 200)
[Unit]
Description=Backup Immich photos (rsync from VM 200)
After=network-online.target
[Service]
Type=oneshot
ExecStart=/bin/sh -c '/root/scripts/backup-immich-photos.sh && echo $(date -Iseconds) > /var/run/backup-immich-photos.ok'
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,9 @@
[Unit]
Description=Backup Immich photos daily at 01:30
[Timer]
OnCalendar=*-*-* 01:30:00
Persistent=yes
[Install]
WantedBy=timers.target

View File

@@ -0,0 +1,16 @@
# Выгрузка /mnt/backup/photos в Yandex S3 через restic
[Unit]
Description=Backup photos to Yandex S3 (restic)
After=network-online.target
[Service]
Type=oneshot
Environment=HOME=/root
Environment=PATH=/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
ExecStart=/bin/sh -c '/root/scripts/backup-restic-yandex-photos.sh && echo $(date -Iseconds) > /var/run/backup-restic-yandex-photos.ok'
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,9 @@
[Unit]
Description=Restic backup photos to Yandex daily at 04:10
[Timer]
OnCalendar=*-*-* 04:10:00
Persistent=yes
[Install]
WantedBy=timers.target

View File

@@ -0,0 +1,16 @@
# Выгрузка /mnt/backup (без photos) в Yandex S3 через restic
[Unit]
Description=Backup to Yandex S3 (restic, main)
After=network-online.target
[Service]
Type=oneshot
Environment=HOME=/root
Environment=PATH=/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
ExecStart=/bin/sh -c '/root/scripts/backup-restic-yandex.sh && echo $(date -Iseconds) > /var/run/backup-restic-yandex.ok'
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,9 @@
[Unit]
Description=Restic backup to Yandex daily at 04:00
[Timer]
OnCalendar=*-*-* 04:00:00
Persistent=yes
[Install]
WantedBy=timers.target

View File

@@ -0,0 +1,14 @@
# Бэкап данных Vaultwarden (CT 103)
[Unit]
Description=Backup Vaultwarden data (CT 103)
After=network-online.target
[Service]
Type=oneshot
ExecStart=/bin/sh -c '/root/scripts/backup-vaultwarden-data.sh && echo $(date -Iseconds) > /var/run/backup-vaultwarden-data.ok'
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,9 @@
[Unit]
Description=Backup Vaultwarden daily at 02:45
[Timer]
OnCalendar=*-*-* 02:45:00
Persistent=yes
[Install]
WantedBy=timers.target

View File

@@ -0,0 +1,14 @@
# Бэкап БД Immich (VM 200)
[Unit]
Description=Backup Immich PostgreSQL (VM 200)
After=network-online.target
[Service]
Type=oneshot
ExecStart=/bin/sh -c '/root/scripts/backup-vm200-pgdump.sh && echo $(date -Iseconds) > /var/run/backup-vm200-pgdump.ok'
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,9 @@
[Unit]
Description=Backup Immich DB daily at 03:15
[Timer]
OnCalendar=*-*-* 03:15:00
Persistent=yes
[Install]
WantedBy=timers.target

View File

@@ -0,0 +1,17 @@
# Копировать на Proxmox: /etc/systemd/system/
# systemctl daemon-reload && systemctl enable --now backup-vps-miran.timer
# Удалить из cron: 0 1 * * *
[Unit]
Description=Backup VPS Miran (БД бота, voice_users, S3)
After=network-online.target
[Service]
Type=oneshot
# Запись .ok только при успехе (для watchdog)
ExecStart=/bin/sh -c '/root/scripts/backup-vps-miran.sh && echo $(date -Iseconds) > /var/run/backup-vps-miran.ok'
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,9 @@
[Unit]
Description=Backup VPS Miran daily at 01:00
[Timer]
OnCalendar=*-*-* 01:00:00
Persistent=yes
[Install]
WantedBy=timers.target

View File

@@ -0,0 +1,14 @@
# Бэкап конфигов MTProto + сайт (VPS Германия)
[Unit]
Description=Backup VPS MTProto (Germany)
After=network-online.target
[Service]
Type=oneshot
ExecStart=/bin/sh -c '/root/scripts/backup-vps-mtproto.sh && echo $(date -Iseconds) > /var/run/backup-vps-mtproto.ok'
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,9 @@
[Unit]
Description=Backup VPS MTProto daily at 01:45
[Timer]
OnCalendar=*-*-* 01:45:00
Persistent=yes
[Install]
WantedBy=timers.target

View File

@@ -0,0 +1,14 @@
# Watchdog: проверка failed timers и устаревших healthcheck-файлов
[Unit]
Description=Backup watchdog (failed timers, stale .ok files)
After=network-online.target
[Service]
Type=oneshot
ExecStart=/root/scripts/watchdog-timers.sh
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,9 @@
[Unit]
Description=Backup watchdog daily at 12:00
[Timer]
OnCalendar=*-*-* 12:00:00
Persistent=yes
[Install]
WantedBy=timers.target

View File

@@ -0,0 +1,15 @@
# Проверка локального vzdump за последние 2 ч, отправка сводки в Telegram
# Задание vzdump в Proxmox UI выполняется в 02:00
[Unit]
Description=Notify vzdump success (check dump dir, send Telegram)
After=network-online.target
[Service]
Type=oneshot
ExecStart=/root/scripts/notify-vzdump-success.sh
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,9 @@
[Unit]
Description=Notify vzdump success daily at 03:00
[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=yes
[Install]
WantedBy=timers.target

View File

@@ -0,0 +1,14 @@
# Restic check --read-data (раз в 6 мес: 1 янв и 1 июля)
[Unit]
Description=Verify restic repository (full read-data, semiannual)
After=network-online.target
[Service]
Type=oneshot
ExecStart=/root/scripts/verify-restore-level1.sh full-check
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,10 @@
[Unit]
Description=Restic full check semiannual (Jan 1, Jul 1 at 10:00)
[Timer]
OnCalendar=*-01-01 10:00:00
OnCalendar=*-07-01 10:00:00
Persistent=yes
[Install]
WantedBy=timers.target

View File

@@ -0,0 +1,14 @@
# Restic check --read-data-subset=10% (ежемесячно, 1-е число)
[Unit]
Description=Verify restic repository (monthly read-data-subset)
After=network-online.target
[Service]
Type=oneshot
ExecStart=/root/scripts/verify-restore-level1.sh monthly-check
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,9 @@
[Unit]
Description=Restic check read-data-subset monthly (1st at 10:00)
[Timer]
OnCalendar=*-*-01 10:00:00
Persistent=yes
[Install]
WantedBy=timers.target

View File

@@ -0,0 +1,14 @@
# Тест restore дампа Nextcloud из restic (ежемесячно)
[Unit]
Description=Verify Nextcloud dump restore from restic (monthly)
After=network-online.target
[Service]
Type=oneshot
ExecStart=/root/scripts/verify-restore-level1.sh monthly-dump
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,9 @@
[Unit]
Description=Verify Nextcloud dump restore monthly (1st at 11:00)
[Timer]
OnCalendar=*-*-01 11:00:00
Persistent=yes
[Install]
WantedBy=timers.target

View File

@@ -0,0 +1,14 @@
# Restic check (еженедельно)
[Unit]
Description=Verify restic repository (weekly check)
After=network-online.target
[Service]
Type=oneshot
ExecStart=/root/scripts/verify-restore-level1.sh weekly
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,9 @@
[Unit]
Description=Restic check weekly (Sunday 03:00)
[Timer]
OnCalendar=Sun *-*-* 03:00:00
Persistent=yes
[Install]
WantedBy=timers.target

View File

@@ -0,0 +1,14 @@
# Автотест vzdump CT 107 (ежемесячно)
[Unit]
Description=Verify vzdump restore (CT 107, monthly)
After=network-online.target
[Service]
Type=oneshot
ExecStart=/root/scripts/verify-vzdump-level2.sh
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,9 @@
[Unit]
Description=Verify vzdump restore monthly (1st at 12:00)
[Timer]
OnCalendar=*-*-01 12:00:00
Persistent=yes
[Install]
WantedBy=timers.target

147
scripts/verify-restore-level1.sh Executable file
View File

@@ -0,0 +1,147 @@
#!/bin/bash
# Тест восстановления уровня 1: restic check и проверка дампа Nextcloud из restic.
# Запускать на хосте Proxmox под root.
# Режимы (аргумент): weekly | monthly-check | full-check | monthly-dump
# weekly — restic check (еженедельно)
# monthly-check — restic check --read-data-subset=10% (ежемесячно, 1-е число)
# full-check — restic check --read-data (раз в 612 мес, 1 янв и 1 июля)
# monthly-dump — restore дампа Nextcloud из restic, проверка целостности (ежемесячно)
# Секреты: из Vaultwarden (объект RESTIC), как в backup-restic-yandex.sh.
# Cron/Timer: отдельные таймеры для каждого режима.
set -e
export PATH="/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:${PATH:-}"
MODE="${1:-weekly}"
NOTIFY_SCRIPT="${NOTIFY_SCRIPT:-/root/scripts/notify-telegram.sh}"
RESTORE_TARGET="/tmp/restore-test"
RESTIC_PATH_NEXTCLOUD="/mnt/backup/databases/ct101-nextcloud"
MIN_DUMP_SIZE_MB=1
if [ "$(id -u)" -ne 0 ]; then
echo "Запускайте под root."
exit 1
fi
# Загрузка кредов restic из Vaultwarden (как в backup-restic-yandex.sh)
setup_restic_env() {
BW_MASTER_PASSWORD_FILE="${BW_MASTER_PASSWORD_FILE:-/root/.bw-master}"
if [ ! -f "$BW_MASTER_PASSWORD_FILE" ]; then
echo "Нет файла с мастер-паролем Vaultwarden: $BW_MASTER_PASSWORD_FILE"
return 1
fi
if ! command -v bw >/dev/null 2>&1 || ! command -v jq >/dev/null 2>&1; then
echo "Установите bw (Bitwarden CLI) и jq."
return 1
fi
export BW_SESSION
BW_SESSION=$(bw unlock --passwordfile "$BW_MASTER_PASSWORD_FILE" --raw 2>/dev/null) || return 1
RESTIC_ITEM=$(bw get item "RESTIC" 2>/dev/null) || return 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
[ -z "${!var}" ] && return 1
done
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
return 0
}
notify_ok() {
[ -x "$NOTIFY_SCRIPT" ] && "$NOTIFY_SCRIPT" "$1" "$2" || true
}
notify_err() {
[ -x "$NOTIFY_SCRIPT" ] && "$NOTIFY_SCRIPT" "⚠️ $1" "$2" || true
}
case "$MODE" in
weekly)
echo "[verify-restore-level1] Режим: weekly (restic check)"
setup_restic_env || { notify_err "Restic check" "Не удалось загрузить креды restic."; exit 1; }
if restic check 2>&1; then
echo "[verify-restore-level1] restic check OK"
# При еженедельном успехе — не спамим (только при ошибке)
else
notify_err "Restic check" "Ошибка проверки репозитория restic."
exit 1
fi
;;
monthly-check)
echo "[verify-restore-level1] Режим: monthly-check (restic check --read-data-subset=10%)"
setup_restic_env || { notify_err "Restic check (read-data-subset)" "Не удалось загрузить креды restic."; exit 1; }
if restic check --read-data-subset=10% 2>&1; then
echo "[verify-restore-level1] restic check --read-data-subset=10% OK"
notify_ok "Тест restic (read-data-subset)" "OK, 10% данных проверено."
else
notify_err "Restic check (read-data-subset)" "Ошибка проверки 10% данных репозитория."
exit 1
fi
;;
full-check)
echo "[verify-restore-level1] Режим: full-check (restic check --read-data)"
setup_restic_env || { notify_err "Restic check (read-data)" "Не удалось загрузить креды restic."; exit 1; }
if restic check --read-data 2>&1; then
echo "[verify-restore-level1] restic check --read-data OK"
notify_ok "Тест restic (full read-data)" "OK, полная проверка данных завершена."
else
notify_err "Restic check (read-data)" "Ошибка полной проверки данных репозитория."
exit 1
fi
;;
monthly-dump)
echo "[verify-restore-level1] Режим: monthly-dump (restore и проверка дампа Nextcloud)"
setup_restic_env || { notify_err "Тест дампа Nextcloud" "Не удалось загрузить креды restic."; exit 1; }
rm -rf "$RESTORE_TARGET"
mkdir -p "$RESTORE_TARGET"
trap 'rm -f "${RESTIC_PASSWORD_FILE:-}" 2>/dev/null; rm -rf "$RESTORE_TARGET"' EXIT INT TERM
if ! restic restore latest --target "$RESTORE_TARGET" --path "$RESTIC_PATH_NEXTCLOUD" 2>&1; then
notify_err "Тест дампа Nextcloud" "Ошибка restic restore: не удалось восстановить $RESTIC_PATH_NEXTCLOUD"
exit 1
fi
# Путь после restore: RESTORE_TARGET/mnt/backup/databases/ct101-nextcloud/
RESTORED_DIR="$RESTORE_TARGET/mnt/backup/databases/ct101-nextcloud"
if [ ! -d "$RESTORED_DIR" ]; then
notify_err "Тест дампа Nextcloud" "Ошибка: каталог $RESTORED_DIR не найден после restore."
exit 1
fi
LATEST_SQL=$(ls -t "$RESTORED_DIR"/nextcloud-db-*.sql.gz 2>/dev/null | head -1)
if [ -z "$LATEST_SQL" ] || [ ! -f "$LATEST_SQL" ]; then
notify_err "Тест дампа Nextcloud" "Ошибка: не найден .sql.gz в $RESTORED_DIR"
exit 1
fi
SIZE_BYTES=$(stat -c%s "$LATEST_SQL" 2>/dev/null || echo 0)
SIZE_MB=$(( SIZE_BYTES / 1024 / 1024 ))
if [ "$SIZE_MB" -lt "$MIN_DUMP_SIZE_MB" ]; then
notify_err "Тест дампа Nextcloud" "Ошибка: размер дампа ${SIZE_MB} MB < ${MIN_DUMP_SIZE_MB} MB (файл: $LATEST_SQL)"
exit 1
fi
if ! gunzip -t "$LATEST_SQL" 2>/dev/null; then
notify_err "Тест дампа Nextcloud" "Ошибка: gunzip -t не прошёл для $LATEST_SQL"
exit 1
fi
if ! gunzip -c "$LATEST_SQL" 2>/dev/null | grep -q 'CREATE TABLE'; then
notify_err "Тест дампа Nextcloud" "Ошибка: в распакованном дампе нет CREATE TABLE (возможно не SQL дамп)"
exit 1
fi
echo "[verify-restore-level1] Дамп Nextcloud OK: $LATEST_SQL, размер ${SIZE_MB} MB"
notify_ok "Тест дампа Nextcloud" "OK, размер ${SIZE_MB} MB."
;;
*)
echo "Использование: $0 {weekly|monthly-check|full-check|monthly-dump}"
exit 1
;;
esac
exit 0

88
scripts/verify-vzdump-level2.sh Executable file
View File

@@ -0,0 +1,88 @@
#!/bin/bash
# Тест восстановления уровня 2: автотест vzdump CT 107.
# Восстанавливает последний vzdump-lxc-107 в временный CT 999, проверяет запуск, удаляет.
# Запускать на хосте Proxmox под root. Ежемесячно (systemd timer).
# При успехе/ошибке — уведомление в Telegram.
set -e
DUMP_DIR="/mnt/backup/proxmox/dump/dump"
TEST_VMID=999
TEST_IP="192.168.1.199/24"
TEST_GW="192.168.1.1"
STORAGE="local-lvm"
WAIT_START_SEC=60
NOTIFY_SCRIPT="${NOTIFY_SCRIPT:-/root/scripts/notify-telegram.sh}"
if [ "$(id -u)" -ne 0 ]; then
echo "Запускайте под root."
exit 1
fi
# Очистка при выходе (успех или ошибка)
cleanup() {
if pct status "$TEST_VMID" &>/dev/null; then
echo "[verify-vzdump] Останавливаем и удаляем CT $TEST_VMID..."
pct stop "$TEST_VMID" --skiplock 2>/dev/null || true
sleep 2
pct destroy "$TEST_VMID" --purge 1 --force 2>/dev/null || true
fi
}
trap cleanup EXIT INT TERM
notify_ok() {
[ -x "$NOTIFY_SCRIPT" ] && "$NOTIFY_SCRIPT" "✅ Тест vzdump CT 107" "$1" || true
}
notify_err() {
[ -x "$NOTIFY_SCRIPT" ] && "$NOTIFY_SCRIPT" "⚠️ Тест vzdump CT 107" "Ошибка: $1" || true
}
if [ ! -d "$DUMP_DIR" ]; then
notify_err "Каталог $DUMP_DIR не найден."
exit 1
fi
# Последний vzdump-lxc-107
ARCHIVE=$(ls -t "$DUMP_DIR"/vzdump-lxc-107-*.tar.zst 2>/dev/null | head -1)
if [ -z "$ARCHIVE" ] || [ ! -f "$ARCHIVE" ]; then
notify_err "Не найден vzdump-lxc-107-*.tar.zst в $DUMP_DIR"
exit 1
fi
echo "[verify-vzdump] Архив: $ARCHIVE"
# Убедиться, что CT 999 не существует (остаток от прошлого запуска)
if pct status "$TEST_VMID" &>/dev/null; then
pct destroy "$TEST_VMID" --purge 1 --force 2>/dev/null || true
sleep 2
fi
echo "[verify-vzdump] Создаём CT $TEST_VMID из архива..."
if ! pct create "$TEST_VMID" "$ARCHIVE" --restore 1 --storage "$STORAGE" 2>&1; then
notify_err "pct create не удался."
exit 1
fi
# Другой IP, чтобы не конфликтовать с оригиналом 107
echo "[verify-vzdump] Настраиваем сеть (IP $TEST_IP)..."
pct set "$TEST_VMID" --net0 "name=eth0,bridge=vmbr0,gw=$TEST_GW,ip=$TEST_IP,type=veth"
echo "[verify-vzdump] Запускаем CT $TEST_VMID..."
if ! pct start "$TEST_VMID" 2>&1; then
notify_err "pct start не удался."
exit 1
fi
echo "[verify-vzdump] Ожидание $WAIT_START_SEC сек..."
sleep "$WAIT_START_SEC"
STATUS=$(pct exec "$TEST_VMID" -- systemctl is-system-running 2>/dev/null || echo "unknown")
if [ "$STATUS" != "running" ]; then
notify_err "systemctl is-system-running вернул: $STATUS (ожидалось running)"
exit 1
fi
echo "[verify-vzdump] CT 999 запущен, system running. Тест пройден."
notify_ok "OK"
exit 0

58
scripts/watchdog-timers.sh Executable file
View File

@@ -0,0 +1,58 @@
#!/bin/bash
# Watchdog: проверка провалившихся systemd timers.
# Запускать раз в день (например 12:00). При наличии failed → notify в Telegram.
# Timer: backup-watchdog-timers.timer
NOTIFY_SCRIPT="${NOTIFY_SCRIPT:-/root/scripts/notify-telegram.sh}"
MAX_AGE_HOURS=24
BACKUP_OK_DIR="/var/run"
if [ "$(id -u)" -ne 0 ]; then
echo "Запускайте под root."
exit 1
fi
# 1. Проверка systemctl list-timers --failed
FAILED=$(systemctl list-timers --failed --no-legend --no-pager 2>/dev/null | grep -v '^$' || true)
if [ -n "$FAILED" ]; then
MSG="Провалившиеся таймеры:
$FAILED"
if [ -x "$NOTIFY_SCRIPT" ]; then
"$NOTIFY_SCRIPT" "⚠️ Systemd timers" "$MSG" || true
fi
echo "[watchdog] Найдены провалившиеся таймеры"
echo "$FAILED"
exit 1
fi
# 2. Проверка healthcheck-файлов (если файл старше 24 ч — алерт)
BACKUP_NAMES="vps-miran ct101-pgdump immich-photos vps-mtproto etc-pve ct104-pgdump vaultwarden-data ct103-gitea-pgdump vm200-pgdump ct105-vectors restic-yandex restic-yandex-photos"
STALE=""
for name in $BACKUP_NAMES; do
OK_FILE="$BACKUP_OK_DIR/backup-$name.ok"
if [ -f "$OK_FILE" ]; then
AGE_SEC=$(( $(date +%s) - $(stat -c %Y "$OK_FILE" 2>/dev/null || echo 0) ))
AGE_HOURS=$(( AGE_SEC / 3600 ))
if [ "$AGE_HOURS" -ge "$MAX_AGE_HOURS" ]; then
STALE="${STALE}backup-$name.ok (${AGE_HOURS}h)
"
fi
else
STALE="${STALE}backup-$name.ok (отсутствует)
"
fi
done
if [ -n "$STALE" ]; then
MSG="Файлы .ok старше ${MAX_AGE_HOURS} ч или отсутствуют (последний успешный бэкап):
$STALE"
if [ -x "$NOTIFY_SCRIPT" ]; then
"$NOTIFY_SCRIPT" "⚠️ Backup watchdog" "$MSG" || true
fi
echo "[watchdog] Устаревшие healthcheck-файлы"
echo "$STALE"
exit 1
fi
echo "[watchdog] OK: таймеры и healthcheck-файлы в порядке"
exit 0