Бэкап серверов fr1/ru1 на rpi: сервер шифрует age открытым ключом, малина только хранит

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
gilfanovr
2026-10-11 14:44:05 +03:00
co-authored by Claude Opus 5.5
parent e645377916
commit 7d0a891793
11 changed files with 334 additions and 8 deletions
+68
View File
@@ -0,0 +1,68 @@
# Бэкап серверов на rpi (fr1, ru1)
Внешняя площадка для VPS — домашняя малина. Схема «малина забирает, сервер шифрует»:
```mermaid
sequenceDiagram
participant R as rpi (таймер 02:15)
participant S as сервер (fr1 / ru1)
R->>S: ssh -i /etc/server-backup/id_ed25519 <профиль>
Note over S: forced command backup-export<br/>tar | zstd | age -r <ОТКРЫТЫЙ ключ>
S-->>R: stdout: шифротекст
S-->>R: stderr: "SHA256 <hex>" шифротекста
Note over R: сверяет sha256, кладёт<br/>/mnt/backup/servers/<host>/<профиль>/
```
- **Малина не может расшифровать то, что хранит.** Шифрует сервер открытым ключом age,
закрытый ключ только у владельца: Mac `~/.config/age/servers-backup.key` (+ резервная копия
вне малины). Взлом малины или промпт-инъекция агента Astra = утечка только шифротекста.
- **Сервер не получает доступа к малине** (в отличие от схемы «сервер пушит через обратный
туннель»): ключ малины на сервере умеет только запустить выгрузку (`restrict,command=…`).
- Целостность: sha256 шифротекста считает сервер, малина сверяет при приёме и раз в неделю
(`servers-backup verify`). Подмену/порчу внутри поймает и age при расшифровке (AEAD).
## Что входит
| Хост/профиль | Когда | Содержимое | Размер |
|---|---|---|---|
| ru1 daily | ежедневно | `/etc` целиком (OpenVPN, AWG, mtproxy, tproxy, iptables, юниты), `/opt` (MTProxy, xray), `/srv`, `/usr/local`, `~mik` (**easy-rsa — CA OpenVPN**, клиентские .ovpn, wireguard, gost+LE, smart-routing), `/root`, crontab | ~17 МБ |
| fr1 daily | ежедневно | `/etc` целиком (AWG-ключи, nginx, letsencrypt, tg-notify…), `/srv/gitea` + согласованный снимок `gitea.db` (`snap/gitea.db`, через sqlite `.backup`), `/usr/local`, `~rus` (monitoring с секретами, bin, MyVault, .ssh), `/root`, crontab | ~2.1 ГБ |
| fr1 weekly | вс | docker-тома `claude-channels_claude-home` (⚠️ ключи Astra к хостам), grafana, alertmanager, pushgateway; `~tgclaude` (imgbot-data, imgbot, боты) | ~10 ГБ |
Не входит сознательно: том Prometheus (метрики, 7.9 ГБ, потеря не критична), `~rus/backups`
и `~tgclaude/backups` (разовые архивы — скопировать один раз отдельно), спящий openclaw,
кэши `.claude`/`.cache`, сборки (`badvpn`, `telemt-build`).
Хранение: `daily` — 14, `weekly` — 8, `monthly` — 12 (хардлинк первого daily месяца).
## Файлы
- `fr1-backup-export` → fr1 `~rus/bin/backup-export` (читает root-файлы через `docker run -v /:/host:ro`:
sudo на fr1 по паролю, а rus в группе docker). `age` — статический бинарник `~rus/bin/age`.
- `ru1-backup-export` → ru1 `/usr/local/sbin/backup-export` (root, 750), forced command
`sudo -n …` у `mik` (у него sudo NOPASSWD). `age` — `/usr/local/bin/age`.
- `rpi/servers-backup`, `rpi/servers-backup.{service,timer}` → rpi `/usr/local/bin`, `/etc/systemd/system`.
Ключ `/etc/server-backup/id_ed25519` (root 600), host keys серверов закреплены в
`/etc/server-backup/known_hosts`. Итог прогона → метрики `backup_*{job="backup_servers"}`,
алерт `BackupStale` (>26 ч) и `BackupFailed`.
## Восстановление
Нужен закрытый ключ (на Mac). Архив можно расшифровывать потоком, не храня открытым на диске:
```bash
# список файлов
ssh rpi 'sudo sh -c "cat /mnt/backup/servers/ru1/daily/<файл>.age"' \
| age -d -i ~/.config/age/servers-backup.key | zstd -dq | tar -tvf -
# достать один файл/каталог (например CA OpenVPN)
ssh rpi 'sudo sh -c "cat /mnt/backup/servers/ru1/daily/<файл>.age"' \
| age -d -i ~/.config/age/servers-backup.key | zstd -dq | tar -xvf - home/mik/easy-rsa
# fr1: пути в архиве с префиксом host/ (tar шёл из контейнера с / на /host):
# репозитории — host/srv/gitea/data/git/repositories/…, БД gitea — snap/gitea.db
# (живой host/srv/gitea/data/gitea.db исключён — он был бы рваной копией)
```
⚠️ **Потеря закрытого ключа = все бэкапы бесполезны.** Минимум две копии в разных местах,
и пробное восстановление раз в квартал.