Enhance backup documentation for Proxmox and VPS configurations. Add details for MTProto proxy setup on VPS, clarify backup processes for Immich photos, and update restic backup scripts to exclude photo directories. Include test recovery results and refine instructions for restoring various services and configurations.

This commit is contained in:
2026-02-26 23:06:02 +03:00
parent feaa31f702
commit 56cee83198
10 changed files with 727 additions and 20 deletions

View File

@@ -25,7 +25,8 @@
│ └── ct105-vectors/ ← векторы RAG (vectors.npz) из CT 105
├── restic/ ← (опционально)
└── vps/
── miran/ ← VPS Миран: БД бота, voice_users, копия S3 (telegram-helper-bot)
── miran/ ← VPS Миран: БД бота, voice_users, копия S3 (telegram-helper-bot)
└── mtproto-germany/ ← VPS Германия: конфиги MTProto + сайт katykhin.store (mtg, nginx, certs)
```
---
@@ -44,7 +45,9 @@
| **Векторы 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 дней |
| **Оригиналы фото Immich** | VM 200, `/mnt/data/library/` | `/mnt/backup/photos/library/` | **01:30** ежедневно (cron: `backup-immich-photos.sh`, rsync) | Все копии (без автоудаления) |
| **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 — перезапись |
| **Выгрузка в Yandex (restic)** | `/mnt/backup` целиком | Yandex Object Storage | **04:00** ежедневно (cron: `backup-restic-yandex.sh`) | 3 daily, 2 weekly, 2 monthly |
| **Конфиги 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 дней |
| **Выгрузка в Yandex (restic)** | `/mnt/backup` без photos | Yandex Object Storage | **04:00** ежедневно (cron: `backup-restic-yandex.sh`) | 3 daily, 2 weekly, 2 monthly |
| **Выгрузка в Yandex (restic, фото)** | `/mnt/backup/photos` | Yandex Object Storage (тот же репо) | **01:00** ежедневно (cron: `backup-restic-yandex-photos.sh`) | 3 daily, 2 weekly, 2 monthly |
**Окно бэкапов:** внутренние копии (синк внутри сервера) — **01:0003:30**; выгрузка в облако — **04:00**. **05:00** зарезервировано под плановую перезагрузку сервера. Задание vzdump — из веб-интерфейса Proxmox (Центр обработки данных → Резервная копия).
@@ -56,24 +59,45 @@
**Когда нужно:** потеря или поломка одной/нескольких гостевых систем.
1. В Proxmox: **Центр обработки данных → Резервная копия** (или узел → Резервная копия).
**Через веб-интерфейс:**
1. **Центр обработки данных → Резервная копия** (или узел → Резервная копия).
2. Выбрать хранилище **backup** (или то, куда пишет задание).
3. Найти нужный бэкап по VMID и дате.
4. **Восстановить** → указать новый VMID (если восстанавливаем как копию) или тот же (если заменяем сломанный), узел и storage для дисков.
5. Запустить ВМ/контейнер и проверить доступность.
**С CLI (на хосте):**
**С CLI (на хосте Proxmox):**
- LXC: `pct restore <vmid> /path/to/backup.vma.zst --storage local-lvm` (и т.п., см. `pct restore --help`).
- VM: `qm restore <vmid> /path/to/backup.vma.zst` (и т.п., см. `qm restore --help`).
Путь к файлам бэкапа: `/mnt/backup/proxmox/dump/dump/` (имя вида `vzdump-lxc-100-YYYY_MM_DD-HH_MM_SS.tar.zst` или `vzdump-qemu-200-...`).
Путь к файлу бэкапа на хосте: `/mnt/backup/proxmox/dump/` (имя файла вида `vzdump-lxc-100-...` или `vzdump-qemu-200-...`).
- **LXC** — восстановить в новый VMID (например 999) на storage `local-lvm`:
```bash
pct create 999 /mnt/backup/proxmox/dump/dump/vzdump-lxc-107-2026_02_26-02_03_14.tar.zst --restore 1 --storage local-lvm
```
Если восстанавливаем поверх существующего контейнера: сначала удалить его (`pct destroy 107`), затем в команде указать тот же VMID (107). Доп. опции: `pct create --help` (режим restore).
- **VM (KVM)** — порядок аргументов: сначала архив, потом VMID:
```bash
qm restore /mnt/backup/proxmox/dump/dump/vzdump-qemu-200-YYYY_MM_DD-HH_MM_SS.vma.zst 200 --storage local-lvm
```
У VM файлы бэкапа обычно с расширением `.vma.zst` или `.vma`. Подробнее: `qm restore --help`.
**После восстановления (пример для LXC):**
- Если восстановили в новый слот (например 999) и не нужен конфликт IP с оригиналом — сменить IP:
`pct set 999 --net0 name=eth0,bridge=vmbr0,gw=192.168.1.1,ip=192.168.1.199/24,type=veth`
- Запуск: `pct start 999` (LXC) или `qm start 200` (VM).
- Проверка: пинг, консоль (`pct exec 999 -- bash`), при необходимости сервисы и порты внутри контейнера.
Если vzdump есть только в Yandex (локального диска нет) — см. раздел **Восстановление из restic (Yandex)** → «Восстановление одного контейнера (vzdump)».
---
### 2. Восстановление конфигов хоста (/etc/pve и сеть)
**Когда нужно:** переустановка Proxmox или потеря конфигов узла (при этом диск с бэкапами доступен).
**Когда нужно:** переустановка Proxmox или потеря конфигов узла (при этом диск с бэкапами доступен).
Если конфигов нет локально, но есть в Yandex — см. раздел **Восстановление из restic** → «Восстановление конфигов хоста (/etc/pve)».
1. Скопировать нужный архив с хоста, например:
`etc-pve-YYYYMMDD-HHMM.tar.gz` и/или `etc-host-configs-YYYYMMDD-HHMM.tar.gz` из `/mnt/backup/proxmox/etc-pve/`.
@@ -117,6 +141,8 @@
**Когда нужно:** потеря данных на диске VM 200 (например `/mnt/data/library`).
**Требование для бэкапа:** на VM 200 должен быть установлен rsync (`sudo apt install rsync`), т.к. скрипт запускает rsync по SSH с хоста.
На VM 200 (или с хоста через rsync в обратную сторону): скопировать содержимое `/mnt/backup/photos/library/` обратно в каталог библиотеки Immich на VM 200 (в .env указан `UPLOAD_LOCATION`, обычно `/mnt/data/library`). Пример с хоста Proxmox:
```bash
@@ -125,6 +151,8 @@ rsync -av /mnt/backup/photos/library/ admin@192.168.1.200:/mnt/data/library/
После копирования на VM 200 выставить владельца/права под контейнер Immich (если нужно) и перезапустить сервисы.
Если фото есть только в Yandex — см. раздел **Восстановление из restic** → «Восстановление фото (библиотека Immich)».
---
### 5. Восстановление БД Paperless (CT 104), Gitea (CT 103)
@@ -133,12 +161,16 @@ rsync -av /mnt/backup/photos/library/ admin@192.168.1.200:/mnt/data/library/
**Gitea:** дамп из `/mnt/backup/databases/ct103-gitea/gitea-db-*.sql.gz`. На CT 103: остановить Gitea, восстановить в контейнер `gitea-db-1` (psql -U gitea -d gitea), запустить стек.
Если дампов нет локально — см. раздел **Восстановление из restic** → «Восстановление дампов БД».
---
### 6. Восстановление данных Vaultwarden (CT 103)
Архив из `/mnt/backup/other/vaultwarden/vaultwarden-data-*.tar.gz`. На CT 103: остановить Vaultwarden, распаковать в `/opt/docker/vaultwarden/` (получится каталог `data/`), выставить владельца/права под контейнер, запустить Vaultwarden.
Если архива нет локально (есть только в Yandex) — см. раздел **Восстановление из restic** → «Восстановление данных Vaultwarden (пароли)».
---
### 7. Восстановление бэкапа VPS Миран (telegram-helper-bot)
@@ -161,13 +193,34 @@ rsync -av /mnt/backup/photos/library/ admin@192.168.1.200:/mnt/data/library/
---
### 8. Восстановление векторов RAG (CT 105)
### 8. Восстановление конфигов MTProto (VPS Германия)
**Когда нужно:** потеря конфигов на VPS 185.103.253.99 или перенос MTProto и сайта-заглушки на другой хост.
В архиве `mtproto-config-YYYYMMDD-HHMM.tar.gz` из `/mnt/backup/vps/mtproto-germany/` лежат:
- **mtg:** `etc/systemd/system/mtg.service` (в т.ч. секрет и cloak-port).
- **nginx:** `etc/nginx/sites-available/`, `etc/nginx/sites-enabled/` (конфиг для katykhin.store на порту 993).
- **Let's Encrypt:** `etc/letsencrypt/live/katykhin.store/`, `archive/katykhin.store/`, `renewal/katykhin.store.conf`.
- **Сайт:** `var/www/katykhin.store/`.
**Восстановление на VPS (от root):** скопировать архив на сервер и распаковать в корень:
```bash
scp /mnt/backup/vps/mtproto-germany/mtproto-config-YYYYMMDD-HHMM.tar.gz root@185.103.253.99:/tmp/
ssh root@185.103.253.99 "tar -xzf /tmp/mtproto-config-YYYYMMDD-HHMM.tar.gz -C /"
```
После распаковки: `systemctl daemon-reload && systemctl restart mtg nginx`. На новом хосте дополнительно установить mtg, nginx, certbot и настроить ufw (см. [план MTProto + сайт](../vps/vpn-vps-mtproto-site-plan.md)).
**Требования для бэкапа:** на хосте Proxmox — SSH по ключу root → root@185.103.253.99 (порт 22). Ключ хоста должен быть добавлен в `authorized_keys` на VPS.
---
### 9. Восстановление векторов RAG (CT 105)
Архив из `/mnt/backup/other/ct105-vectors/vectors-*.tar.gz`. Распаковать на хосте и скопировать в контейнер: `tar -xzf vectors-*.tar.gz` → затем `pct push 105 ./vectors /home/rag-service/data/` или распаковать внутри CT 105 в `/home/rag-service/data/` (получится каталог `vectors/` с `vectors.npz`).
---
### 9. Восстановление VM 200 (Immich) с нуля
### 10. Восстановление VM 200 (Immich) с нуля
VM 200 **не входит** в задание vzdump (образ ~380 ГБ, не помещается в политику 7 копий). В бэкапе есть: **конфиг ВМ** (в архивах `/etc/pve`), **БД** (pg_dump), **фото** (rsync в `photos/library`). Восстановление — создание новой ВМ с теми же параметрами и перенос данных.
@@ -201,7 +254,150 @@ VM 200 **не входит** в задание vzdump (образ ~380 ГБ, н
## Restic и Yandex
Скрипт **`backup-restic-yandex.sh`** выгружает весь каталог `/mnt/backup` в Yandex Object Storage (S3). Retention: **3 daily, 2 weekly, 2 monthly** (`restic forget --keep-daily 3 --keep-weekly 2 --keep-monthly 2`). Пароли и дампы — чувствительные данные; не выкладывать в открытый доступ.
Два задания в одном репозитории: **`backup-restic-yandex.sh`** выгружает `/mnt/backup` **без** каталога `photos`; **`backup-restic-yandex-photos.sh`** выгружает только `/mnt/backup/photos` (отдельный снимок, больше всего данных). Retention у обоих: **3 daily, 2 weekly, 2 monthly**. Пароли и дампы — чувствительные данные; не выкладывать в открытый доступ.
---
## Восстановление из restic (Yandex)
**Когда нужно:** локальных бэкапов нет (потеря диска, другой хост), данные есть только в Yandex Object Storage.
### Подготовка на хосте
- Те же креды, что для бэкапа: **`/root/.restic-yandex.env`** (RESTIC_REPOSITORY, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY), **`/root/.restic-password`**.
- Установлены **restic** и **FUSE** (для `restic mount`): `apt install restic fuse`.
- Восстановление делаем **на раздел с достаточным местом** (например `/mnt/backup/restore-...`), не в `/tmp`.
### Два типа снимков в одном репо
В репозитории два вида снимков (различаются по полю **Paths** в `restic snapshots`):
| Paths в снимке | Откуда | Что внутри |
|------------------|--------|-----------|
| `/mnt/backup` | backup-restic-yandex.sh | Всё кроме photos: proxmox/dump, proxmox/etc-pve, databases/, other/, vps/ |
| `/mnt/backup/photos` | backup-restic-yandex-photos.sh | Только каталог photos (библиотека Immich) |
При восстановлении **конфигов, паролей, дампов БД, vzdump** — брать снимок с path **/mnt/backup**. При восстановлении **фото** — брать снимок с path **/mnt/backup/photos**.
```bash
# Список снимков (указать нужный по Paths и дате)
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
```
### Сводка: что откуда восстанавливать
| Что восстановить | Путь в снимке (основной репо) | Снимок | Способ |
|----------------------|--------------------------------------|-------------|--------|
| Один LXC/VM (vzdump) | /mnt/backup/proxmox/dump/dump/... | /mnt/backup | Скрипт mount + cp (см. ниже) |
| Конфиги /etc/pve | /mnt/backup/proxmox/etc-pve/ | /mnt/backup | restic restore --path ... |
| Vaultwarden (пароли) | /mnt/backup/other/vaultwarden/ | /mnt/backup | restic restore --path ... |
| Дампы БД | /mnt/backup/databases/... | /mnt/backup | restic restore --path ... |
| VPS, other | /mnt/backup/vps/, other/ | /mnt/backup | restic restore --path ... |
| Фото Immich | /mnt/backup/photos/ | **/mnt/backup/photos** | restic restore из снимка photos |
---
### Восстановление одного контейнера (vzdump) из restic
Чтобы не выкачивать весь репо, используется **mount** и копирование одного файла.
1. Узнать имя нужного архива в снимке (например CT 107):
```bash
restic ls latest --path /mnt/backup/proxmox/dump/dump | grep vzdump-lxc-107
```
Использовать снимок с path `/mnt/backup` (не photos).
2. Запустить скрипт (он сам монтирует, копирует файл, размонтирует):
```bash
/root/scripts/restore-one-vzdump-from-restic.sh latest /mnt/backup/proxmox/dump/dump/vzdump-lxc-107-YYYY_MM_DD-HH_MM_SS.tar.zst /mnt/backup
```
Файл появится в `/mnt/backup/vzdump-lxc-107-....tar.zst`.
3. Восстановить контейнер из файла (как в разделе 1 выше):
```bash
pct create 999 /mnt/backup/vzdump-lxc-107-....tar.zst --restore 1 --storage local-lvm
pct set 999 --net0 name=eth0,bridge=vmbr0,gw=192.168.1.1,ip=192.168.1.199/24,type=veth
pct start 999
```
Если скрипта нет — вручную: `restic mount /mnt/backup/restic-mount &`, подождать, скопировать из `.../restic-mount/ids/<SNAPSHOT_ID>/mnt/backup/proxmox/dump/dump/vzdump-lxc-107-....tar.zst` в нужное место, затем `fusermount -u /mnt/backup/restic-mount`.
---
### Восстановление конфигов хоста (/etc/pve) из restic
1. Выбрать снимок с path **/mnt/backup** (по дате): `restic snapshots`.
2. Восстановить только каталог etc-pve:
```bash
restic restore SNAPSHOT_ID --target /mnt/backup/restore-etc-pve --path /mnt/backup/proxmox/etc-pve
```
Файлы появятся в `/mnt/backup/restore-etc-pve/mnt/backup/proxmox/etc-pve/` (архивы `etc-pve-*.tar.gz`, `etc-host-configs-*.tar.gz`).
3. Распаковать нужный архив в корень (как в разделе 2 выше):
```bash
tar -xzf /mnt/backup/restore-etc-pve/mnt/backup/proxmox/etc-pve/etc-pve-YYYYMMDD-HHMM.tar.gz -C /
tar -xzf /mnt/backup/restore-etc-pve/mnt/backup/proxmox/etc-pve/etc-host-configs-YYYYMMDD-HHMM.tar.gz -C /
```
4. При необходимости поправить сеть и перезапустить сервисы.
---
### Восстановление данных Vaultwarden (пароли) из restic
1. Снимок с path **/mnt/backup**.
2. Восстановить каталог vaultwarden:
```bash
restic restore SNAPSHOT_ID --target /mnt/backup/restore-vw --path /mnt/backup/other/vaultwarden
```
Результат: `/mnt/backup/restore-vw/mnt/backup/other/vaultwarden/vaultwarden-data-*.tar.gz`.
3. Скопировать архив на хост, откуда можно отправить на CT 103, либо распаковать во временный каталог и скопировать каталог `data/` на CT 103 в `/opt/docker/vaultwarden/` (остановив Vaultwarden). Детали — раздел 6 выше.
---
### Восстановление фото (библиотека Immich) из restic
Фото лежат в **отдельном снимке** (path `/mnt/backup/photos`). Сначала выбрать этот снимок:
```bash
restic snapshots | grep photos
```
Затем восстановить в каталог с достаточным местом:
```bash
restic restore SNAPSHOT_ID --target /mnt/backup/restore-photos --path /mnt/backup/photos
```
Фото окажутся в `/mnt/backup/restore-photos/mnt/backup/photos/library/`. Дальше — скопировать на VM 200 (раздел 4 выше):
`rsync -av /mnt/backup/restore-photos/mnt/backup/photos/library/ admin@192.168.1.200:/mnt/data/library/`
---
### Восстановление дампов БД из restic
Снимок с path **/mnt/backup**. Восстановить нужный подкаталог, например только ct104-paperless:
```bash
restic restore SNAPSHOT_ID --target /mnt/backup/restore-db --path /mnt/backup/databases/ct104-paperless
```
Файлы появятся в `/mnt/backup/restore-db/mnt/backup/databases/ct104-paperless/`. Дальше — восстановление БД по разделам 3 или 5 выше (скопировать дамп на контейнер и загрузить в PostgreSQL).
Аналогично для других БД: `--path /mnt/backup/databases/ct101-nextcloud`, `ct103-gitea`, `vm200-immich`.
---
### Восстановление прочего (VPS, векторы RAG) из restic
- **VPS Миран / MTProto:**
`restic restore SNAPSHOT_ID --target /mnt/backup/restore-vps --path /mnt/backup/vps`
Дальше — копировать нужные файлы на VPS по разделам 78.
- **Векторы RAG (ct105-vectors):**
`restic restore SNAPSHOT_ID --target /mnt/backup/restore-other --path /mnt/backup/other/ct105-vectors`
Дальше — по разделу 9.
---
@@ -210,6 +406,7 @@ VM 200 **не входит** в задание vzdump (образ ~380 ГБ, н
| Скрипт | Назначение | Cron |
|--------|------------|------|
| `/root/scripts/backup-vps-miran.sh` | Бэкап VPS Миран: БД бота, voice_users, S3 (Miran) | 0 1 * * * |
| `/root/scripts/backup-vps-mtproto.sh` | Копирование конфигов MTProto + сайт с VPS Германия (185.103.253.99) | 45 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-etc-pve.sh` | Бэкап /etc/pve и конфигов хоста | 15 2 * * * |
@@ -218,7 +415,8 @@ VM 200 **не входит** в задание vzdump (образ ~380 ГБ, н
| `/root/scripts/backup-ct103-gitea-pgdump.sh` | Логический дамп БД Gitea из CT 103 | 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 в Yandex S3 (restic), retention 3/2/2 | 0 4 * * * |
| `/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 | 0 1 * * * |
Задание vzdump (LXC/VM) настраивается в Proxmox UI (расписание 02:00). **05:00** оставлено свободным для плановой перезагрузки сервера.

View File

@@ -220,7 +220,7 @@ tar -czf "$BACKUP_ROOT/etc-host-configs-$DATE.tar.gz" -C / etc/network/interface
- [x] Vaultwarden развёрнут (CT 103).
- [ ] Секреты перенесены в Vaultwarden. *(на усмотрение: root PVE, пароли БД, API и т.д.)*
- [ ] Бэкап данных Vaultwarden включён в restic (Yandex S3). *Локально данные уже копируются в `/mnt/backup/other/vaultwarden/` (backup-vaultwarden-data.sh); при настройке restic — включить этот каталог в источники.*
- [ ] Выполнено тестовое восстановление одного контейнера (другой VMID), проверена работоспособность.
- [x] Выполнено тестовое восстановление одного контейнера (другой VMID), проверена работоспособность. *(26.02.2026: восстановлен CT 107 в слот 999 из `/mnt/backup/proxmox/dump/dump/vzdump-lxc-107-*.tar.zst`, проверены консоль, пинг, Docker, Invidious на 3000; тестовый CT удалён.)*
- [x] В документации зафиксирована процедура полного восстановления Proxmox «с нуля». *[backup-howto.md](backup-howto.md): восстановление из vzdump, конфигов, БД, VM 200 с нуля, Vaultwarden, VPS и др.*
---
@@ -256,5 +256,5 @@ tar -czf "$BACKUP_ROOT/etc-host-configs-$DATE.tar.gz" -C / etc/network/interface
- **Yandex + Restic:** подготовить Static Key (Access Key + Secret), записать endpoint и имя бакета; настроить restic на хосте (cron): выгрузка `/mnt/backup` в Yandex S3, retention 3 daily / 2 weekly / 2 monthly. Скрипт: `scripts/backup-restic-yandex.sh`.
- **Проверка:** один раз запустить Backup now в Proxmox UI и убедиться, что файлы появляются в storage.
- **Тестовое восстановление:** восстановить один контейнер (например 105 или 107) под другим VMID, проверить доступ и сервисы, затем удалить тестовый.
- **Тестовое восстановление:** ~~восстановить один контейнер (например 105 или 107) под другим VMID~~ выполнено 26.02.2026 (CT 107 → 999).
- **Секреты:** при необходимости перенести пароли/ключи (root PVE, БД, API) в Vaultwarden и обновлять при смене.

View File

@@ -92,4 +92,12 @@ ss -ulnp | grep 33118
Параметры обфускации на обоих серверах (Германия и США) **одинаковые** — конфиг можно полностью перенести на новый сервер при переезде. На роутере создаётся второе VPN-подключение; переключение между Germany и USA — выбор нужного профиля.
**Подробно:** [Перенос конфигурации AmneziaWG между серверами](vpn-migrate-config.md).
**Подробно:** [Перенос конфигурации AmneziaWG между серверами](vpn-migrate-config.md).
---
## MTProto и сайт-заглушка (план)
На этом же VPS можно развернуть MTProto proxy с маскировкой под обычный HTTPS-сайт (один порт 443): при заходе на домен — статический сайт, при подключении Telegram с секретом — прокси. Домен: katykhin.store. VPN (AmneziaWG) при этом остаётся на порту 33118/UDP без изменений.
**План развёртывания:** [MTProto + сайт на VPS Германия (план)](vpn-vps-mtproto-site-plan.md).

View File

@@ -0,0 +1,112 @@
# Отчёт проверки MTProto + маскировка (katykhin.store)
Проверки выполнены **с внешней консоли** (без SSH на сервер) 2026-02-26. Цель: убедиться, что на 443 выглядит как обычный HTTPS, а MTProto срабатывает только при наличии секрета.
---
## 1) HTTPS сайта
**Команда:** `curl -I https://katykhin.store/`
**Результат:**
- **HTTP/1.1 200 OK**
- Server: nginx/1.24.0 (Ubuntu)
- Content-Type: text/html
**Вывод:** Сайт отдаётся по **HTTPS**, не по HTTP. Маскировка по TLS выполнена.
---
## 2) Порт 443 и TLS
### Порт 443 открыт
**Команда:** `nc -vz katykhin.store 443`
**Результат:** `Connection to katykhin.store port 443 [tcp/https] succeeded!`
### TLS-рукопожатие и сертификат
**Команда:** `openssl s_client -connect katykhin.store:443 -servername katykhin.store`
**Результат:**
- **Verification: OK**
- **Сертификат:** Let's Encrypt (E8), CN=katykhin.store
- **Срок:** NotBefore 26 Feb 2026, NotAfter 27 May 2026
- **Протокол:** TLSv1.3, Cipher TLS_AES_256_GCM_SHA384
- **SAN:** host "katykhin.store" в сертификате
**Вывод:** Внешний вид — обычный HTTPS с валидным доверенным сертификатом, без самоподписи.
---
## 3) Однострочная проверка
**Команда:** `curl -Iv https://katykhin.store/`
**Результат:**
- TLS handshake успешен
- Сертификат проверен (SSL certificate verify ok)
- **HTTP/1.1 200 OK**
- subject: CN=katykhin.store, issuer: Let's Encrypt
**Вывод:** Важнейшая часть маскировки выполнена: **katykhin.store:443 = обычный HTTPS**.
---
## 4) Поведение при «не-TLS» подключении к 443
### Сырой TCP без данных
**Команда:** подключение к 443 без отправки данных (nc без ввода).
**Результат:** Ответа нет (сервер ждёт данные). mtg перенаправил соединение на nginx:993; nginx ждёт TLS ClientHello — логично.
### Plain HTTP на порт 443
**Команда:** отправка `GET / HTTP/1.0` на 443 (без TLS).
**Результат:** Ответ от nginx:
- **HTTP/1.1 400 Bad Request**
- «The plain HTTP request was sent to HTTPS port»
**Вывод:** На 443 приходит трафик без MTProto-секрета → mtg отдаёт его nginx (cloak). Nginx на 993 принимает только TLS, поэтому на plain HTTP отвечает 400. Для DPI/активной проверки, которые делают **нормальный TLS** (как браузер или `openssl s_client`), будет обычный HTTPS и страница сайта. Plain HTTP на 443 даёт только 400 от nginx, а не MTProto.
---
## 5) Итоговая таблица поведения
| Подключение | Ожидание по плану | Факт |
|--------------------------|--------------------------|-------------------------------|
| Браузер / curl HTTPS | Сайт-заглушка, 200 | ✅ HTTP/1.1 200, nginx, TLS |
| TLS (openssl s_client) | Валидный HTTPS | ✅ Let's Encrypt, TLS 1.3 |
| Telegram с MTProto | Работа прокси | ✅ Подтверждено пользователем |
| Plain HTTP на 443 | Не MTProto | ✅ 400 от nginx (HTTPS port) |
| Соединение без данных | Нет «сырого» HTTP-ответа| ✅ Нет ответа (ожидание TLS) |
---
## 6) Что не проверялось из консоли (нужен сервер или устройство)
- **Логи nginx** (access.log / error.log) — нужен SSH.
- **Логи mtg** (принятые соединения с секретом) — нужен SSH.
- **Проверка из РФ** (iPhone/устройство в целевой сети, без общего VPN, с MTProto) — нужно выполнить у тебя.
- **Симуляция активного probing** уже по сути сделана: `openssl s_client` к 443 ведёт себя как обычный HTTPS-клиент и получает нормальный TLS и сайт.
---
## 7) Частые ошибки — статус
| Риск | Статус |
|-----------------------------|--------|
| Сайт только по HTTP | ❌ Нет: HTTPS отвечает 200 |
| Самоподписанный сертификат | ❌ Нет: Let's Encrypt |
| MTProto на нестандартном порту | ❌ Нет: используется 443 |
| Не-MTProto видит прокси | ❌ Нет: без секрета — nginx/сайт или 400 |
---
## Краткий вывод
**Сеть видит katykhin.store:443 как обычный HTTPS** (валидный сертификат, TLS 1.3, ответ 200 на GET /).
Только клиент с правильным MTProto-секретом попадает на прокси; остальное идёт в nginx (сайт или 400 на plain HTTP). Маскировка настроена корректно.

View File

@@ -0,0 +1,172 @@
# План: MTProto + сайт-заглушка на VPS Германия (один порт 443)
Развёртывание MTProto proxy с TLS-camouflage и статическим сайтом на одном порту 443 на VPS в Германии (185.103.253.99). VPN (AmneziaWG) остаётся на текущем кастомном порту без изменений.
---
## Цель
- **Один порт 443:** при заходе по HTTPS на домен — отдаётся обычный сайт; при подключении Telegram с секретом — трафик идёт в MTProto. Для DPI/проверок сервер выглядит как обычный веб-сайт.
- **Домен:** katykhin.store (A-запись на 185.103.253.99).
- **VPN:** без изменений, порт 33118/UDP (AmneziaWG), текущие конфиги клиентов и роутера не трогаем.
---
## Архитектура
```
Клиент (браузер) → 443 → mtg → (нет секрета) → nginx:993 → статический сайт
Клиент (Telegram + proxy) → 443 → mtg → (есть секрет) → MTProto → Telegram DC
```
- **mtg** слушает 0.0.0.0:443. По первым байтам определяет: если передан корректный dd-secret — обрабатывает как MTProto; иначе перенаправляет соединение на **cloak-port** (nginx на 993).
- **Nginx** слушает 127.0.0.1:993 (или 0.0.0.0:993) с TLS, отдаёт статический сайт по Let's Encrypt для katykhin.store.
- **AmneziaWG** — как сейчас, порт 33118/UDP, конфигурация не меняется.
Выбор **mtg** (а не nginx stream + официальный MTProxy): один компонент на 443, встроенная логика «секрет → MTProto, иначе → cloak», меньше точек отказа и проще отладка. Избегаем связки обфускация + MTProxy на одном сервере.
---
## Как это работает
### Потоки трафика
- **Обычный HTTPS (браузер / curl):**
- Клиент устанавливает TLSсоединение на `katykhin.store:443`.
- `mtg` принимает соединение, не видит корректного MTProtoсекрета и прокидывает его на nginx по `localhost:993` (cloakпорт).
- Nginx завершает TLS, отдаёт статический сайт (`/var/www/katykhin.store`), сертификат — Let's Encrypt для `katykhin.store`.
- **Telegram с MTProtoproxy (с правильным секретом):**
- Клиент шлёт fakeTLS трафик с секретом (`ee…`).
- `mtg` распознаёт секрет и работает как MTProtoпрокси: устанавливает соединение с Telegram DC и пересылает трафик через себя.
- Для внешнего наблюдателя подключение всё равно идёт на `katykhin.store:443` по TLS.
- **Неверный/отсутствующий секрет, “чужой” клиент или plain HTTP на 443:**
- Соединение уходит в nginx на 993.
- TLSклиенты (обычный `openssl s_client`, браузер) получают нормальный сертификат и ответ 200.
- Plain HTTP на 443 получает от nginx стандартный `400 The plain HTTP request was sent to HTTPS port`.
### Роль отдельных компонентов
- **mtg (fake TLS / obfuscated secret):**
- Слушает `0.0.0.0:443`.
- Секрет сгенерирован как `mtg generate-secret -c katykhin.store tls` (префикс `ee…`, fake TLS).
- Если секрет корректный — MTProto; если нет — прокидка на nginx (cloakпорт).
- **nginx:**
- Слушает `993/tcp` с TLS, работает только как backend для mtg.
- Для любого валидного TLSклиента выглядит как обычный сайт с Lets Encryptсертификатом.
- **AmneziaWG:**
- Продолжает слушать `33118/udp` на VPS, используется для VPNтуннеля и никак не завязан на MTProto/HTTPS.
---
## Маскировка и безопасность
- **TLSмаскировка:**
- Сертификат: Lets Encrypt, CN=`katykhin.store`, TLS 1.3.
- При проверке через `curl -Iv` и `openssl s_client` сервер ведёт себя как одиночный нормальный HTTPSхост.
- Никаких самоподписанных сертификатов или нестандартных портов.
- **Fake TLS / obfuscated secret:**
- Используется `mtg` с режимом fake TLS, секрет вида `ee…`.
- Без секрета MTProto не “торчит наружу”: active probing с обычным TLS видит только сайт или стандартную ошибку HTTPSпорта.
- **Firewall (ufw):**
- Входящие разрешены только:
- `22/tcp` — SSH.
- `80/tcp` — HTTP (для выдачи/продления сертификатов).
- `443/tcp` — MTProto+HTTPS (mtg).
- `33118/udp` — AmneziaWG.
- `993/tcp` не открыт наружу — им пользуется только mtg локально.
- **Rate limiting и fail2ban:**
- В nginx включён лимит запросов и соединений на сервере `katykhin.store` (cloakпорт 993):
- `limit_req_zone` ~ 10 r/s на IP, `burst=20`.
- `limit_conn` ~ 10 одновременных соединений на IP.
- `fail2ban`:
- jail `sshd` — защита от перебора паролей по SSH.
- jail `nginx-limit-req` настроен, но в текущей схеме почти не срабатывает (см. примечание ниже).
**Важно:** запросы к nginx на 993 приходят от mtg (`127.0.0.1`), поэтому в логах nginx реальные клиентские IP не видны, и `nginx-limit-req` практически не пригоден для блокировки внешних адресов. Основная практическая защита здесь — rate limiting самого nginx и jail `sshd` в `fail2ban`.
---
## Использование
### MTProtoproxy
- **Параметры прокси:**
- Сервер: `katykhin.store` (или напрямую IP `185.103.253.99`).
- Порт: `443`.
- Секрет: `eecf027cbd3a05d5ff99a18796a51958516b6174796b68696e2e73746f7265`.
- **Готовые ссылки:**
- Через t.me:
`https://t.me/proxy?server=katykhin.store&port=443&secret=eecf027cbd3a05d5ff99a18796a51958516b6174796b68696e2e73746f7265`
- Через tg:// по IP:
`tg://proxy?server=185.103.253.99&port=443&secret=eecf027cbd3a05d5ff99a18796a51958516b6174796b68696e2e73746f7265`
- **Ожидаемое поведение:**
- При включении прокси Telegram устанавливает соединение на `katykhin.store:443`.
- Секрет распознаётся mtg, трафик уходит в Telegram DC.
- Снаружи это выглядит как обычный HTTPSтрафик к сайту.
### VPN (AmneziaWG)
- Конфигурация VPN не менялась:
- Сервер: `185.103.253.99`.
- Порт: `33118/udp`.
- Все существующие клиенты/роутер продолжают использовать старые конфиги.
MTProto и VPN живут независимо: MTProto занимает 443/tcp, AmneziaWG — 33118/udp.
---
## Мониторинг и отладка (на уже развёрнутой схеме)
- **mtg:**
- Текущий статус сервиса: `systemctl status mtg`.
- Живые логи: `journalctl -u mtg -f`.
- **nginx:**
- Доступы/ошибки по сайту:
`tail -f /var/log/nginx/access.log`
`tail -f /var/log/nginx/error.log`
- **fail2ban:**
- Общий статус: `fail2ban-client status`.
- jail `sshd`: `fail2ban-client status sshd`.
- Разбан IP: `fail2ban-client set sshd unbanip <IP>`.
---
---
## Итоговая схема портов
| Порт | Служба | Доступ |
|-------------|-------------|---------------|
| 22/tcp | SSH | интернет |
| 80/tcp | HTTP | для certbot |
| 443/tcp | mtg | интернет |
| 993/tcp | nginx (SSL) | только localhost (для mtg cloak) |
| 33118/udp | AmneziaWG | интернет |
---
## Ссылки и ссылка для Telegram
- Документация mtg: https://github.com/9seconds/mtg
- Руководство с cloak: https://v2how.github.io/post/2021-02-18-camouflage-telegram-mtproto-proxy-ubuntu-20-04/
- Ссылка для подключения к прокси (развёрнуто 2026-02-26):
`https://t.me/proxy?server=katykhin.store&port=443&secret=eecf027cbd3a05d5ff99a18796a51958516b6174796b68696e2e73746f7265`
Альтернативно по IP: `tg://proxy?server=185.103.253.99&port=443&secret=eecf027cbd3a05d5ff99a18796a51958516b6174796b68696e2e73746f7265`
---
## Связь с другими документами
- Текущий VPN и доступ к VPS: [VPN-сервер (VPS, AmneziaWG)](vpn-vps-amneziawg.md).
- После выполнения плана имеет смысл добавить в `vpn-vps-amneziawg.md` краткий раздел «MTProto и сайт» со ссылкой на этот документ и указанием домена/порта.

View File

@@ -1,6 +1,6 @@
#!/bin/bash
# Копирование библиотеки фото Immich (оригиналы) с VM 200 на диск бэкапов хоста.
# Запускать на хосте Proxmox под root. Требуется SSH без пароля: root → admin@192.168.1.200.
# Запускать на хосте Proxmox под root. Требуется: SSH без пароля root → admin@192.168.1.200; на VM 200 установлен rsync (apt install rsync).
# Результат: /mnt/backup/photos/library/ (зеркало /mnt/data/library с VM 200).
# Без --delete: удалённые в Immich фото в бэкапе остаются (страховка).
set -e

View File

@@ -0,0 +1,64 @@
#!/bin/bash
# Выгрузка только /mnt/backup/photos в Yandex Object Storage (S3) через restic.
# Тот же репозиторий, что и backup-restic-yandex.sh; фото вынесены в отдельный снимок (больше всего данных).
# Запускать на хосте Proxmox под root. Требуется тот же /root/.restic-yandex.env и /root/.restic-password.
# Cron: 0 1 * * * (01:00).
set -e
ENV_FILE="/root/.restic-yandex.env"
BACKUP_PATH="/mnt/backup/photos"
if [ "$(id -u)" -ne 0 ]; then
echo "Запускайте под root."
exit 1
fi
if [ ! -f "$ENV_FILE" ]; then
echo "Нет файла $ENV_FILE. Скопируйте restic-yandex-env.example и задайте ключи."
exit 1
fi
# shellcheck source=/dev/null
source "$ENV_FILE"
for var in RESTIC_REPOSITORY AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY; do
if [ -z "${!var}" ]; then
echo "В $ENV_FILE не задано: $var"
exit 1
fi
done
if [ -z "$RESTIC_PASSWORD_FILE" ]; then
RESTIC_PASSWORD_FILE="/root/.restic-password"
fi
if [ ! -f "$RESTIC_PASSWORD_FILE" ]; then
echo "Файл с паролем репозитория не найден: $RESTIC_PASSWORD_FILE"
exit 1
fi
export AWS_ACCESS_KEY_ID
export AWS_SECRET_ACCESS_KEY
export RESTIC_PASSWORD_FILE
export RESTIC_REPOSITORY
export AWS_DEFAULT_REGION="${AWS_DEFAULT_REGION:-ru-central1}"
if ! command -v restic >/dev/null 2>&1; then
echo "restic не установлен. Установите: apt install restic."
exit 1
fi
if [ ! -d "$BACKUP_PATH" ]; then
echo "Каталог $BACKUP_PATH не найден. Пропуск."
exit 0
fi
echo "Restic backup (photos): $BACKUP_PATH -> $RESTIC_REPOSITORY"
restic backup "$BACKUP_PATH" --quiet
echo "Restic forget (retention 3 daily, 2 weekly, 2 monthly)..."
restic forget --keep-daily 3 --keep-weekly 2 --keep-monthly 2 --prune --quiet
echo "Restic prune..."
restic prune --quiet
echo "Restic photos backup done."

View File

@@ -1,5 +1,6 @@
#!/bin/bash
# Выгрузка /mnt/backup в Yandex Object Storage (S3) через restic.
# Выгрузка /mnt/backup в Yandex Object Storage (S3) через restic (без каталога photos).
# Фото бэкапятся отдельно: backup-restic-yandex-photos.sh.
# Запускать на хосте Proxmox под root.
# Перед первым запуском: установить restic, создать /root/.restic-yandex.env и /root/.restic-password, выполнить restic init.
# Cron: 0 4 * * * (04:00, после окна 01:0003:30; 05:00 зарезервировано под перезагрузку).
@@ -7,8 +8,8 @@ set -e
ENV_FILE="/root/.restic-yandex.env"
BACKUP_PATH="/mnt/backup"
# Исключаем служебные каталоги
EXCLUDE_OPTS=(--exclude="$BACKUP_PATH/lost+found")
# Исключаем служебные каталоги и photos (фото — отдельный бэкап)
EXCLUDE_OPTS=(--exclude="$BACKUP_PATH/lost+found" --exclude="$BACKUP_PATH/photos")
if [ "$(id -u)" -ne 0 ]; then
echo "Запускайте под root."
@@ -49,7 +50,7 @@ if ! command -v restic >/dev/null 2>&1; then
exit 1
fi
echo "Restic backup: $BACKUP_PATH -> $RESTIC_REPOSITORY"
echo "Restic backup: $BACKUP_PATH (excl. photos) -> $RESTIC_REPOSITORY"
restic backup "$BACKUP_PATH" "${EXCLUDE_OPTS[@]}" --quiet
echo "Restic forget (retention 3 daily, 2 weekly, 2 monthly)..."

View File

@@ -0,0 +1,42 @@
#!/bin/bash
# Бэкап конфигов MTProto + сайт-заглушка с VPS Германия (185.103.253.99).
# Запускать на хосте Proxmox под root.
# Требуется: SSH без пароля с хоста к root@185.103.253.99 (ключ в ~/.ssh).
# Результат: /mnt/backup/vps/mtproto-germany/mtproto-config-YYYYMMDD-HHMM.tar.gz
set -e
VPS_HOST="185.103.253.99"
VPS_USER="root"
SSH_OPTS=(-o ConnectTimeout=15 -o BatchMode=yes)
BACKUP_ROOT="/mnt/backup/vps/mtproto-germany"
RETENTION_DAYS=14
if [ "$(id -u)" -ne 0 ]; then
echo "Запускайте под root."
exit 1
fi
mkdir -p "$BACKUP_ROOT"
DATE=$(date +%Y%m%d-%H%M)
ARCHIVE="$BACKUP_ROOT/mtproto-config-$DATE.tar.gz"
# Проверка доступа к VPS
if ! ssh "${SSH_OPTS[@]}" "${VPS_USER}@${VPS_HOST}" "echo ok" >/dev/null 2>&1; then
echo "Ошибка: нет доступа по SSH к ${VPS_USER}@${VPS_HOST}. Настройте ключ: ssh-copy-id ${VPS_USER}@${VPS_HOST}"
exit 1
fi
# Архив с VPS: mtg, nginx, letsencrypt (katykhin.store), статика сайта
ssh "${SSH_OPTS[@]}" "${VPS_USER}@${VPS_HOST}" "tar -chzf - -C / \
etc/systemd/system/mtg.service \
etc/nginx/sites-available \
etc/nginx/sites-enabled \
etc/letsencrypt/live/katykhin.store \
etc/letsencrypt/archive/katykhin.store \
etc/letsencrypt/renewal/katykhin.store.conf \
var/www/katykhin.store" > "$ARCHIVE"
chmod 600 "$ARCHIVE"
echo "Бэкап MTProto (VPS DE): $ARCHIVE ($(du -h "$ARCHIVE" | cut -f1))"
find "$BACKUP_ROOT" -name 'mtproto-config-*.tar.gz' -mtime +$RETENTION_DAYS -delete

View File

@@ -0,0 +1,110 @@
#!/bin/bash
# Восстановление одного файла vzdump из restic (Yandex S3) через mount.
# Не выкачивает весь репозиторий — подгружаются только нужные данные для выбранного файла.
# Запускать на хосте Proxmox под root. Требуется FUSE (restic mount).
#
# Использование:
# restore-one-vzdump-from-restic.sh [SNAPSHOT] [ПУТЬ_В_СНИМКЕ] [КУДА_СОХРАНИТЬ]
#
# Пример (последний снимок, CT 107, сохранить в /mnt/backup):
# ./restore-one-vzdump-from-restic.sh
# ./restore-one-vzdump-from-restic.sh latest /mnt/backup/proxmox/dump/dump/vzdump-lxc-107-2026_02_26-02_03_14.tar.zst /mnt/backup
#
# Список снимков: restic snapshots
# Список файлов в снимке: restic ls SNAPSHOT
set -e
ENV_FILE="${ENV_FILE:-/root/.restic-yandex.env}"
MOUNT_DIR="${MOUNT_DIR:-/mnt/backup/restic-mount}"
RESTIC_PASSWORD_FILE="${RESTIC_PASSWORD_FILE:-/root/.restic-password}"
SNAPSHOT="${1:-latest}"
# Путь к файлу внутри снимка (как в restic ls) — бэкапим /mnt/backup, пути вида /mnt/backup/proxmox/dump/dump/vzdump-lxc-107-...
FILE_IN_SNAPSHOT="${2:-}"
OUTPUT_DIR="${3:-/mnt/backup}"
if [ "$(id -u)" -ne 0 ]; then
echo "Запускайте под root."
exit 1
fi
if [ ! -f "$ENV_FILE" ]; then
echo "Нет файла $ENV_FILE. Скопируйте restic-yandex-env.example и задайте ключи."
exit 1
fi
# shellcheck source=/dev/null
source "$ENV_FILE"
for var in RESTIC_REPOSITORY AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY; do
if [ -z "${!var}" ]; then
echo "В $ENV_FILE не задано: $var"
exit 1
fi
done
export AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY RESTIC_REPOSITORY
export RESTIC_PASSWORD_FILE
export AWS_DEFAULT_REGION="${AWS_DEFAULT_REGION:-ru-central1}"
if ! command -v restic >/dev/null 2>&1; then
echo "restic не установлен. Установите: apt install restic."
exit 1
fi
if [ -z "$FILE_IN_SNAPSHOT" ]; then
echo "Использование: $0 [SNAPSHOT] ПУТЬ_В_СНИМКЕ [КУДА_СОХРАНИТЬ]"
echo "Пример: $0 latest /mnt/backup/proxmox/dump/dump/vzdump-lxc-107-2026_02_26-02_03_14.tar.zst /mnt/backup"
echo ""
echo "Доступные снимки:"
restic snapshots 2>/dev/null || true
exit 1
fi
# Resolve "latest" to snapshot ID (restic mount показывает ids/<short-id>/)
if [ "$SNAPSHOT" = "latest" ]; then
SNAPSHOT_ID=$(restic snapshots 2>/dev/null | grep -E '^[a-f0-9]{8}[[:space:]]' | tail -1 | awk '{print $1}')
else
SNAPSHOT_ID="$SNAPSHOT"
fi
[ -z "$SNAPSHOT_ID" ] && echo "Не удалось определить ID снимка." && exit 1
echo "Снимок: $SNAPSHOT_ID"
mkdir -p "$OUTPUT_DIR"
mkdir -p "$MOUNT_DIR"
# Проверяем, не смонтировано ли уже
if mountpoint -q "$MOUNT_DIR" 2>/dev/null; then
echo "Точка монтирования $MOUNT_DIR уже занята. Размонтируйте: fusermount -u $MOUNT_DIR"
exit 1
fi
echo "Монтируем репозиторий в $MOUNT_DIR ..."
restic mount "$MOUNT_DIR" &
MOUNT_PID=$!
trap 'kill $MOUNT_PID 2>/dev/null; fusermount -u "$MOUNT_DIR" 2>/dev/null; exit' EXIT INT TERM
# В смонтированном репо файлы снимка лежат в ids/<short_id>/<путь>
SOURCE_FILE="$MOUNT_DIR/ids/$SNAPSHOT_ID/$FILE_IN_SNAPSHOT"
for i in $(seq 1 45); do
if [ -e "$SOURCE_FILE" ] 2>/dev/null; then
break
fi
sleep 1
done
if [ ! -e "$SOURCE_FILE" ]; then
echo "Файл не найден: $SOURCE_FILE (подождали 45 с). Проверьте: restic ls $SNAPSHOT_ID"
exit 1
fi
BASENAME_FILE="$(basename "$FILE_IN_SNAPSHOT")"
OUTPUT_FILE="$OUTPUT_DIR/$BASENAME_FILE"
echo "Копируем $SOURCE_FILE -> $OUTPUT_FILE ..."
cp "$SOURCE_FILE" "$OUTPUT_FILE"
echo "Готово: $OUTPUT_FILE ($(du -sh "$OUTPUT_FILE" | cut -f1))"
# Размонтировать
kill $MOUNT_PID 2>/dev/null || true
wait $MOUNT_PID 2>/dev/null || true
fusermount -u "$MOUNT_DIR" 2>/dev/null || true
trap - EXIT INT TERM
echo "Размонтировано. Для восстановления контейнера:"
echo " pct create NEW_VMID $OUTPUT_FILE --restore 1 --storage local-lvm"