Бэкап серверов fr1/ru1 на rpi: сервер шифрует age открытым ключом, малина только хранит
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
e645377916
commit
7d0a891793
@@ -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 исключён — он был бы рваной копией)
|
||||
```
|
||||
|
||||
⚠️ **Потеря закрытого ключа = все бэкапы бесполезны.** Минимум две копии в разных местах,
|
||||
и пробное восстановление раз в квартал.
|
||||
Reference in New Issue
Block a user