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