fix(vds-backup): MSSQL backup INIT->FORMAT + estate backup audit

VDS daily backup пал с 12.06 (line 108, exit 1): mssql-блок (added 11.06,
commit f8ca0794) использовал `WITH ... INIT`, упирался в компрессованный
media-header майских .bak (созданы WITH COMPRESSION на Developer-источнике).
Express не пишет в compression-форматированный media set -> Msg 1844, молчаливый
провал первым же cron-запуском. 5 боевых CMS-баз 3 недели без offsite-копии.

Fix: INIT -> FORMAT (всегда новый media set, иммунно к остаткам). Прогон
verified зелёным: VDS backup OK 95m41s, все 5 .bak на kreknin
(MoreThenCms 910M, StayerCalculator 528M, StayerPrice 39M, TireService 4.5M,
stostayer 990M), speedup 22.62 (--link-dest хардлинкует).

- scripts/vds-backup-rsync-kreknin/run.sh: синхронизирован с задеплоенным
  (mssql-блока в репо не было); FORMAT
- .wiki/concepts/mssql-on-vds.md: gotcha #2 (INIT vs FORMAT) + backup-gap
- .wiki/concepts/backup-inventory-2026-06.md: новая — карта estate × что реально
  бэкапится (с доказательством); триаж дыр
- openwrt UCI backup настроен (cron 03:30 -> kreknin, restricted forced-command
  key) — документация в entity + inventory
- .tasks/kreknin-self-backup.md: backlog #1 SPOF (приёмник сам не бэкапится)
- STATUS.md: incident + audit summary

Урок: бэкап-шаг не готов, пока не предъявлен лог одного реального успеха;
прод-крон не должен быть первым тестом бэкап-пути.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-12 10:21:44 +03:00
parent 1fb7fc2e90
commit b332af66e4
7 changed files with 129 additions and 4 deletions

View File

@@ -0,0 +1,61 @@
---
title: Backup inventory & gap audit — вся estate (2026-06-12)
type: concept
tags: [backup, audit, inventory, infra, ops, disaster-recovery, gap]
related: [[../entities/vds-kzntsv]], [[../entities/books-vds]], [[../entities/ruvds-iis-host]], [[../entities/kreknin-synology]], [[../entities/nl-vds-3xui]], [[../entities/openwrt-router]], [[mssql-on-vds]], [[vds-backup-rsync-kreknin]]
updated: 2026-06-12
---
# Backup inventory & gap audit (2026-06-12)
Полная карта: что за данные на каждой машине и **что реально бэкапится** (с доказательством
последнего успеха, не «настроено по таске»). Триггер аудита — MSSQL-инцидент 12.06: 5 боевых
CMS-баз на [[../entities/vds-kzntsv]] **3 недели не имели offsite-копии** (dump-ветка добавлена
11.06, молча падала первым cron-запуском — см. [[mssql-on-vds]] gotcha #2). Урок: **бэкап-шаг не
готов, пока не предъявлен лог хотя бы одного реального успешного прогона.**
## Матрица
| Машина | Данные / сервисы | Метод | Куда | Расписание | Последний УСПЕХ (доказательство) | Статус |
|---|---|---|---|---|---|---|
| **vds-kzntsv** `89.253.255.94` | gitea, verdaccio (8.6G), registry, traefik, portainer, ntfy, owncloud; DB: postgres, mariadb, mongo, redis, **MSSQL ×5 (MoreThenCms/StayerCalculator/StayerPrice/TireService/stostayer)** | logical dumps + rsync `--link-dest` | kreknin `/volume1/NetBackup/vds-kzntsv/` ret.7 | daily 05:00 | snapshots до 2026-06-12 ✓. **MSSQL — впервые в копии 12.06** (910+528+39+4.5+990 МБ) | ✅ (MSSQL fixed 12.06) |
| **books-vds** `89.253.255.133` | books-db (maria), mongo 4.2, scheduler-mongo, ES 7.10 (928k docs), minio, imgproxy | dumps + ES REST snapshot + rsync | kreknin `/volume1/NetBackup/books-vds/` ret.7 | daily 06:00 | snapshots до 2026-06-12 ✓; ES restore проверен 46 сек (28.05) | ✅ / ⚠️ bookva-* не покрыт |
| **ruvds-iis-host** `80.64.31.36` | `C:\sites\snolla` (8.66G), applicationHost.config, IIS WebConfig, LE PFX, ssh-config. БД нет (→ mssql.kzntsv.site) | rclone sync (SFTP) | kreknin `/volume1/NetBackup/ruvds-iis/` ret.7 | daily 04:30 | snapshots до 2026-06-12 ✓ | ✅ |
| **kreknin-synology** `195.19.90.188` | **приёмник всех 4 пайплайнов** + Hyper Backup vault (430G) + live-сервисы | — | — | — | — | ❌ **SPOF: сам не бэкапится** |
| **nl-vds-3xui** `213.176.64.253` | 3x-UI SQLite `/etc/x-ui/x-ui.db` (все inbound, Reality-ключи, client UUID, SS-ключи) | только локальные `.bak` на том же хосте | (локально) | manual | нет offsite | ❌ **no offsite** |
| **openwrt-router** `192.168.1.1` | UCI `/etc/config/*`, port-forwards, DHCP-резервации, dropbear keys | — | — | — | — | ❌ **none** |
| **windows-recovery-host** DESKTOP-NSEF0UK | DECOMM 08.06; осталось: stostayer IIS ×2, lightrag+postgres, markitdown MCP | скрипт-сирота (таргетит удалённое) | (был) kreknin | (был) 03:00 | таска удалена 11.06 | ⚠️ stale script; lightrag/postgres без копии |
| **dead-synology** `192.168.1.10` | МЁРТВ — данные мигрированы | n/a (живёт в kreknin hbk) | — | — | hbk 2026-05-09 | n/a |
| **snolla-recovery-vm** | УДАЛЕНА 08.06 | n/a | — | — | — | n/a |
## Дыры — статус после триажа 2026-06-12
| # | Дыра | Решение user (2026-06-12) | Статус |
|---|---|---|---|
| 1 | **kreknin сам не бэкапится** — SPOF всей estate (1 том 7 ТБ, RAID не подтверждён; смерть `/volume1` = одновременная потеря offsite-копий ВСЕХ машин) | «задача, но на потом» | ⚪ заведена `[[../../.tasks/kreknin-self-backup]]` (#1 по риску) |
| 2 | **openwrt UCI** — единственный публичный ingress всех CMS, без копии | «можешь сделать» | ✅ **СДЕЛАНО** — см. ниже |
| 3 | **bookva-* на books-vds** (`bookva-db/mongo/es/minio`, 4.7 ГБ tenant-данных) не дампятся/не rsync'ятся | не обсуждалось | ⚠️ открыто (follow-up при активации bookva) |
| 4 | **nl-vds-3xui `x-ui.db`** — профили + Reality-ключи только на хосте | «не нужен» | ⏸ accepted (won't-do) |
| 5 | **windows-host stale script** — таргетит удалённые 08.06 ресурсы | «забыть, не актуально» | ⏸ accepted (ignore) |
### openwrt UCI backup — реализовано 2026-06-12
Daily push с роутера на kreknin, **без раздачи роутеру широких прав в хранилище**:
- На роутере: `dropbearkey` ed25519 `/root/.ssh/id_kreknin`; `/root/uci-backup.sh` = `sysupgrade -b``dbclient` pipe; cron `30 3 * * *`.
- На kreknin: pubkey роутера в `~/.ssh/authorized_keys` с **forced-command** `command="cat > /volume1/NetBackup/openwrt/openwrt-latest.tar.gz"` + `no-port-forwarding,no-X11,no-agent,no-pty`. Ключ умеет ТОЛЬКО писать в один файл — компрометация публично-доступного роутера не даёт доступа к остальному vault.
- Latest-only (без истории) — конфиг роутера меняется редко и осознанно; приемлемо. Tarball ~15 КБ.
- Verified 2026-06-12: `openwrt-latest.tar.gz` 14554 байт на kreknin (== source).
## Что сделано правильно (не трогать)
- Все 3 живых VDS-пайплайна используют **logical dumps поверх raw datadir** — корректно
(running-container locks → raw `.mdf/.ldf` копия битая). `/opt/stacks/databases/*/data` намеренно
вне rsync.
- `--link-dest` hardlink-incremental + retention 7 + ntfy/email нотификация на каждый прогон.
- MinIO blobs на books-vds — raw-копия `minio/data` (immutable objects, допустимо).
## Связанные
- [[mssql-on-vds]] — INIT vs FORMAT gotcha, корень инцидента 12.06
- [[vds-backup-rsync-kreknin]] — таска VDS-пайплайна
- [[../entities/kreknin-synology]] — приёмник (SPOF #1)

View File

@@ -3,7 +3,7 @@ title: MSSQL on vds-kzntsv (Express 2022 Linux)
type: concept
tags: [vds, mssql, migration, ops]
related: [[../entities/vds-kzntsv]], [[recovery-architecture-snapshot]], [[traefik-tcp-passthrough-vs-starttls]], [[db-tls-self-signed-via-traefik-raw-tcp]]
updated: 2026-05-22
updated: 2026-06-12
---
# MSSQL on vds-kzntsv
@@ -93,13 +93,17 @@ source /opt/stacks/databases/mssql/.env # injects MSSQL_SA_PASSWORD
for DB in MoreThenCms StayerCalculator StayerPrice TireService stostayer; do
docker exec mssql /opt/mssql-tools18/bin/sqlcmd \
-S localhost -U sa -P "$MSSQL_SA_PASSWORD" -C -b \
-Q "BACKUP DATABASE [$DB] TO DISK='/var/opt/mssql/backups/${DB}.bak' WITH COPY_ONLY, INIT"
-Q "BACKUP DATABASE [$DB] TO DISK='/var/opt/mssql/backups/${DB}.bak' WITH COPY_ONLY, FORMAT"
mv /opt/stacks/databases/mssql/backups/${DB}.bak "$DUMP_DIR/mssql-${DB}.bak"
done
unset MSSQL_SA_PASSWORD
```
**Gotcha:** Express Edition не поддерживает `WITH COMPRESSION` — только `COPY_ONLY, INIT`. .bak файлы создаются в bind-mount `/var/opt/mssql/backups/` (uid 10001:10001); root mv их в `$DUMP_DIR` для rsync на kreknin. 5 DBs ~ 486 MB uncompressed. sqlcmd path: `/opt/mssql-tools18/bin/sqlcmd`.
**Gotcha #1 — Express не умеет COMPRESSION:** Express Edition не поддерживает `WITH COMPRESSION`. .bak файлы создаются в bind-mount `/var/opt/mssql/backups/` (uid 10001:10001); root mv их в `$DUMP_DIR` для rsync на kreknin. sqlcmd path: `/opt/mssql-tools18/bin/sqlcmd`.
**Gotcha #2 — `FORMAT`, не `INIT` (incident 2026-06-12):** первая версия блока (added 2026-06-11) использовала `WITH COPY_ONLY, INIT` и **молча падала первым же cron-запуском**. Причина: в `/var/opt/mssql/backups/` лежали миграционные `.bak` от 22.05, созданные `WITH COMPRESSION` на Developer-источнике. `INIT` переиспользует существующий media-set header (компрессованный) → Express не может в него писать → `Msg 1844 "BACKUP ... WITH COMPRESSION is not supported on Express"` (хотя в команде COMPRESSION нет!). `NO_COMPRESSION` тоже не спасает → `Msg 3098 "media formatted with an incompatible structure"`. **Fix: `WITH COPY_ONLY, FORMAT`** — всегда создаёт новый media set, иммунно к остаткам. Урок: при бэкапе на Express в каталог с возможными чужими/старыми `.bak` — только `FORMAT`, не `INIT`.
**Backup gap:** между 22.05 (миграция) и 12.06 у 5 боевых баз **не было ни одной offsite-копии** — raw datadir намеренно не rsync'ится (lock), а dump-ветка падала с первого дня. Закрыто 2026-06-12.
## Rollback recipe

View File

@@ -55,7 +55,15 @@ updated: 2026-05-19
Дополнительные input rules для wan (стандартные OpenWRT): Allow-DHCP-Renew, Allow-Ping, Allow-IGMP, Allow-DHCPv6, Allow-MLD, Allow-ICMPv6-*, Allow-IPSec-ESP.
## Что bgужно сделать (на потом)
## Config backup (с 2026-06-12)
Daily UCI/config backup → [[kreknin-synology]]:
- `/root/uci-backup.sh``sysupgrade -b /tmp/uci-backup.tar.gz``dbclient -i /root/.ssh/id_kreknin -y vitya@195.19.90.188` (pipe stdin).
- Cron `/etc/crontabs/root`: `30 3 * * * /root/uci-backup.sh`.
- Ключ роутера `/root/.ssh/id_kreknin` (dropbear ed25519) авторизован на kreknin с **forced-command** `cat > /volume1/NetBackup/openwrt/openwrt-latest.tar.gz` + `no-pty,no-*-forwarding` — ключ умеет только записать один файл (роутер публично-доступен → ограничение обязательно).
- Latest-only (без истории снапшотов); tarball ~15 КБ. См. [[../concepts/backup-inventory-2026-06]].
## Что нужно сделать (на потом)
- Отключить устаревшие redirects на 192.168.1.10 (DSM/FTP/SQL/Cloud Station) — снижает attack surface.
- Запланировать DDNS для случая смены публичного IP (REGRU domains всё ещё указывают на 94.19.247.14).

View File

@@ -22,6 +22,7 @@ Catalog of all wiki pages. One line per page, organized by type. Updated on ever
- [admin-infra-project](concepts/admin-infra-project.md) — design + migration plan для OpeItcLoc03/admin (canonical)
- [backup-inventory-2026-06](concepts/backup-inventory-2026-06.md) — карта всех машин × что реально бэкапится (с доказательством); дыры по приоритету (kreknin SPOF, nl-vds x-ui.db, bookva-*, openwrt) — триггер: MSSQL 3-нед gap
- [vds-kzntsv-ssh-access](concepts/vds-kzntsv-ssh-access.md) — SSH access audit на vds-kzntsv (infra VDS), retained keys + add/revoke processes (was misnamed `books-ssh-access` до 2026-05-25 — content всегда был про vds-kzntsv)
- [compose-bcrypt-escape-trap](concepts/compose-bcrypt-escape-trap.md) — Docker Compose ест `$` в bcrypt hashes
- [db-tls-self-signed-via-traefik-raw-tcp](concepts/db-tls-self-signed-via-traefik-raw-tcp.md) — DB TLS через traefik raw TCP с self-signed certs