diff --git a/docs/backup/backup-howto.md b/docs/backup/backup-howto.md index 52d5a40..42181ae 100644 --- a/docs/backup/backup-howto.md +++ b/docs/backup/backup-howto.md @@ -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:00–03: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 /path/to/backup.vma.zst --storage local-lvm` (и т.п., см. `pct restore --help`). -- VM: `qm restore /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//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 по разделам 7–8. + +- **Векторы 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** оставлено свободным для плановой перезагрузки сервера. diff --git a/docs/backup/proxmox-phase1-backup.md b/docs/backup/proxmox-phase1-backup.md index 5802bbc..ae3d2d6 100644 --- a/docs/backup/proxmox-phase1-backup.md +++ b/docs/backup/proxmox-phase1-backup.md @@ -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 и обновлять при смене. diff --git a/docs/vps/vpn-vps-amneziawg.md b/docs/vps/vpn-vps-amneziawg.md index 6fc58b0..1d72282 100644 --- a/docs/vps/vpn-vps-amneziawg.md +++ b/docs/vps/vpn-vps-amneziawg.md @@ -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). diff --git a/docs/vps/vpn-vps-mtproto-check-report.md b/docs/vps/vpn-vps-mtproto-check-report.md new file mode 100644 index 0000000..ba56d80 --- /dev/null +++ b/docs/vps/vpn-vps-mtproto-check-report.md @@ -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). Маскировка настроена корректно. diff --git a/docs/vps/vpn-vps-mtproto-site-plan.md b/docs/vps/vpn-vps-mtproto-site-plan.md new file mode 100644 index 0000000..917e457 --- /dev/null +++ b/docs/vps/vpn-vps-mtproto-site-plan.md @@ -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 с MTProto‑proxy (с правильным секретом):** + - Клиент шлёт fake‑TLS трафик с секретом (`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‑клиента выглядит как обычный сайт с Let’s Encrypt‑сертификатом. + +- **AmneziaWG:** + - Продолжает слушать `33118/udp` на VPS, используется для VPN‑туннеля и никак не завязан на MTProto/HTTPS. + +--- + +## Маскировка и безопасность + +- **TLS‑маскировка:** + - Сертификат: Let’s 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`. + +--- + +## Использование + +### MTProto‑proxy + +- **Параметры прокси:** + - Сервер: `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 `. + +--- + +--- + +## Итоговая схема портов + +| Порт | Служба | Доступ | +|-------------|-------------|---------------| +| 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 и сайт» со ссылкой на этот документ и указанием домена/порта. diff --git a/scripts/backup-immich-photos.sh b/scripts/backup-immich-photos.sh index 6127bec..4fca34e 100644 --- a/scripts/backup-immich-photos.sh +++ b/scripts/backup-immich-photos.sh @@ -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 diff --git a/scripts/backup-restic-yandex-photos.sh b/scripts/backup-restic-yandex-photos.sh new file mode 100644 index 0000000..5d46416 --- /dev/null +++ b/scripts/backup-restic-yandex-photos.sh @@ -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." diff --git a/scripts/backup-restic-yandex.sh b/scripts/backup-restic-yandex.sh index 589764f..c744e04 100644 --- a/scripts/backup-restic-yandex.sh +++ b/scripts/backup-restic-yandex.sh @@ -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:00–03: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)..." diff --git a/scripts/backup-vps-mtproto.sh b/scripts/backup-vps-mtproto.sh new file mode 100644 index 0000000..ecd2237 --- /dev/null +++ b/scripts/backup-vps-mtproto.sh @@ -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 diff --git a/scripts/restore-one-vzdump-from-restic.sh b/scripts/restore-one-vzdump-from-restic.sh new file mode 100644 index 0000000..3323c39 --- /dev/null +++ b/scripts/restore-one-vzdump-from-restic.sh @@ -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//) +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//<путь> +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"