From 53769e683265199d581f38c35330d8031a34fa6a Mon Sep 17 00:00:00 2001 From: Andrey Date: Sat, 28 Feb 2026 15:43:39 +0300 Subject: [PATCH] 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. --- docs/architecture/architecture.md | 4 +- docs/backup/backup-howto.md | 87 +++++--- docs/backup/restore-test-manual.md | 140 ++++++++++++ docs/containers/host-proxmox.md | 146 +++++++++++++ docs/monitoring/netdata-proxmox-setup.md | 202 ++++++++++++++++++ docs/monitoring/smartd-setup.md | 109 ++++++++++ docs/network/network-topology.md | 5 +- docs/vps/healthchecks-miran-setup.md | 112 ++++++++++ docs/vps/vps-miran-bots.md | 5 +- scripts/backup-restic-yandex-photos.sh | 2 + scripts/backup-restic-yandex.sh | 3 + scripts/healthcheck-ping.sh | 20 ++ scripts/healthchecks-docker/.env.example | 23 ++ .../healthchecks-docker/docker-compose.yml | 31 +++ scripts/healthchecks-nginx-server.conf | 25 +++ scripts/healthchecks.env.example | 6 + scripts/smartd-notify.sh | 30 +++ scripts/systemd/README.md | 24 +++ scripts/systemd/backup-ct101-pgdump.service | 14 ++ scripts/systemd/backup-ct101-pgdump.timer | 9 + .../systemd/backup-ct103-gitea-pgdump.service | 14 ++ .../systemd/backup-ct103-gitea-pgdump.timer | 9 + scripts/systemd/backup-ct104-pgdump.service | 14 ++ scripts/systemd/backup-ct104-pgdump.timer | 9 + scripts/systemd/backup-ct105-vectors.service | 14 ++ scripts/systemd/backup-ct105-vectors.timer | 9 + scripts/systemd/backup-etc-pve.service | 14 ++ scripts/systemd/backup-etc-pve.timer | 9 + .../systemd/backup-healthcheck-ping.service | 14 ++ scripts/systemd/backup-healthcheck-ping.timer | 9 + scripts/systemd/backup-immich-photos.service | 14 ++ scripts/systemd/backup-immich-photos.timer | 9 + .../backup-restic-yandex-photos.service | 16 ++ .../systemd/backup-restic-yandex-photos.timer | 9 + scripts/systemd/backup-restic-yandex.service | 16 ++ scripts/systemd/backup-restic-yandex.timer | 9 + .../systemd/backup-vaultwarden-data.service | 14 ++ scripts/systemd/backup-vaultwarden-data.timer | 9 + scripts/systemd/backup-vm200-pgdump.service | 14 ++ scripts/systemd/backup-vm200-pgdump.timer | 9 + scripts/systemd/backup-vps-miran.service | 17 ++ scripts/systemd/backup-vps-miran.timer | 9 + scripts/systemd/backup-vps-mtproto.service | 14 ++ scripts/systemd/backup-vps-mtproto.timer | 9 + .../systemd/backup-watchdog-timers.service | 14 ++ scripts/systemd/backup-watchdog-timers.timer | 9 + scripts/systemd/notify-vzdump-success.service | 15 ++ scripts/systemd/notify-vzdump-success.timer | 9 + .../verify-restore-level1-full-check.service | 14 ++ .../verify-restore-level1-full-check.timer | 10 + ...erify-restore-level1-monthly-check.service | 14 ++ .../verify-restore-level1-monthly-check.timer | 9 + ...verify-restore-level1-monthly-dump.service | 14 ++ .../verify-restore-level1-monthly-dump.timer | 9 + .../verify-restore-level1-weekly.service | 14 ++ .../verify-restore-level1-weekly.timer | 9 + scripts/systemd/verify-vzdump-level2.service | 14 ++ scripts/systemd/verify-vzdump-level2.timer | 9 + scripts/verify-restore-level1.sh | 147 +++++++++++++ scripts/verify-vzdump-level2.sh | 88 ++++++++ scripts/watchdog-timers.sh | 58 +++++ 61 files changed, 1697 insertions(+), 39 deletions(-) create mode 100644 docs/backup/restore-test-manual.md create mode 100644 docs/containers/host-proxmox.md create mode 100644 docs/monitoring/netdata-proxmox-setup.md create mode 100644 docs/monitoring/smartd-setup.md create mode 100644 docs/vps/healthchecks-miran-setup.md create mode 100755 scripts/healthcheck-ping.sh create mode 100644 scripts/healthchecks-docker/.env.example create mode 100644 scripts/healthchecks-docker/docker-compose.yml create mode 100644 scripts/healthchecks-nginx-server.conf create mode 100644 scripts/healthchecks.env.example create mode 100755 scripts/smartd-notify.sh create mode 100644 scripts/systemd/README.md create mode 100644 scripts/systemd/backup-ct101-pgdump.service create mode 100644 scripts/systemd/backup-ct101-pgdump.timer create mode 100644 scripts/systemd/backup-ct103-gitea-pgdump.service create mode 100644 scripts/systemd/backup-ct103-gitea-pgdump.timer create mode 100644 scripts/systemd/backup-ct104-pgdump.service create mode 100644 scripts/systemd/backup-ct104-pgdump.timer create mode 100644 scripts/systemd/backup-ct105-vectors.service create mode 100644 scripts/systemd/backup-ct105-vectors.timer create mode 100644 scripts/systemd/backup-etc-pve.service create mode 100644 scripts/systemd/backup-etc-pve.timer create mode 100644 scripts/systemd/backup-healthcheck-ping.service create mode 100644 scripts/systemd/backup-healthcheck-ping.timer create mode 100644 scripts/systemd/backup-immich-photos.service create mode 100644 scripts/systemd/backup-immich-photos.timer create mode 100644 scripts/systemd/backup-restic-yandex-photos.service create mode 100644 scripts/systemd/backup-restic-yandex-photos.timer create mode 100644 scripts/systemd/backup-restic-yandex.service create mode 100644 scripts/systemd/backup-restic-yandex.timer create mode 100644 scripts/systemd/backup-vaultwarden-data.service create mode 100644 scripts/systemd/backup-vaultwarden-data.timer create mode 100644 scripts/systemd/backup-vm200-pgdump.service create mode 100644 scripts/systemd/backup-vm200-pgdump.timer create mode 100644 scripts/systemd/backup-vps-miran.service create mode 100644 scripts/systemd/backup-vps-miran.timer create mode 100644 scripts/systemd/backup-vps-mtproto.service create mode 100644 scripts/systemd/backup-vps-mtproto.timer create mode 100644 scripts/systemd/backup-watchdog-timers.service create mode 100644 scripts/systemd/backup-watchdog-timers.timer create mode 100644 scripts/systemd/notify-vzdump-success.service create mode 100644 scripts/systemd/notify-vzdump-success.timer create mode 100644 scripts/systemd/verify-restore-level1-full-check.service create mode 100644 scripts/systemd/verify-restore-level1-full-check.timer create mode 100644 scripts/systemd/verify-restore-level1-monthly-check.service create mode 100644 scripts/systemd/verify-restore-level1-monthly-check.timer create mode 100644 scripts/systemd/verify-restore-level1-monthly-dump.service create mode 100644 scripts/systemd/verify-restore-level1-monthly-dump.timer create mode 100644 scripts/systemd/verify-restore-level1-weekly.service create mode 100644 scripts/systemd/verify-restore-level1-weekly.timer create mode 100644 scripts/systemd/verify-vzdump-level2.service create mode 100644 scripts/systemd/verify-vzdump-level2.timer create mode 100755 scripts/verify-restore-level1.sh create mode 100755 scripts/verify-vzdump-level2.sh create mode 100755 scripts/watchdog-timers.sh diff --git a/docs/architecture/architecture.md b/docs/architecture/architecture.md index d19d836..4084df7 100644 --- a/docs/architecture/architecture.md +++ b/docs/architecture/architecture.md @@ -24,6 +24,7 @@ | cloud.katykhin.ru | — | | docs.katykhin.ru | — | | git.katykhin.ru | — | +| healthchecks.katykhin.ru | Healthchecks (Dead man's switch для бэкапов; на VPS Миран) | | home.katykhin.ru | Homepage | | immich.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). -- **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). - **Роутер:** 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). diff --git a/docs/backup/backup-howto.md b/docs/backup/backup-howto.md index 83738b0..95c0862 100644 --- a/docs/backup/backup-howto.md +++ b/docs/backup/backup-howto.md @@ -8,6 +8,16 @@ Все локальные бэкапы лежат на отдельном диске хоста 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/ ├── 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 Миран | -| **БД Nextcloud (PostgreSQL)** | CT 101, контейнер `nextcloud-db-1` в `/opt/nextcloud` | `/mnt/backup/databases/ct101-nextcloud/` | **01:15** (cron: `backup-ct101-pgdump.sh`) | 14 дней | 🗄️ Nextcloud (БД) | -| **Оригиналы фото Immich** | VM 200, `/mnt/data/library/` | `/mnt/backup/photos/library/` | **01:30** (cron: `backup-immich-photos.sh`, rsync) | Все копии (без автоудаления) | 📷 Фото 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) | -| **LXC и VM** | Все выбранные контейнеры (100–109) и VM 200 | `/mnt/backup/proxmox/dump/` | **02:00** (задание в Proxmox UI) | По настройкам задания (7 daily, 4 weekly, 6 monthly) | 💾 Backup local (cron 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 дней | ⚙️ Конфиги хоста | -| **БД Paperless (PostgreSQL)** | CT 104, контейнер `paperless-db-1` в `/opt/paperless` | `/mnt/backup/databases/ct104-paperless/` | **02:30** (cron: `backup-ct104-pgdump.sh`) | 14 дней | 🗄️ Paperless (БД) | -| **Данные Vaultwarden (пароли)** | CT 103, `/opt/docker/vaultwarden/data` | `/mnt/backup/other/vaultwarden/`; каталог в restic → Yandex | **02:45** (cron: `backup-vaultwarden-data.sh`) | 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 (БД) | -| **БД Immich (PostgreSQL)** | VM 200, контейнер `database` в `/opt/immich` | `/mnt/backup/databases/vm200-immich/` | **03:15** (cron: `backup-vm200-pgdump.sh`) | 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 | -| **Выгрузка в 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:10** (cron: `backup-restic-yandex-photos.sh`) | 3 daily, 2 weekly, 2 monthly | 📷 Restic Yandex (photos) | +| **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** (timer: `backup-ct101-pgdump`) | 14 дней | 🗄️ Nextcloud (БД) | +| **Оригиналы фото 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** (timer: `backup-vps-mtproto`) | 14 дней | 🌐 VPS MTProto (DE) | +| **LXC и VM** | Все выбранные контейнеры (100–109) и 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** (timer: `backup-etc-pve`) | 30 дней | ⚙️ Конфиги хоста | +| **БД 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** (timer: `backup-vaultwarden-data`) | 14 дней | 🔐 Vaultwarden | +| **БД 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** (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** (timer: `backup-ct105-vectors`) | 14 дней | 📐 Векторы RAG | +| **Выгрузка в 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** (timer: `backup-restic-yandex-photos`) | 3 daily, 2 weekly, 2 monthly | 📷 Restic Yandex (photos) | -**Окно бэкапов:** внутренние копии — **01:00–03:30**; выгрузка в облако — **04:00** (основной restic), **04:10** (restic фото). **05:00** зарезервировано под плановую перезагрузку сервера. Задание vzdump — из веб-интерфейса Proxmox (Центр обработки данных → Резервная копия). +**Окно бэкапов:** внутренние копии — **01:00–03: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 `. -| Скрипт | Назначение | Cron | -|--------|------------|------| -| `/root/scripts/backup-vps-miran.sh` | Бэкап VPS Миран: БД бота, voice_users, S3 (Miran) | 0 1 * * * | -| `/root/scripts/backup-ct101-pgdump.sh` | Логический дамп БД Nextcloud из CT 101 | 15 1 * * * | -| `/root/scripts/backup-immich-photos.sh` | Копирование библиотеки фото Immich (rsync с VM 200) | 30 1 * * * | -| `/root/scripts/backup-vps-mtproto.sh` | Копирование конфигов MTProto + сайт с VPS Германия (185.103.253.99) | 45 1 * * * | -| `/root/scripts/backup-etc-pve.sh` | Бэкап /etc/pve и конфигов хоста | 15 2 * * * | -| `/root/scripts/backup-ct104-pgdump.sh` | Логический дамп БД Paperless из CT 104 | 30 2 * * * | -| `/root/scripts/backup-vaultwarden-data.sh` | Копирование данных Vaultwarden (пароли) из CT 103 | 45 2 * * * | -| `/root/scripts/backup-ct103-gitea-pgdump.sh` | Логический дамп БД Gitea из CT 103 | 0 3 * * * | -| `/root/scripts/notify-vzdump-success.sh` | Проверка локального vzdump за последние 2 ч, отправка сводки в Telegram | 0 3 * * * | -| `/root/scripts/backup-vm200-pgdump.sh` | Логический дамп БД Immich с VM 200 | 15 3 * * * | -| `/root/scripts/backup-ct105-vectors.sh` | Копирование векторов RAG (vectors.npz) из CT 105 | 30 3 * * * | -| `/root/scripts/backup-restic-yandex.sh` | Выгрузка /mnt/backup (без photos) в Yandex S3 (restic), retention 3/2/2 | 0 4 * * * | -| `/root/scripts/backup-restic-yandex-photos.sh` | Выгрузка только /mnt/backup/photos в Yandex S3 (тот же репо), retention 3/2/2 | 10 4 * * * | -| `/root/scripts/notify-telegram.sh` | Шлюз отправки уведомлений в Telegram (вызывают скрипты бэкапов) | — | +| Скрипт | Timer | Расписание | +|--------|-------|------------| +| `backup-vps-miran.sh` | backup-vps-miran.timer | 01:00 | +| `backup-ct101-pgdump.sh` | backup-ct101-pgdump.timer | 01:15 | +| `backup-immich-photos.sh` | backup-immich-photos.timer | 01:30 | +| `backup-vps-mtproto.sh` | backup-vps-mtproto.timer | 01:45 | +| `backup-etc-pve.sh` | backup-etc-pve.timer | 02:15 | +| `backup-ct104-pgdump.sh` | backup-ct104-pgdump.timer | 02:30 | +| `backup-vaultwarden-data.sh` | backup-vaultwarden-data.timer | 02:45 | +| `backup-ct103-gitea-pgdump.sh` | backup-ct103-gitea-pgdump.timer | 03:00 | +| `notify-vzdump-success.sh` | notify-vzdump-success.timer | 03:00 | +| `backup-vm200-pgdump.sh` | backup-vm200-pgdump.timer | 03:15 | +| `backup-ct105-vectors.sh` | backup-ct105-vectors.timer | 03:30 | +| `backup-restic-yandex.sh` | backup-restic-yandex.timer | 04:00 | +| `backup-restic-yandex-photos.sh` | backup-restic-yandex-photos.timer | 04:10 | +| `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-.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** оставлено свободным для плановой перезагрузки сервера. @@ -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 ``` -**Запуск из 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 отправляется короткое сообщение (заголовок с эмодзи + краткая сводка). Уведомления приходят по завершении соответствующего скрипта; для локального vzdump — по cron в **03:00** (проверка файлов за последние 2 часа). +После **успешного** выполнения каждого бэкапа в Telegram отправляется короткое сообщение (заголовок с эмодзи + краткая сводка). Уведомления приходят по завершении соответствующего скрипта; для локального vzdump — таймер `notify-vzdump-success` в **03:00** (проверка файлов за последние 2 часа). | Заголовок | Когда | Тело сообщения | @@ -516,8 +531,6 @@ chmod 600 /root/.telegram-notify.env Если конфига или кредов нет, шлюз тихо выходит с 0 и не ломает вызывающие скрипты. -**Позже** тот же шлюз можно вызывать с VM 200 или с VPS (например по SSH на хост Proxmox) — отдельно не реализовано, архитектура это допускает. - --- ## Связанные документы @@ -525,4 +538,8 @@ chmod 600 /root/.telegram-notify.env - [Vaultwarden и секреты](../vaultwarden-secrets.md) — получение паролей через `bw` для скриптов бэкапов. - [Архитектура](../architecture/architecture.md) — хост, IP, доступ. - [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) — мониторинг дисков, уведомления при отклонениях. diff --git a/docs/backup/restore-test-manual.md b/docs/backup/restore-test-manual.md new file mode 100644 index 0000000..e5d1cd2 --- /dev/null +++ b/docs/backup/restore-test-manual.md @@ -0,0 +1,140 @@ +# Ручной тест восстановления (уровень 3) + +Пошаговые команды для полной проверки восстановления после потери данных или миграции. Выполнять периодически (раз в 6–12 месяцев) или после значительных изменений инфраструктуры. + +--- + +## 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 --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). + - Выбрать небольшое изображение (например 1–2 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, доступ. diff --git a/docs/containers/host-proxmox.md b/docs/containers/host-proxmox.md new file mode 100644 index 0000000..94d3a25 --- /dev/null +++ b/docs/containers/host-proxmox.md @@ -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-.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 Миран diff --git a/docs/monitoring/netdata-proxmox-setup.md b/docs/monitoring/netdata-proxmox-setup.md new file mode 100644 index 0000000..41779ed --- /dev/null +++ b/docs/monitoring/netdata-proxmox-setup.md @@ -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="" +``` + +Креды можно взять из 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) — бэкапы diff --git a/docs/monitoring/smartd-setup.md b/docs/monitoring/smartd-setup.md new file mode 100644 index 0000000..1326e8c --- /dev/null +++ b/docs/monitoring/smartd-setup.md @@ -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). diff --git a/docs/network/network-topology.md b/docs/network/network-topology.md index 69b724f..9783046 100644 --- a/docs/network/network-topology.md +++ b/docs/network/network-topology.md @@ -132,7 +132,7 @@ flowchart TB │ VPS DE │ │ VPS US │ │ VPS Миран (СПБ) │ │ 185.103.253.99 │ │ 147.45.124.117 │ │ 185.147.80.190 │ │ 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 | | **VPS DE** | 185.103.253.99 | AmneziaWG (обход блокировок) | Туннель с роутера (10.8.1.x) | | **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 | --- @@ -241,6 +241,7 @@ flowchart TB ## Связь с другими документами - [Архитектура и подключение](../architecture/architecture.md) — общее описание, таблица контейнеров, поток запросов. +- [Хост Proxmox](../containers/host-proxmox.md) — скрипты, таймеры, пути на 192.168.1.150. - [Контейнер 100](../containers/container-100.md) — NPM, AdGuard, Homepage, порядок запуска. - [Контейнер 109](../containers/container-109.md) — WireGuard VPN (local-vpn), доступ к vault и LAN. - [Генерация .mobileconfig для WireGuard (On-Demand)](vpn-mobileconfig-wireguard.md) — как собрать профиль для iOS/macOS с автоматическим подключением вне дома. diff --git a/docs/vps/healthchecks-miran-setup.md b/docs/vps/healthchecks-miran-setup.md new file mode 100644 index 0000000..97f329b --- /dev/null +++ b/docs/vps/healthchecks-miran-setup.md @@ -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= +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= + +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 +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= +``` + +--- + +## Смена пароля без 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) — бэкапы, расписание diff --git a/docs/vps/vps-miran-bots.md b/docs/vps/vps-miran-bots.md index 5c1ea33..8d7974d 100644 --- a/docs/vps/vps-miran-bots.md +++ b/docs/vps/vps-miran-bots.md @@ -81,6 +81,9 @@ VPS в ЦОД Миран (Санкт-Петербург). Развёрнуты | 9100 | node-exporter | TCP | | 3000 | Grafana | 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) -Бэкап выполняется **с хоста 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 дней). 2. **Голосовые сообщения:** `/home/prod/bots/telegram-helper-bot/voice_users/` → `/mnt/backup/vps/miran/voice_users/` (rsync). diff --git a/scripts/backup-restic-yandex-photos.sh b/scripts/backup-restic-yandex-photos.sh index 1c1d27c..00e47d7 100644 --- a/scripts/backup-restic-yandex-photos.sh +++ b/scripts/backup-restic-yandex-photos.sh @@ -4,6 +4,8 @@ # Запускать на хосте Proxmox под root. Секреты из Vaultwarden (объект RESTIC), файл /root/.bw-master (chmod 600). # Cron: 10 4 * * * (04:10, после основного restic в 04:00). set -e +export PATH="/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:${PATH:-}" +export HOME="${HOME:-/root}" BACKUP_PATH="/mnt/backup/photos" # Время запуска (для логов и уведомлений) diff --git a/scripts/backup-restic-yandex.sh b/scripts/backup-restic-yandex.sh index 99a3ec4..848d7a1 100644 --- a/scripts/backup-restic-yandex.sh +++ b/scripts/backup-restic-yandex.sh @@ -6,6 +6,9 @@ # Перед первым запуском: установить restic, bw (Bitwarden CLI), jq; bw config server https://vault.katykhin.ru; restic init. # Cron: 0 4 * * * (04:00, после окна 01:00–03:30; 05:00 зарезервировано под перезагрузку). 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" # Время запуска (для логов и уведомлений) diff --git a/scripts/healthcheck-ping.sh b/scripts/healthcheck-ping.sh new file mode 100755 index 0000000..544145e --- /dev/null +++ b/scripts/healthcheck-ping.sh @@ -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 diff --git a/scripts/healthchecks-docker/.env.example b/scripts/healthchecks-docker/.env.example new file mode 100644 index 0000000..795dd9f --- /dev/null +++ b/scripts/healthchecks-docker/.env.example @@ -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 diff --git a/scripts/healthchecks-docker/docker-compose.yml b/scripts/healthchecks-docker/docker-compose.yml new file mode 100644 index 0000000..42c3afe --- /dev/null +++ b/scripts/healthchecks-docker/docker-compose.yml @@ -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 diff --git a/scripts/healthchecks-nginx-server.conf b/scripts/healthchecks-nginx-server.conf new file mode 100644 index 0000000..98a38bf --- /dev/null +++ b/scripts/healthchecks-nginx-server.conf @@ -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; } + } diff --git a/scripts/healthchecks.env.example b/scripts/healthchecks.env.example new file mode 100644 index 0000000..872e0c0 --- /dev/null +++ b/scripts/healthchecks.env.example @@ -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= diff --git a/scripts/smartd-notify.sh b/scripts/smartd-notify.sh new file mode 100755 index 0000000..d3b6e95 --- /dev/null +++ b/scripts/smartd-notify.sh @@ -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 diff --git a/scripts/systemd/README.md b/scripts/systemd/README.md new file mode 100644 index 0000000..a4f2cca --- /dev/null +++ b/scripts/systemd/README.md @@ -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`). diff --git a/scripts/systemd/backup-ct101-pgdump.service b/scripts/systemd/backup-ct101-pgdump.service new file mode 100644 index 0000000..2c9bbb6 --- /dev/null +++ b/scripts/systemd/backup-ct101-pgdump.service @@ -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 diff --git a/scripts/systemd/backup-ct101-pgdump.timer b/scripts/systemd/backup-ct101-pgdump.timer new file mode 100644 index 0000000..068f5cf --- /dev/null +++ b/scripts/systemd/backup-ct101-pgdump.timer @@ -0,0 +1,9 @@ +[Unit] +Description=Backup Nextcloud DB daily at 01:15 + +[Timer] +OnCalendar=*-*-* 01:15:00 +Persistent=yes + +[Install] +WantedBy=timers.target diff --git a/scripts/systemd/backup-ct103-gitea-pgdump.service b/scripts/systemd/backup-ct103-gitea-pgdump.service new file mode 100644 index 0000000..d24504a --- /dev/null +++ b/scripts/systemd/backup-ct103-gitea-pgdump.service @@ -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 diff --git a/scripts/systemd/backup-ct103-gitea-pgdump.timer b/scripts/systemd/backup-ct103-gitea-pgdump.timer new file mode 100644 index 0000000..03509d7 --- /dev/null +++ b/scripts/systemd/backup-ct103-gitea-pgdump.timer @@ -0,0 +1,9 @@ +[Unit] +Description=Backup Gitea DB daily at 03:00 + +[Timer] +OnCalendar=*-*-* 03:00:00 +Persistent=yes + +[Install] +WantedBy=timers.target diff --git a/scripts/systemd/backup-ct104-pgdump.service b/scripts/systemd/backup-ct104-pgdump.service new file mode 100644 index 0000000..c4bee40 --- /dev/null +++ b/scripts/systemd/backup-ct104-pgdump.service @@ -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 diff --git a/scripts/systemd/backup-ct104-pgdump.timer b/scripts/systemd/backup-ct104-pgdump.timer new file mode 100644 index 0000000..2f28f0e --- /dev/null +++ b/scripts/systemd/backup-ct104-pgdump.timer @@ -0,0 +1,9 @@ +[Unit] +Description=Backup Paperless DB daily at 02:30 + +[Timer] +OnCalendar=*-*-* 02:30:00 +Persistent=yes + +[Install] +WantedBy=timers.target diff --git a/scripts/systemd/backup-ct105-vectors.service b/scripts/systemd/backup-ct105-vectors.service new file mode 100644 index 0000000..a95136b --- /dev/null +++ b/scripts/systemd/backup-ct105-vectors.service @@ -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 diff --git a/scripts/systemd/backup-ct105-vectors.timer b/scripts/systemd/backup-ct105-vectors.timer new file mode 100644 index 0000000..11e07d8 --- /dev/null +++ b/scripts/systemd/backup-ct105-vectors.timer @@ -0,0 +1,9 @@ +[Unit] +Description=Backup RAG vectors daily at 03:30 + +[Timer] +OnCalendar=*-*-* 03:30:00 +Persistent=yes + +[Install] +WantedBy=timers.target diff --git a/scripts/systemd/backup-etc-pve.service b/scripts/systemd/backup-etc-pve.service new file mode 100644 index 0000000..8ea4e1b --- /dev/null +++ b/scripts/systemd/backup-etc-pve.service @@ -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 diff --git a/scripts/systemd/backup-etc-pve.timer b/scripts/systemd/backup-etc-pve.timer new file mode 100644 index 0000000..3e8c6b4 --- /dev/null +++ b/scripts/systemd/backup-etc-pve.timer @@ -0,0 +1,9 @@ +[Unit] +Description=Backup etc-pve daily at 02:15 + +[Timer] +OnCalendar=*-*-* 02:15:00 +Persistent=yes + +[Install] +WantedBy=timers.target diff --git a/scripts/systemd/backup-healthcheck-ping.service b/scripts/systemd/backup-healthcheck-ping.service new file mode 100644 index 0000000..d4eccd4 --- /dev/null +++ b/scripts/systemd/backup-healthcheck-ping.service @@ -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 diff --git a/scripts/systemd/backup-healthcheck-ping.timer b/scripts/systemd/backup-healthcheck-ping.timer new file mode 100644 index 0000000..8a37561 --- /dev/null +++ b/scripts/systemd/backup-healthcheck-ping.timer @@ -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 diff --git a/scripts/systemd/backup-immich-photos.service b/scripts/systemd/backup-immich-photos.service new file mode 100644 index 0000000..7313124 --- /dev/null +++ b/scripts/systemd/backup-immich-photos.service @@ -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 diff --git a/scripts/systemd/backup-immich-photos.timer b/scripts/systemd/backup-immich-photos.timer new file mode 100644 index 0000000..7a7c1da --- /dev/null +++ b/scripts/systemd/backup-immich-photos.timer @@ -0,0 +1,9 @@ +[Unit] +Description=Backup Immich photos daily at 01:30 + +[Timer] +OnCalendar=*-*-* 01:30:00 +Persistent=yes + +[Install] +WantedBy=timers.target diff --git a/scripts/systemd/backup-restic-yandex-photos.service b/scripts/systemd/backup-restic-yandex-photos.service new file mode 100644 index 0000000..182d846 --- /dev/null +++ b/scripts/systemd/backup-restic-yandex-photos.service @@ -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 diff --git a/scripts/systemd/backup-restic-yandex-photos.timer b/scripts/systemd/backup-restic-yandex-photos.timer new file mode 100644 index 0000000..a7f85c3 --- /dev/null +++ b/scripts/systemd/backup-restic-yandex-photos.timer @@ -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 diff --git a/scripts/systemd/backup-restic-yandex.service b/scripts/systemd/backup-restic-yandex.service new file mode 100644 index 0000000..6a81b13 --- /dev/null +++ b/scripts/systemd/backup-restic-yandex.service @@ -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 diff --git a/scripts/systemd/backup-restic-yandex.timer b/scripts/systemd/backup-restic-yandex.timer new file mode 100644 index 0000000..3111c9d --- /dev/null +++ b/scripts/systemd/backup-restic-yandex.timer @@ -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 diff --git a/scripts/systemd/backup-vaultwarden-data.service b/scripts/systemd/backup-vaultwarden-data.service new file mode 100644 index 0000000..486ea41 --- /dev/null +++ b/scripts/systemd/backup-vaultwarden-data.service @@ -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 diff --git a/scripts/systemd/backup-vaultwarden-data.timer b/scripts/systemd/backup-vaultwarden-data.timer new file mode 100644 index 0000000..5a971af --- /dev/null +++ b/scripts/systemd/backup-vaultwarden-data.timer @@ -0,0 +1,9 @@ +[Unit] +Description=Backup Vaultwarden daily at 02:45 + +[Timer] +OnCalendar=*-*-* 02:45:00 +Persistent=yes + +[Install] +WantedBy=timers.target diff --git a/scripts/systemd/backup-vm200-pgdump.service b/scripts/systemd/backup-vm200-pgdump.service new file mode 100644 index 0000000..ac7d376 --- /dev/null +++ b/scripts/systemd/backup-vm200-pgdump.service @@ -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 diff --git a/scripts/systemd/backup-vm200-pgdump.timer b/scripts/systemd/backup-vm200-pgdump.timer new file mode 100644 index 0000000..7baa733 --- /dev/null +++ b/scripts/systemd/backup-vm200-pgdump.timer @@ -0,0 +1,9 @@ +[Unit] +Description=Backup Immich DB daily at 03:15 + +[Timer] +OnCalendar=*-*-* 03:15:00 +Persistent=yes + +[Install] +WantedBy=timers.target diff --git a/scripts/systemd/backup-vps-miran.service b/scripts/systemd/backup-vps-miran.service new file mode 100644 index 0000000..d7442a2 --- /dev/null +++ b/scripts/systemd/backup-vps-miran.service @@ -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 diff --git a/scripts/systemd/backup-vps-miran.timer b/scripts/systemd/backup-vps-miran.timer new file mode 100644 index 0000000..a955d8a --- /dev/null +++ b/scripts/systemd/backup-vps-miran.timer @@ -0,0 +1,9 @@ +[Unit] +Description=Backup VPS Miran daily at 01:00 + +[Timer] +OnCalendar=*-*-* 01:00:00 +Persistent=yes + +[Install] +WantedBy=timers.target diff --git a/scripts/systemd/backup-vps-mtproto.service b/scripts/systemd/backup-vps-mtproto.service new file mode 100644 index 0000000..e256c3f --- /dev/null +++ b/scripts/systemd/backup-vps-mtproto.service @@ -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 diff --git a/scripts/systemd/backup-vps-mtproto.timer b/scripts/systemd/backup-vps-mtproto.timer new file mode 100644 index 0000000..6454c21 --- /dev/null +++ b/scripts/systemd/backup-vps-mtproto.timer @@ -0,0 +1,9 @@ +[Unit] +Description=Backup VPS MTProto daily at 01:45 + +[Timer] +OnCalendar=*-*-* 01:45:00 +Persistent=yes + +[Install] +WantedBy=timers.target diff --git a/scripts/systemd/backup-watchdog-timers.service b/scripts/systemd/backup-watchdog-timers.service new file mode 100644 index 0000000..36aab87 --- /dev/null +++ b/scripts/systemd/backup-watchdog-timers.service @@ -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 diff --git a/scripts/systemd/backup-watchdog-timers.timer b/scripts/systemd/backup-watchdog-timers.timer new file mode 100644 index 0000000..722fe84 --- /dev/null +++ b/scripts/systemd/backup-watchdog-timers.timer @@ -0,0 +1,9 @@ +[Unit] +Description=Backup watchdog daily at 12:00 + +[Timer] +OnCalendar=*-*-* 12:00:00 +Persistent=yes + +[Install] +WantedBy=timers.target diff --git a/scripts/systemd/notify-vzdump-success.service b/scripts/systemd/notify-vzdump-success.service new file mode 100644 index 0000000..5b9762f --- /dev/null +++ b/scripts/systemd/notify-vzdump-success.service @@ -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 diff --git a/scripts/systemd/notify-vzdump-success.timer b/scripts/systemd/notify-vzdump-success.timer new file mode 100644 index 0000000..707245d --- /dev/null +++ b/scripts/systemd/notify-vzdump-success.timer @@ -0,0 +1,9 @@ +[Unit] +Description=Notify vzdump success daily at 03:00 + +[Timer] +OnCalendar=*-*-* 03:00:00 +Persistent=yes + +[Install] +WantedBy=timers.target diff --git a/scripts/systemd/verify-restore-level1-full-check.service b/scripts/systemd/verify-restore-level1-full-check.service new file mode 100644 index 0000000..1b4fdf3 --- /dev/null +++ b/scripts/systemd/verify-restore-level1-full-check.service @@ -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 diff --git a/scripts/systemd/verify-restore-level1-full-check.timer b/scripts/systemd/verify-restore-level1-full-check.timer new file mode 100644 index 0000000..1ed0c5d --- /dev/null +++ b/scripts/systemd/verify-restore-level1-full-check.timer @@ -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 diff --git a/scripts/systemd/verify-restore-level1-monthly-check.service b/scripts/systemd/verify-restore-level1-monthly-check.service new file mode 100644 index 0000000..93b7685 --- /dev/null +++ b/scripts/systemd/verify-restore-level1-monthly-check.service @@ -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 diff --git a/scripts/systemd/verify-restore-level1-monthly-check.timer b/scripts/systemd/verify-restore-level1-monthly-check.timer new file mode 100644 index 0000000..b38ac95 --- /dev/null +++ b/scripts/systemd/verify-restore-level1-monthly-check.timer @@ -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 diff --git a/scripts/systemd/verify-restore-level1-monthly-dump.service b/scripts/systemd/verify-restore-level1-monthly-dump.service new file mode 100644 index 0000000..f789725 --- /dev/null +++ b/scripts/systemd/verify-restore-level1-monthly-dump.service @@ -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 diff --git a/scripts/systemd/verify-restore-level1-monthly-dump.timer b/scripts/systemd/verify-restore-level1-monthly-dump.timer new file mode 100644 index 0000000..baaae59 --- /dev/null +++ b/scripts/systemd/verify-restore-level1-monthly-dump.timer @@ -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 diff --git a/scripts/systemd/verify-restore-level1-weekly.service b/scripts/systemd/verify-restore-level1-weekly.service new file mode 100644 index 0000000..57af43e --- /dev/null +++ b/scripts/systemd/verify-restore-level1-weekly.service @@ -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 diff --git a/scripts/systemd/verify-restore-level1-weekly.timer b/scripts/systemd/verify-restore-level1-weekly.timer new file mode 100644 index 0000000..545c31e --- /dev/null +++ b/scripts/systemd/verify-restore-level1-weekly.timer @@ -0,0 +1,9 @@ +[Unit] +Description=Restic check weekly (Sunday 03:00) + +[Timer] +OnCalendar=Sun *-*-* 03:00:00 +Persistent=yes + +[Install] +WantedBy=timers.target diff --git a/scripts/systemd/verify-vzdump-level2.service b/scripts/systemd/verify-vzdump-level2.service new file mode 100644 index 0000000..9174292 --- /dev/null +++ b/scripts/systemd/verify-vzdump-level2.service @@ -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 diff --git a/scripts/systemd/verify-vzdump-level2.timer b/scripts/systemd/verify-vzdump-level2.timer new file mode 100644 index 0000000..a53911a --- /dev/null +++ b/scripts/systemd/verify-vzdump-level2.timer @@ -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 diff --git a/scripts/verify-restore-level1.sh b/scripts/verify-restore-level1.sh new file mode 100755 index 0000000..8948e54 --- /dev/null +++ b/scripts/verify-restore-level1.sh @@ -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 (раз в 6–12 мес, 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 diff --git a/scripts/verify-vzdump-level2.sh b/scripts/verify-vzdump-level2.sh new file mode 100755 index 0000000..6da3fba --- /dev/null +++ b/scripts/verify-vzdump-level2.sh @@ -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 diff --git a/scripts/watchdog-timers.sh b/scripts/watchdog-timers.sh new file mode 100755 index 0000000..88fc891 --- /dev/null +++ b/scripts/watchdog-timers.sh @@ -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