Update documentation to centralize Vaultwarden integration details and enhance backup scripts
Refactor README, architecture, and backup documentation to emphasize the use of Vaultwarden for credential management across various services. Update scripts for Nextcloud, Gitea, Paperless, and others to reference Vaultwarden for sensitive information. Remove outdated references to previous backup strategies and ensure clarity on credential retrieval processes. This improves security practices and streamlines backup operations.
This commit is contained in:
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
**Точка входа:** [Архитектура и подключение](docs/architecture/architecture.md) — схема сети, IP, домены, таблица всех хостов.
|
**Точка входа:** [Архитектура и подключение](docs/architecture/architecture.md) — схема сети, IP, домены, таблица всех хостов.
|
||||||
**Топология и риски:** [Схема сети и зависимости](docs/network/network-topology.md) — узлы, маршруты NPM, зависимости сервисов, единые точки отказа (SPOF).
|
**Топология и риски:** [Схема сети и зависимости](docs/network/network-topology.md) — узлы, маршруты NPM, зависимости сервисов, единые точки отказа (SPOF).
|
||||||
**Приоритет №1:** [Бэкапы Proxmox (фаза 1)](docs/backup/proxmox-phase1-backup.md) — стратегия бэкапов LXC/VM и /etc/pve, тестовое восстановление.
|
**Приоритет №1:** [Бэкапы: как устроены и как восстанавливать](docs/backup/backup-howto.md) — что бэкапится, куда, когда и как восстановить.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -9,8 +9,9 @@
|
|||||||
- **Внешний IP:** 185.35.193.144
|
- **Внешний IP:** 185.35.193.144
|
||||||
- **Домашний сервер (Proxmox):** 192.168.1.150 (LAN)
|
- **Домашний сервер (Proxmox):** 192.168.1.150 (LAN)
|
||||||
- Подключение: `ssh root@192.168.1.150`
|
- Подключение: `ssh root@192.168.1.150`
|
||||||
|
- **Прямой SSH на контейнеры и ВМ:** `ssh root@192.168.1.{100,101,103,104,105,107,108,109}`; ВМ 200: `ssh admin@192.168.1.200`. Ключи развёртываются скриптом `scripts/deploy-ssh-keys-homelab.sh`.
|
||||||
- **DNS домена katykhin.ru:** Beget.com
|
- **DNS домена katykhin.ru:** Beget.com
|
||||||
- Учётная запись: логин `amauri7g`, пароль `QgkaKL3RykeI`, ID аккаунта 2536839. Режим API включён. Домен **katykhin.store** в аккаунте есть, но не используется (поддоменов нет).
|
- Учётная запись: логин и пароль в Vaultwarden (объект **beget**). Режим API включён. Домен **katykhin.store** в аккаунте есть, но не используется (поддоменов нет).
|
||||||
- **Reverse proxy и SSL:** Nginx Proxy Manager (NPM) на контейнере 100.
|
- **Reverse proxy и SSL:** Nginx Proxy Manager (NPM) на контейнере 100.
|
||||||
|
|
||||||
**Поддомены katykhin.ru:**
|
**Поддомены katykhin.ru:**
|
||||||
|
|||||||
@@ -433,7 +433,7 @@ restic restore SNAPSHOT_ID --target /mnt/backup/restore-db --path /mnt/backup/da
|
|||||||
|
|
||||||
Если дампы БД (Nextcloud, Paperless, Gitea), архив Vaultwarden или векторы RAG (CT 105) получаются по 20 байт — в копию попал только пустой gzip/tar, команда внутри контейнера не отдала данные. Скрипты при размере < 512 байт завершаются с ошибкой и выводят stderr. Для векторов проверьте путь `/home/rag-service/data/vectors` в CT 105: `pct exec 105 -- ls -la /home/rag-service/data/vectors`.
|
Если дампы БД (Nextcloud, Paperless, Gitea), архив Vaultwarden или векторы RAG (CT 105) получаются по 20 байт — в копию попал только пустой gzip/tar, команда внутри контейнера не отдала данные. Скрипты при размере < 512 байт завершаются с ошибкой и выводят stderr. Для векторов проверьте путь `/home/rag-service/data/vectors` в CT 105: `pct exec 105 -- ls -la /home/rag-service/data/vectors`.
|
||||||
|
|
||||||
**Частая причина дампов БД:** PostgreSQL в контейнере требует пароль (md5/scram). Скрипты берут пароль из **Vaultwarden** (Bitwarden CLI `bw`): объекты **NEXTCLOUD** (поле `dbpassword` или пароль), **PAPERLESS**, **GITEA**. На хосте нужны: `bw`, при необходимости `jq`, разблокировка по мастер-паролю из файла `/root/.bw-master` (см. [Переключение скриптов на секреты из Vaultwarden](proxmox-phase1-backup.md#переключение-скриптов-на-секреты-из-vaultwarden) в proxmox-phase1-backup.md).
|
**Частая причина дампов БД:** PostgreSQL в контейнере требует пароль (md5/scram). Скрипты берут пароль из **Vaultwarden** (Bitwarden CLI `bw`): объекты **NEXTCLOUD** (поле `dbpassword` или пароль), **PAPERLESS**, **GITEA**. На хосте нужны: `bw`, при необходимости `jq`, разблокировка по мастер-паролю из файла `/root/.bw-master` (см. [vaultwarden-secrets.md](../vaultwarden-secrets.md)).
|
||||||
|
|
||||||
**Проверка вручную (без подавления stderr):** зайти в контейнер и выполнить дамп, чтобы увидеть сообщение об ошибке:
|
**Проверка вручную (без подавления stderr):** зайти в контейнер и выполнить дамп, чтобы увидеть сообщение об ошибке:
|
||||||
|
|
||||||
@@ -522,7 +522,7 @@ chmod 600 /root/.telegram-notify.env
|
|||||||
|
|
||||||
## Связанные документы
|
## Связанные документы
|
||||||
|
|
||||||
- [Стратегия бэкапов (фаза 1)](proxmox-phase1-backup.md) — общий план и принятые решения.
|
- [Vaultwarden и секреты](../vaultwarden-secrets.md) — получение паролей через `bw` для скриптов бэкапов.
|
||||||
- [Архитектура](../architecture/architecture.md) — хост, IP, доступ.
|
- [Архитектура](../architecture/architecture.md) — хост, IP, доступ.
|
||||||
- [VM 200 (Immich)](../containers/container-200.md) — сервисы, пути, .env.
|
- [VM 200 (Immich)](../containers/container-200.md) — сервисы, пути, .env.
|
||||||
|
|
||||||
|
|||||||
@@ -1,377 +0,0 @@
|
|||||||
# Фаза 1: Стратегия бэкапов Proxmox
|
|
||||||
|
|
||||||
Цель: при смерти SSD с системой или потере `/etc/pve` — развернуть Proxmox, восстановить контейнеры/ВМ, поднять сервисы без многочасового восстановления и угадывания паролей.
|
|
||||||
|
|
||||||
**Приоритет №1.** ИБП уже есть; защищаемся от смерти диска.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Что бэкапить
|
|
||||||
|
|
||||||
| Объект | Зачем |
|
|
||||||
|--------|--------|
|
|
||||||
| **LXC и VM целиком** (vzdump) | Восстановление контейнера/ВМ из одного архива: ОС, конфиги, данные на корневом томе. Не только данные внутри — сам образ для restore. |
|
|
||||||
| **/etc/pve** | Конфиги кластера, VM/LXC (ID, сеть, диски, задачи), пользователи Proxmox, права. Без этого после переустановки Proxmox не восстановить привязку дисков и настройки. |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Пошаговый план
|
|
||||||
|
|
||||||
### Шаг 1. Определить хранилище для бэкапов
|
|
||||||
|
|
||||||
**Выбранная схема:**
|
|
||||||
|
|
||||||
| Место | Описание | Что туда |
|
|
||||||
|-------|----------|----------|
|
|
||||||
| **Локально: /dev/sdb1 (1 ТБ под бэкапы)** | Отдельный SSD 2 ТБ; под копии выделен 1 ТБ, смонтирован в `/mnt/backup`. Второй ТБ — в запас. | Proxmox vzdump (через UI), затем те же данные (dump, etc-pve, фотки, VPS) в Yandex через restic. Фотки: оригиналы + метаданные + БД Immich; остальное пересчитать можно. VPS: Amnezia — конфиг; Миран — БД бота + контент (контент можно в S3 Мирана; копию на sdb1 — опционально). Конфигурацию серверов не бэкапим — есть Ansible. |
|
|
||||||
| **Офсайт: Yandex Object Storage (S3)** | Арендованный бакет, S3-совместимый API. [Yandex Object Storage](https://yandex.cloud/ru/docs/storage/s3/). | **Restic** с хоста (cron на ноде Proxmox): выгрузка содержимого `/mnt/backup`. Retention: 3 daily, 2 weekly, 2 monthly. |
|
|
||||||
|
|
||||||
**Принято:** Вариант A — отдельный диск/раздел на хосте (sdb1, 1 ТБ в `/mnt/backup`). Варианты B (NFS/SMB) и C (внешний USB) в текущей схеме не используются; USB опционально для параноидального 3-2-1 (см. выше).
|
|
||||||
|
|
||||||
**3-2-1:** Три копии: (1) прод — система и данные на основном диске; (2) локальный бэкап — sdb1; (3) офсайт — Yandex. Два типа носителей: локальный SSD и облачное object storage. Один офсайт — да. **Стратегия удовлетворяет 3-2-1.** Опционально: четвёртая копия на внешнем USB/другом ПК для параноидального сценария (пожар/кража) — по желанию.
|
|
||||||
|
|
||||||
**Принято:** Точка монтирования — `/mnt/backup`. На диске 2 ТБ выделено 1 ТБ под бэкапы; второй ТБ пока в запас, назначение не определено.
|
|
||||||
|
|
||||||
**Действие:** Разметить 1 ТБ на /dev/sdb1, создать ФС (ext4/xfs), смонтировать в `/mnt/backup`. Структуру каталогов — см. раздел «Структура локального хранилища» ниже.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Шаг 2. Добавить Backup Storage в Proxmox
|
|
||||||
|
|
||||||
1. В веб-интерфейсе: **Datacenter → Storage → Add**.
|
|
||||||
2. Тип: **Directory** (если папка на локальном диске) или **NFS/CIFS** (если сетевое).
|
|
||||||
3. Указать:
|
|
||||||
- **ID** (например `backup-local` или `backup-nfs`),
|
|
||||||
- **Directory** — путь, например `/mnt/backup/dump`,
|
|
||||||
- Включить опции **Content: VZDump backup file** (и при необходимости **ISO**, **Container template** — по желанию).
|
|
||||||
4. Сохранить. Убедиться, что storage виден и доступен для записи.
|
|
||||||
|
|
||||||
Если используешь NFS: сначала смонтировать NFS в `/mnt/backup` на хосте (fstab или systemd mount), затем добавить Storage с Directory `/mnt/backup/dump`.
|
|
||||||
|
|
||||||
**Для выбранной схемы:** Directory = `/mnt/backup/proxmox/dump` (см. структуру ниже).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Структура локального хранилища (1 ТБ на sdb1)
|
|
||||||
|
|
||||||
«Аналог S3» на одном диске — это просто понятная иерархия каталогов. **MinIO не используем:** лишний сервис; достаточно директорий и restic с бэкендом `local` (если понадобится локальный restic) и `s3` для Yandex. Proxmox пишет в **Directory** — ему достаточно пути.
|
|
||||||
|
|
||||||
Пример структуры под `/mnt/backup`:
|
|
||||||
|
|
||||||
```
|
|
||||||
/mnt/backup/
|
|
||||||
├── proxmox/
|
|
||||||
│ ├── dump/ ← Proxmox Backup Job (VZDump) — сюда добавляем Storage в PVE
|
|
||||||
│ └── etc-pve/ ← архивы tar.gz из cron: etc-pve-*, etc-host-configs-* (backup-etc-pve.sh)
|
|
||||||
├── restic/
|
|
||||||
│ ├── local/ ← репозитории restic для локальных снапшотов (опционально)
|
|
||||||
│ └── ... ← или restic только в Yandex, локально только сырые копии
|
|
||||||
├── photos/ ← Immich: оригиналы фото + метаданные + БД (остальное пересчитать)
|
|
||||||
├── vps/ ← Amnezia: конфиг. Миран: БД бота (+ контент при необходимости; основной контент можно в S3 Мирана)
|
|
||||||
└── other/ ← прочие важные данные (конфиги, скрипты, что ещё решите)
|
|
||||||
```
|
|
||||||
|
|
||||||
Квоты: при необходимости ограничить размер по каталогам (например `proxmox/dump` — не более 500 ГБ) через отдельные подразделы или скрипты очистки (retention).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Шифрование диска бэкапов (LUKS)
|
|
||||||
|
|
||||||
**LUKS** (Linux Unified Key Setup) — стандартное шифрование раздела в Linux. Если диск с бэкапами украдут или вынесут, без пароля/ключа данные не прочитать. Минусы: нужно вводить пароль при загрузке (или хранить ключ на другом носителе), небольшая нагрузка на CPU.
|
|
||||||
|
|
||||||
**Принято:** LUKS пока не используем. Раздел sdb1 — без шифрования. При необходимости можно добавить позже (потребуется перенос данных).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Restic и Yandex Object Storage
|
|
||||||
|
|
||||||
- **Restic** поддерживает бэкенд **S3**. Yandex Object Storage совместим с S3 API — используешь endpoint бакета и ключи (Access Key / Secret).
|
|
||||||
- **Retention в Yandex:** 3 daily, 2 weekly, 2 monthly — `restic forget --keep-daily 3 --keep-weekly 2 --keep-monthly 2` и затем `prune`.
|
|
||||||
- **Что гнать в Yandex через restic:** Proxmox dump (каталог `proxmox/dump`), `/etc/pve` (архивы из `proxmox/etc-pve`), фотки (оригиналы + метаданные + БД Immich), бэкапы с VPS — см. «Принятые решения: что куда» ниже.
|
|
||||||
|
|
||||||
**Локально отдельной политики retention для restic не нужно:** на sdb1 retention задаётся в самом Proxmox Backup Job (например «keep last 7») и в скрипте бэкапа `/etc/pve` (удалять архивы старше N дней). Restic используется только для отправки в Yandex с политикой 7/4/6. Локальных restic-репозиториев можно не заводить — только каталоги и выгрузка их содержимого в облако.
|
|
||||||
|
|
||||||
Документация Yandex: [Object Storage S3](https://yandex.cloud/ru/docs/storage/s3/). Нужны: bucket name, region, endpoint, Static Key (Access Key ID + Secret Access Key). **Бакет создан; ключи и endpoint зафиксировать при настройке restic.**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Хранилище паролей
|
|
||||||
|
|
||||||
Чтобы не терять пароли при восстановлении и держать креды в одном месте. Рассматривались варианты: Vaultwarden (self-hosted), Bitwarden Cloud, KeePass/KeePassXC, 1Password и др.
|
|
||||||
|
|
||||||
**Принято и сделано:** **Vaultwarden** развёрнут на **CT 103** LXC. Домен через NPM (HTTPS), клиенты Bitwarden на ПК/телефоне. Бэкап данных Vaultwarden включён в общий план (restic → Yandex).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Где запускать бэкапы (централизация)
|
|
||||||
|
|
||||||
**С хоста Proxmox (cron на ноде):**
|
|
||||||
|
|
||||||
- **Proxmox Backup Job** уже выполняется на хосте и пишет vzdump всех выбранных LXC/VM в `/mnt/backup/proxmox/dump` — это и есть «бэкапы всех контейнеров и ВМ», централизованно.
|
|
||||||
- **Restic** (backup/forget/prune) тоже запускаем с хоста по cron: бэкапит каталоги на хосте (например весь `/mnt/backup` или выбранные подкаталоги) в Yandex S3. Данные для бэкапа — локальные пути (dump, etc-pve, а фотки и данные с VPS нужно либо копировать на хост в `/mnt/backup` скриптами, либо монтировать и тогда restic будет их читать с хоста). Контейнеры и ВМ целиком не бэкапим через restic — ими занимается только Proxmox Backup Job.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Шаг 3. Настроить расписание бэкапа LXC и VM
|
|
||||||
|
|
||||||
1. **Datacenter → Backup** (или **Backup** в меню узла).
|
|
||||||
2. **Add** — создаётся задача (Job).
|
|
||||||
3. Параметры:
|
|
||||||
- **Storage** — выбранное хранилище (шаг 2).
|
|
||||||
- **Schedule** — например `0 2 * * *` (каждую ночь в 02:00). Подстроить под окно, когда нагрузка минимальна.
|
|
||||||
- **Selection mode:** включить нужные узлы (или **All**), затем отметить **галочками** конкретные **LXC (100–108) и VM (200)**. Либо выбрать "Backup all" для всех VMs/containers.
|
|
||||||
- **Mode:**
|
|
||||||
- **Snapshot** — контейнер/ВМ не останавливается, создаётся снимок (рекомендуется для минимизации даунтайма).
|
|
||||||
- **Suspend** — ВМ приостанавливается на время бэкапа (более консистентно для БД, но даунтайм).
|
|
||||||
Для LXC обычно достаточно **Snapshot**. Для **VM 200** (PostgreSQL и др.): Snapshot **не гарантирует консистентность БД** — PostgreSQL может быть в середине транзакции. **Правильная стратегия:** внутри VM делать логический бэкап БД (`pg_dump`), а **vzdump snapshot** использовать для остального (ОС, конфиги, файлы). Итого: VM 200 — vzdump snapshot ок для образа; консистентность БД — отдельно через `pg_dump` внутри гостя.
|
|
||||||
- **Compression:** ZSTD (хороший компромисс скорость/размер).
|
|
||||||
- **Retention:** например «Keep last 7» или «Keep last 4 weekly» — чтобы не забивать диск.
|
|
||||||
|
|
||||||
4. Сохранить job. Проверить по кнопке **Backup now**, что задача запускается и файлы появляются в Storage.
|
|
||||||
|
|
||||||
Важно: бэкап должен включать **и LXC, и VM 200**. Не только данные внутри них (те уже описаны в документации контейнеров), а именно полный dump для restore.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Шаг 4. Бэкап /etc/pve и конфигов хоста
|
|
||||||
|
|
||||||
Конфиги кластера и виртуалок лежат в `/etc/pve`. Плюс для восстановления хоста полезны: `/etc/network/interfaces`, `/etc/hosts`, `/etc/resolv.conf`. Всё это нужно копировать регулярно и хранить в безопасном месте (желательно не только на том же диске, что и система).
|
|
||||||
|
|
||||||
**Принято: вариант A — cron на хосте Proxmox.**
|
|
||||||
|
|
||||||
1. Создать скрипт, например `/root/scripts/backup-etc-pve.sh`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
#!/bin/bash
|
|
||||||
BACKUP_ROOT="/mnt/backup/proxmox/etc-pve" # по структуре выше
|
|
||||||
DATE=$(date +%Y%m%d-%H%M)
|
|
||||||
mkdir -p "$BACKUP_ROOT"
|
|
||||||
tar -czf "$BACKUP_ROOT/etc-pve-$DATE.tar.gz" -C / etc/pve
|
|
||||||
tar -czf "$BACKUP_ROOT/etc-host-configs-$DATE.tar.gz" -C / etc/network/interfaces etc/hosts etc/resolv.conf
|
|
||||||
# опционально: удалять бэкапы старше N дней
|
|
||||||
# find "$BACKUP_ROOT" -name 'etc-pve-*.tar.gz' -mtime +30 -delete
|
|
||||||
# find "$BACKUP_ROOT" -name 'etc-host-configs-*.tar.gz' -mtime +30 -delete
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Сделать исполняемым: `chmod +x /root/scripts/backup-etc-pve.sh`.
|
|
||||||
3. Добавить в cron: `crontab -e`. Окно внутренних бэкапов 01:00–03:30; пример для etc-pve: `15 2 * * * /root/scripts/backup-etc-pve.sh`
|
|
||||||
|
|
||||||
**Вариант B:** Тот же скрипт можно вызывать из задачи в Proxmox (Script/Command в задаче типа Hook script), но проще и надёжнее — отдельный cron на хосте.
|
|
||||||
|
|
||||||
Бэкапы (`etc-pve-*.tar.gz`, `etc-host-configs-*.tar.gz`) хранить **локально** (`/mnt/backup/proxmox/etc-pve`) и **в Yandex** — включить этот каталог в источники restic (мало весит, критично при потере хоста). Файлы с ограниченными правами (chmod 600); `/etc/pve` содержит секреты — не выкладывать в открытый доступ.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Шаг 5. Хранить секреты отдельно (пароли, ключи)
|
|
||||||
|
|
||||||
Чтобы «не вспоминать пароли 3 часа» после восстановления:
|
|
||||||
|
|
||||||
**Секретное хранилище** — Vaultwarden на **CT 103** (см. выше). Туда: root Proxmox, пользователи PVE, пароли БД и сервисов (Nextcloud, Gitea, Paperless, Immich, NPM, Galene и т.д.), API-ключи (Beget, certbot, Wallos и др.). Полный список кредов по контейнерам — в статьях container-100 … container-200; свести в один список в Vaultwarden и обновлять при смене паролей.
|
|
||||||
|
|
||||||
Это не «шаг бэкапа», но обязательная часть восстановления: без паролей восстановленные контейнеры не войдут в сервисы.
|
|
||||||
|
|
||||||
**Инвентаризация секретов для переноса в Vaultwarden** — ниже сводная таблица: где лежат креды сейчас и какой объект в Vaultwarden им соответствует. Команды для получения значений из Vaultwarden и переключение скриптов — в разделе «Получение секретов из Vaultwarden» ниже.
|
|
||||||
|
|
||||||
| Хост / CT / VM | Текущее место | Объект Vaultwarden |
|
|
||||||
|----------------|----------------|---------------------|
|
|
||||||
| Proxmox (хост) | root, пользователи PVE | (в менеджере вручную) |
|
|
||||||
| Proxmox (хост) | `/root/.restic-yandex.env`, `/root/.restic-password` | **RESTIC** (поля: RESTIC_BACKUP_KEY, RESTIC_REPOSITORY, AWS_ACCESS_KEY_ID, AWS_DEFAULT_REGION, AWS_SECRET_ACCESS_KEY, TELEGRAM_SELF_CHAT_ID) |
|
|
||||||
| Proxmox (хост) | `/root/.telegram-notify.env` | **HOME_BOT_TOKEN** (пароль = токен бота), **RESTIC** (поле TELEGRAM_SELF_CHAT_ID = chat_id) |
|
|
||||||
| CT 100 | `/root/.secrets/certbot/beget.ini` | **beget** (логин, пароль) |
|
|
||||||
| CT 100 | NPM админка | (в менеджере вручную) |
|
|
||||||
| CT 100 | VPN Route Check compose | **localhost** (логин admin, пароль, поле ROUTER_TELNET_HOST) |
|
|
||||||
| CT 100 | custom_ssl / letsencrypt | (восстановление из /etc/letsencrypt; в Vaultwarden не храним) |
|
|
||||||
| CT 101 | Nextcloud compose, config.php | **NEXTCLOUD** (логин nextcloud, пароль; поля: NEXTCLOUD_TRUSTED_DOMAINS, instanceid, passwordsalt, secret, dbpassword) |
|
|
||||||
| CT 103 | Gitea compose, .env, app.ini | **GITEA** (логин gitea, пароль; поля: GITEA__database__DB_TYPE, GITEA__database__HOST, GITEA_RUNNER_REGISTRATION_TOKEN, LFS_JWT_SECRET, INTERNAL_TOKEN) |
|
|
||||||
| CT 103 | CouchDB local.ini | **OBSIDIAN** (логин obsidian, пароль) |
|
|
||||||
| CT 103 | Vaultwarden .env | **VAULTWARDEN** (пароль = ADMIN_TOKEN, поле SIGNUPS_ALLOWED) |
|
|
||||||
| CT 104 | Paperless compose, docker-compose.env | **PAPERLESS** (логин paperless, пароль; поля: PAPERLESS_URL, PAPERLESS_SECRET_KEY, PAPERLESS_TIME_ZONE, PAPERLESS_OCR_LANGUAGE, PAPERLESS_OCR_LANGUAGES) |
|
|
||||||
| CT 107 | Invidious compose | **INVIDIOUS** (логин kemal, пароль; поля: SERVER_SECRET_KEY, test) |
|
|
||||||
| CT 108 | ice-servers.json | **GALENE** (поле config — JSON TURN) |
|
|
||||||
| VM 200 | `/opt/immich/.env` | **IMMICH** (логин/пароль и поля по .env) |
|
|
||||||
| VM 200 | `/opt/immich-deduper/.env` | **IMMICH_DEDUPER** (логин postgres, пароль; поля: DEDUP_PORT, DEDUP_DATA, DEDUP_IMAGE, IMMICH_PATH, PSQL_HOST, PSQL_PORT, PSQL_DB) |
|
|
||||||
| Proxmox (хост) | `/root/.vps-miran-s3.env` | **MIRAN_S3** (поля: S3_ACCESS_KEY, S3_SECRET_KEY, S3_BUCKET_NAME) |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### Получение секретов из Vaultwarden
|
|
||||||
|
|
||||||
**Требования:** установлены `bw` (Bitwarden CLI) и `jq`; настроен сервер: `bw config server https://vault.katykhin.ru`. Мастер-пароль задаётся через переменную `BW_MASTER_PASSWORD` или через **файл с доступом только для текущего пользователя** (`chmod 600`), например `/root/.bw-master`; файл не хранить в репозитории. Перед запросами: `bw sync` и разблокировка: `export BW_SESSION=$(bw unlock --passwordenv BW_MASTER_PASSWORD --raw)` или `bw unlock --passwordfile /path/to/file --raw`.
|
|
||||||
|
|
||||||
**Команды по объектам** (выполнять после `bw unlock` в той же сессии):
|
|
||||||
|
|
||||||
| Объект | Логин / пароль | Кастомное поле |
|
|
||||||
|--------|----------------|----------------|
|
|
||||||
| **beget** | `bw get username "beget"`, `bw get password "beget"` | — |
|
|
||||||
| **GALENE** | — | `bw get item "GALENE" \| jq -r '.fields[] \| select(.name=="config") \| .value'` |
|
|
||||||
| **GITEA** | `bw get username "GITEA"`, `bw get password "GITEA"` | `bw get item "GITEA" \| jq -r '.fields[] \| select(.name=="ИМЯ_ПОЛЯ") \| .value'` — подставить GITEA_RUNNER_REGISTRATION_TOKEN, LFS_JWT_SECRET, INTERNAL_TOKEN, GITEA__database__DB_TYPE, GITEA__database__HOST |
|
|
||||||
| **HOME_BOT_TOKEN** | — | пароль = токен: `bw get password "HOME_BOT_TOKEN"` |
|
|
||||||
| **IMMICH** | по структуре в Vaultwarden | `bw get item "IMMICH" \| jq '.fields'` |
|
|
||||||
| **IMMICH_DEDUPER** | `bw get username "IMMICH_DEDUPER"`, `bw get password "IMMICH_DEDUPER"` | поля DEDUP_*, IMMICH_PATH, PSQL_* — через `jq -r '.fields[] \| select(.name=="X") \| .value'` |
|
|
||||||
| **INVIDIOUS** | `bw get username "INVIDIOUS"`, `bw get password "INVIDIOUS"` | `bw get item "INVIDIOUS" \| jq -r '.fields[] \| select(.name=="SERVER_SECRET_KEY") \| .value'` |
|
|
||||||
| **localhost** | `bw get username "localhost"`, `bw get password "localhost"` | `bw get item "localhost" \| jq -r '.fields[] \| select(.name=="ROUTER_TELNET_HOST") \| .value'` |
|
|
||||||
| **MIRAN_S3** | — | S3_ACCESS_KEY: `bw get item "MIRAN_S3" \| jq -r '.fields[] \| select(.name=="S3_ACCESS_KEY") \| .value'`; аналогично S3_SECRET_KEY, S3_BUCKET_NAME |
|
|
||||||
| **NEXTCLOUD** | `bw get username "NEXTCLOUD"`, `bw get password "NEXTCLOUD"` | secret: `bw get item "NEXTCLOUD" \| jq -r '.fields[] \| select(.name=="secret") \| .value'`; dbpassword, passwordsalt, instanceid, NEXTCLOUD_TRUSTED_DOMAINS — то же с `.name=="..."` |
|
|
||||||
| **OBSIDIAN** | `bw get username "OBSIDIAN"`, `bw get password "OBSIDIAN"` | — |
|
|
||||||
| **PAPERLESS** | `bw get username "PAPERLESS"`, `bw get password "PAPERLESS"` | PAPERLESS_SECRET_KEY, PAPERLESS_URL и др. — `bw get item "PAPERLESS" \| jq -r '.fields[] \| select(.name=="PAPERLESS_SECRET_KEY") \| .value'` |
|
|
||||||
| **RESTIC** | — | RESTIC_BACKUP_KEY: `bw get item "RESTIC" \| jq -r '.fields[] \| select(.name=="RESTIC_BACKUP_KEY") \| .value'`; RESTIC_REPOSITORY, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_DEFAULT_REGION, TELEGRAM_SELF_CHAT_ID — то же с нужным `.name` |
|
|
||||||
| **VAULTWARDEN** | — | пароль = ADMIN_TOKEN: `bw get password "VAULTWARDEN"`; SIGNUPS_ALLOWED — из полей |
|
|
||||||
|
|
||||||
**Универсальный шаблон для поля по имени:**
|
|
||||||
`bw get item "ИМЯ_ОБЪЕКТА" | jq -r '.fields[] | select(.name=="ИМЯ_ПОЛЯ") | .value'`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### Переключение скриптов на секреты из Vaultwarden
|
|
||||||
|
|
||||||
Ниже — как перейти с чтения из файлов на подстановку из `bw` на **хосте Proxmox**. Мастер-пароль хранить **только в файле с доступом для текущего пользователя:** `chmod 600 /root/.bw-master` (владелец root — только root читает/пишет); в репозиторий файл не коммитить. Либо задавать переменную окружения при запуске по крону.
|
|
||||||
|
|
||||||
**1. Restic (backup-restic-yandex.sh, backup-restic-yandex-photos.sh, restore-one-vzdump-from-restic.sh)**
|
|
||||||
|
|
||||||
Сейчас: `source /root/.restic-yandex.env`, пароль из `/root/.restic-password`.
|
|
||||||
|
|
||||||
Переключение: в начале скрипта (после `set -e`) разблокировать BW и выставить переменные из объекта **RESTIC**:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Разблокировать Vaultwarden (мастер-пароль из файла с chmod 600 или env)
|
|
||||||
BW_MASTER_PASSWORD_FILE="${BW_MASTER_PASSWORD_FILE:-/root/.bw-master}"
|
|
||||||
if [ -f "$BW_MASTER_PASSWORD_FILE" ]; then
|
|
||||||
export BW_SESSION=$(bw unlock --passwordfile "$BW_MASTER_PASSWORD_FILE" --raw)
|
|
||||||
fi
|
|
||||||
# Подставить секреты из RESTIC
|
|
||||||
ITEM=$(bw get item "RESTIC")
|
|
||||||
export RESTIC_REPOSITORY=$(echo "$ITEM" | jq -r '.fields[] | select(.name=="RESTIC_REPOSITORY") | .value')
|
|
||||||
export AWS_ACCESS_KEY_ID=$(echo "$ITEM" | jq -r '.fields[] | select(.name=="AWS_ACCESS_KEY_ID") | .value')
|
|
||||||
export AWS_SECRET_ACCESS_KEY=$(echo "$ITEM" | jq -r '.fields[] | select(.name=="AWS_SECRET_ACCESS_KEY") | .value')
|
|
||||||
export AWS_DEFAULT_REGION=$(echo "$ITEM" | jq -r '.fields[] | select(.name=="AWS_DEFAULT_REGION") | .value')
|
|
||||||
RESTIC_PASSWORD=$(echo "$ITEM" | jq -r '.fields[] | select(.name=="RESTIC_BACKUP_KEY") | .value')
|
|
||||||
export RESTIC_PASSWORD_FILE=$(mktemp -u)
|
|
||||||
echo -n "$RESTIC_PASSWORD" > "$RESTIC_PASSWORD_FILE"
|
|
||||||
chmod 600 "$RESTIC_PASSWORD_FILE"
|
|
||||||
trap 'rm -f "$RESTIC_PASSWORD_FILE"' EXIT
|
|
||||||
```
|
|
||||||
|
|
||||||
Убрать из скрипта: проверку/чтение `ENV_FILE` и `RESTIC_PASSWORD_FILE` из файлов; оставить использование переменных `RESTIC_REPOSITORY`, `AWS_*`, `RESTIC_PASSWORD_FILE` как выше. Для restore-one-vzdump-from-restic.sh — тот же блок в начале.
|
|
||||||
|
|
||||||
**2. Telegram (notify-telegram.sh)**
|
|
||||||
|
|
||||||
Сейчас: `source /root/.telegram-notify.env`, переменные `TELEGRAM_BOT_TOKEN`, `TELEGRAM_CHAT_ID`.
|
|
||||||
|
|
||||||
Переключение: читать токен из **HOME_BOT_TOKEN** (пароль), chat_id из объекта **RESTIC** (поле TELEGRAM_SELF_CHAT_ID). В начале скрипта (если нет уже разблокировки):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
BW_MASTER_PASSWORD_FILE="${BW_MASTER_PASSWORD_FILE:-/root/.bw-master}"
|
|
||||||
if [ -f "$BW_MASTER_PASSWORD_FILE" ]; then
|
|
||||||
export BW_SESSION=$(bw unlock --passwordfile "$BW_MASTER_PASSWORD_FILE" --raw)
|
|
||||||
fi
|
|
||||||
TELEGRAM_BOT_TOKEN=$(bw get password "HOME_BOT_TOKEN")
|
|
||||||
TELEGRAM_CHAT_ID=$(bw get item "RESTIC" | jq -r '.fields[] | select(.name=="TELEGRAM_SELF_CHAT_ID") | .value')
|
|
||||||
```
|
|
||||||
|
|
||||||
Дальше в скрипте использовать `TELEGRAM_BOT_TOKEN` и `TELEGRAM_CHAT_ID` как раньше (проверка на пустоту, вызов curl). Файл `/root/.telegram-notify.env` можно не использовать.
|
|
||||||
|
|
||||||
**3. Дампы БД (backup-ct101-pgdump.sh, backup-ct104-pgdump.sh, backup-ct103-gitea-pgdump.sh)**
|
|
||||||
|
|
||||||
Скрипты уже берут PGPASSWORD из Vaultwarden: в начале разблокировка `bw unlock --passwordfile "$BW_MASTER_PASSWORD_FILE" --raw`, затем для pg_dump передаётся `-e PGPASSWORD=...` в `docker exec`. Источники паролей:
|
|
||||||
|
|
||||||
- **Nextcloud (CT 101):** объект **NEXTCLOUD** — поле `dbpassword` или пароль записи (`bw get password "NEXTCLOUD"`).
|
|
||||||
- **Paperless (CT 104):** объект **PAPERLESS** — пароль (`bw get password "PAPERLESS"`).
|
|
||||||
- **Gitea (CT 103):** объект **GITEA** — пароль (`bw get password "GITEA"`).
|
|
||||||
|
|
||||||
Требования на хосте: `bw`, для Nextcloud — `jq`; файл `/root/.bw-master` с мастер-паролем (chmod 600). При ошибке (дамп < 512 байт) скрипт завершается с кодом 1 и выводит stderr; уведомление в Telegram при ошибке не отправляется.
|
|
||||||
|
|
||||||
**4. Остальные места**
|
|
||||||
|
|
||||||
Конфиги сервисов (Nextcloud config.php, Gitea compose, Paperless .env, Immich .env и т.д.) подставлять вручную при восстановлении или написать небольшие скрипты-обёртки, которые один раз получают нужные значения через `bw get item` / `bw get password` и пишут в .env или конфиг. Имена объектов и полей — по таблице выше.
|
|
||||||
|
|
||||||
После переключения: обновить чек-лист (отметить «Секреты перенесены в Vaultwarden») и при необходимости добавить в cron установку `BW_MASTER_PASSWORD_FILE` или вызов разблокировки в общем wrapper’е.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Шаг 6. Тестовое восстановление одного контейнера
|
|
||||||
|
|
||||||
Без проверки восстановления нельзя считать стратегию рабочей.
|
|
||||||
|
|
||||||
1. Выбрать **некритичный** контейнер (например 105 — RAG, или 107 — Invidious), для которого краткий даунтайм допустим.
|
|
||||||
2. Убедиться, что есть свежий backup этого контейнера в Storage (после шага 3).
|
|
||||||
3. **Восстановление:**
|
|
||||||
- В Proxmox UI: **Datacenter → Backup** → выбрать storage → найти backup нужного CT → **Restore**. Указать **new VMID** (например 999 для теста) и **Storage** для дисков.
|
|
||||||
- Или с CLI:
|
|
||||||
`qmrestore` для VM, для LXC — через GUI или `pct restore` (см. справку `pct restore`).
|
|
||||||
Для LXC типично: Backup → Restore → задать новый ID (например 999), node, storage.
|
|
||||||
4. Запустить восстановленный контейнер (ID 999), проверить:
|
|
||||||
- заходит по SSH/консоль;
|
|
||||||
- сервисы внутри запускаются (docker/ systemd);
|
|
||||||
- с хоста пинг и при необходимости один сервис по порту.
|
|
||||||
5. После проверки удалить тестовый контейнер (999), освободить место.
|
|
||||||
|
|
||||||
Если что-то пошло не так (не находится диск, ошибка прав, сеть) — зафиксировать и поправить стратегию (пути storage, режим backup, права).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Шаг 7. Документировать процедуру восстановления «с нуля»
|
|
||||||
|
|
||||||
Кратко зафиксировать в отдельном разделе (здесь или в architecture):
|
|
||||||
|
|
||||||
1. Установка Proxmox на новое железо (или новый диск).
|
|
||||||
2. Восстановление конфигов: распаковать последний `etc-pve-*.tar.gz` в `/etc/pve` (с учётом того, что нужно корректно подставить ноды и storage; при одномузловой установке обычно достаточно скопировать файлы).
|
|
||||||
3. Подключение storage с backup (или копирование последних vzdump на новый storage).
|
|
||||||
4. Восстановление контейнеров и ВМ из backup по одному (Restore с указанием VMID и storage).
|
|
||||||
5. Запуск контейнеров/ВМ, проверка сети и сервисов.
|
|
||||||
6. Использование сохранённых паролей/ключей для входа и проверки сервисов.
|
|
||||||
|
|
||||||
После первого успешного тестового восстановления (шаг 6) эту процедуру можно уточнить и дописать по факту.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Чек-лист фазы 1
|
|
||||||
|
|
||||||
- [x] Разметка: 1 ТБ на sdb1, ФС, монтирование в `/mnt/backup` (без LUKS). *(скрипт `scripts/backup-setup-sdb1-mount.sh`, каталоги созданы.)*
|
|
||||||
- [x] В Proxmox добавлен Storage для VZDump → `/mnt/backup/proxmox/dump`.
|
|
||||||
- [x] Настроена регулярная задача Backup: LXC (100–108), расписание ночь (02:00), retention задан. *VM 200 исключена из задания (образ ~380 ГБ); восстановление VM 200 — по инструкции «с нуля» в [backup-howto](backup-howto.md).*
|
|
||||||
- [x] Проверен ручной запуск Backup now — файлы появляются в storage. *(рекомендуется проверить разово.)*
|
|
||||||
- [x] Настроен бэкап `/etc/pve` (скрипт + cron) → `/mnt/backup/proxmox/etc-pve`. *(backup-etc-pve.sh, 02:15, 30 дней.)*
|
|
||||||
- [x] Restic: cron на хосте, выгрузка каталогов из `/mnt/backup` в Yandex S3. *(backup-restic-yandex.sh 04:00, backup-restic-yandex-photos.sh 04:10, retention 3 daily / 2 weekly / 2 monthly.)*
|
|
||||||
- [x] Yandex: ключи и endpoint зафиксированы в `/root/.restic-yandex.env`, restic пишет в бакет.
|
|
||||||
- [x] Vaultwarden развёрнут (CT 103).
|
|
||||||
- [ ] Секреты перенесены в Vaultwarden. *(на усмотрение: root PVE, пароли БД, API и т.д.)*
|
|
||||||
- [x] Бэкап данных Vaultwarden включён в restic (Yandex S3). *Локально: backup-vaultwarden-data.sh → `/mnt/backup/other/vaultwarden/`; restic выгружает весь `/mnt/backup` (кроме photos), каталог vaultwarden входит в снимок.*
|
|
||||||
- [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 и др.*
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Ссылки
|
|
||||||
|
|
||||||
- [Архитектура и подключение](../architecture/architecture.md) — хосты, IP, домены.
|
|
||||||
- [Схема сети и зависимости](../network/network-topology.md) — SPOF, зависимость от Proxmox и бэкапов.
|
|
||||||
- [Vaultwarden и использование секретов](../vaultwarden-secrets.md) — установка bw, разблокировка, получение секретов в скриптах.
|
|
||||||
- Документация контейнеров (100–108, 200) — бэкапы *данных внутри* сервисов (БД, тома); фаза 1 дополняет это бэкапом на уровне PVE.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Принятые решения (сводка)
|
|
||||||
|
|
||||||
| Вопрос | Решение |
|
|
||||||
|--------|---------|
|
|
||||||
| Точка монтирования, второй ТБ | `/mnt/backup`; второй ТБ на sdb1 — в запас, назначение позже. |
|
|
||||||
| Шифрование (LUKS) | Пока не делаем; раздел без шифрования. |
|
|
||||||
| Proxmox vzdump | Локально в `proxmox/dump` + дублировать в Yandex через restic. |
|
|
||||||
| Фотки | Оригиналы + метаданные + БД Immich; остальное пересчитать. |
|
|
||||||
| VPS | Amnezia — конфиг. Миран — БД бота + контент (контент можно в S3 Мирана; копия на sdb1 — по желанию). Конфиг серверов не бэкапим — Ansible. |
|
|
||||||
| Где запускать бэкапы | Cron на хосте Proxmox: Backup Job (vzdump) + restic в Yandex. |
|
|
||||||
| Retention локально | Только в Proxmox Job и в скрипте etc-pve; отдельного restic-репозитория локально не делаем. |
|
|
||||||
| /etc/pve + конфиги хоста (interfaces, hosts, resolv.conf) | Вариант A: cron на хосте → `etc-pve` и `etc-host-configs` в `/mnt/backup/proxmox/etc-pve`; локально и в Yandex (restic). |
|
|
||||||
| Пароли | Vaultwarden на CT 103. |
|
|
||||||
| VM 200 (БД PostgreSQL) | vzdump snapshot — для образа ВМ; консистентность БД — отдельно: внутри VM логический бэкап (`pg_dump`). |
|
|
||||||
| Yandex | Бакет создан; ключи и endpoint зафиксировать при настройке restic. |
|
|
||||||
| MinIO | Не используем; директории + restic (s3 для Yandex). |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Осталось сделать
|
|
||||||
|
|
||||||
- **Проверка ручного Backup:** один раз запустить «Backup now» в Proxmox UI (Datacenter → Backup) и убедиться, что файлы появляются в `/mnt/backup/proxmox/dump/dump/`.
|
|
||||||
- **Секреты (по желанию):** перенести пароли/ключи (root PVE, БД, API) в Vaultwarden и обновлять при смене.
|
|
||||||
|
|
||||||
*Выполнено ранее: Yandex + Restic (cron, retention 3/2/2), тестовое восстановление CT 107 → 999 (26.02.2026).*
|
|
||||||
@@ -18,9 +18,9 @@
|
|||||||
|
|
||||||
## Доступ и логины
|
## Доступ и логины
|
||||||
|
|
||||||
- **Debian (CT 100):** логин `root` (или консольный пользователь Debian), пароль `waccEk-fyqbux-rarja3`.
|
- **Debian (CT 100):** логин `root`. Пароль — в Vaultwarden (объект **CT_100_ROOT_PASSWORD**).
|
||||||
- **AdGuard Home (через домен):** https://adguard.katykhin.ru (через NPM, сертификат Let's Encrypt), пользователь `kerrad`, пароль `waccEk-fyqbux-rarja3`. Прямой доступ по порту 3000 больше не используется.
|
- **AdGuard Home (через домен):** https://adguard.katykhin.ru (через NPM, сертификат Let's Encrypt). Пользователь и пароль — в Vaultwarden (объект **ADGUARD**). Прямой доступ по порту 3000 больше не используется.
|
||||||
- **Nginx Proxy Manager:** http://192.168.1.100:81, имя `Kerrad`, email `j3tears100@gmail.com`, пароль `kqEUubVq02DJTS8`.
|
- **Nginx Proxy Manager:** http://192.168.1.100:81. Имя, email и пароль — в Vaultwarden (объект **NPM_ADMIN**).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@
|
|||||||
|
|
||||||
**Certbot на хосте (внутри CT 100):**
|
**Certbot на хосте (внутри CT 100):**
|
||||||
- Установлен в системе, таймер `certbot.timer` (проверка продления дважды в день).
|
- Установлен в системе, таймер `certbot.timer` (проверка продления дважды в день).
|
||||||
- Учётные данные Beget API: `/root/.secrets/certbot/beget.ini`.
|
- Учётные данные Beget API: `/root/.secrets/certbot/beget.ini` (генерируется из Vaultwarden скриптом `deploy-beget-credentials.sh` с хоста Proxmox).
|
||||||
- Deploy-hook’и: `/etc/letsencrypt/renewal-hooks/deploy/` — скрипты `copy-*-to-npm.sh` (video, docs, immich, mini-lm, vault и т.д.) копируют `fullchain.pem` и `privkey.pem` в соответствующий каталог `custom_ssl/npm-<id>/` и делают `docker exec npm nginx -s reload`.
|
- Deploy-hook’и: `/etc/letsencrypt/renewal-hooks/deploy/` — скрипты `copy-*-to-npm.sh` (video, docs, immich, mini-lm, vault и т.д.) копируют `fullchain.pem` и `privkey.pem` в соответствующий каталог `custom_ssl/npm-<id>/` и делают `docker exec npm nginx -s reload`.
|
||||||
|
|
||||||
**vault.katykhin.ru:** сертификат выпускается certbot’ом в `/etc/letsencrypt/live/vault.katykhin.ru/`, deploy-hook `copy-vault-to-npm.sh` копирует его в `custom_ssl/npm-18/`. В NPM у proxy host’а vault.katykhin.ru должен быть выбран именно этот сертификат (Custom SSL → каталог npm-18). Если в NPM по ошибке привязать другой сертификат (например от другого домена), браузер покажет ошибку «нет сертификата» или неверный домен; тогда в конфиге proxy host’а должны быть пути `ssl_certificate /data/custom_ssl/npm-18/...`.
|
**vault.katykhin.ru:** сертификат выпускается certbot’ом в `/etc/letsencrypt/live/vault.katykhin.ru/`, deploy-hook `copy-vault-to-npm.sh` копирует его в `custom_ssl/npm-18/`. В NPM у proxy host’а vault.katykhin.ru должен быть выбран именно этот сертификат (Custom SSL → каталог npm-18). Если в NPM по ошибке привязать другой сертификат (например от другого домена), браузер покажет ошибку «нет сертификата» или неверный домен; тогда в конфиге proxy host’а должны быть пути `ssl_certificate /data/custom_ssl/npm-18/...`.
|
||||||
@@ -181,14 +181,23 @@ docker restart wallos
|
|||||||
|
|
||||||
Проверяет, идут ли запросы к заданным доменам через VPN или через основное подключение (подключение к роутеру по telnet, разбор маршрутов). Результаты отдаёт на порту **8765** (на хосте). В Homepage добавлена ссылка на http://192.168.1.100:8765.
|
Проверяет, идут ли запросы к заданным доменам через VPN или через основное подключение (подключение к роутеру по telnet, разбор маршрутов). Результаты отдаёт на порту **8765** (на хосте). В Homepage добавлена ссылка на http://192.168.1.100:8765.
|
||||||
|
|
||||||
**Переменные окружения в compose:** `ROUTER_TELNET_HOST`, `ROUTER_TELNET_USER`, `ROUTER_TELNET_PASSWORD` — **заданы в самом файле** (не в .env). Рекомендация: вынести в `.env` и не коммитить пароль (см. TODO).
|
**Секреты:** `ROUTER_TELNET_HOST`, `ROUTER_TELNET_USER`, `ROUTER_TELNET_PASSWORD` берутся из Vaultwarden (объект **localhost**). Деплой — единым скриптом на Proxmox:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/root/scripts/deploy-vpn-route-check.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Скрипт: разблокирует bw, получает креды из Vaultwarden, атомарно пишет `.env` в CT 100, запускает `docker compose up -d`. Режим проверки без записи: `--dry-run`. Шаблон compose: `scripts/vpn-route-check/docker-compose.yml`.
|
||||||
|
|
||||||
**Том:** volume `vpn-route-check-data` → `/data` (в контейнере).
|
**Том:** volume `vpn-route-check-data` → `/data` (в контейнере).
|
||||||
|
|
||||||
**Команды:**
|
**Команды:**
|
||||||
```bash
|
```bash
|
||||||
cd /opt/docker/vpn-route-check && docker compose up -d
|
# Деплой (с хоста Proxmox)
|
||||||
docker logs vpn-route-check
|
/root/scripts/deploy-vpn-route-check.sh
|
||||||
|
|
||||||
|
# Логи
|
||||||
|
pct exec 100 -- docker logs vpn-route-check
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -224,7 +233,7 @@ docker logs vpn-route-check
|
|||||||
1. Создать сеть (если ещё нет): `docker network create proxy_network`.
|
1. Создать сеть (если ещё нет): `docker network create proxy_network`.
|
||||||
2. NPM: `cd /opt/docker/nginx-proxy && docker compose up -d`.
|
2. NPM: `cd /opt/docker/nginx-proxy && docker compose up -d`.
|
||||||
3. AdGuard: `cd /opt/docker/adguard && docker compose up -d` (создаёт свою сеть и подключается к proxy_network).
|
3. AdGuard: `cd /opt/docker/adguard && docker compose up -d` (создаёт свою сеть и подключается к proxy_network).
|
||||||
4. VPN Route Check: `cd /opt/docker/vpn-route-check && docker compose up -d`.
|
4. VPN Route Check: `/root/scripts/deploy-vpn-route-check.sh` (с хоста Proxmox).
|
||||||
5. Log-dashboard: при необходимости запустить контейнер с монтом html и портом 8088.
|
5. Log-dashboard: при необходимости запустить контейнер с монтом html и портом 8088.
|
||||||
|
|
||||||
После изменений в NPM (proxy, SSL): перезагрузка nginx внутри контейнера — `docker exec npm nginx -s reload`. Certbot продлевает сертификаты по таймеру; deploy-hook’и копируют их в NPM и перезагружают nginx.
|
После изменений в NPM (proxy, SSL): перезагрузка nginx внутри контейнера — `docker exec npm nginx -s reload`. Certbot продлевает сертификаты по таймеру; deploy-hook’и копируют их в NPM и перезагружают nginx.
|
||||||
@@ -234,7 +243,7 @@ docker logs vpn-route-check
|
|||||||
## Уязвимости и риски
|
## Уязвимости и риски
|
||||||
|
|
||||||
1. **Пароли и креды в конфигах:** В `services.yaml` (Homepage) хранятся пароли виджетов (AdGuard, NPM, Proxmox). Файл лежит только на сервере; не помещать в публичный репозиторий.
|
1. **Пароли и креды в конфигах:** В `services.yaml` (Homepage) хранятся пароли виджетов (AdGuard, NPM, Proxmox). Файл лежит только на сервере; не помещать в публичный репозиторий.
|
||||||
2. **VPN Route Check:** Логин и пароль роутера прописаны в `docker-compose.yml`. Доступ к compose = доступ к роутеру. Рекомендуется вынести в `.env` и ограничить права на файл.
|
2. **VPN Route Check:** Креды роутера в `.env` (генерируется из Vaultwarden скриптом `deploy-vpn-route-check.sh`). Файл `.env` не коммитить.
|
||||||
3. **AdGuard на 3000:** Веб-интерфейс доступен по порту 3000 на хосте. Доступ из LAN; при необходимости закрыть фаерволом снаружи или использовать только через NPM (proxy).
|
3. **AdGuard на 3000:** Веб-интерфейс доступен по порту 3000 на хосте. Доступ из LAN; при необходимости закрыть фаерволом снаружи или использовать только через NPM (proxy).
|
||||||
4. **NPM на 81:** Админка NPM по порту 81. Убедиться, что с интернета доступ только через VPN или не пробрасывать 81 наружу.
|
4. **NPM на 81:** Админка NPM по порту 81. Убедиться, что с интернета доступ только через VPN или не пробрасывать 81 наружу.
|
||||||
5. **Логи NPM:** Часть логов (fallback_*) не ротируется — возможен рост и заполнение диска (см. TODO).
|
5. **Логи NPM:** Часть логов (fallback_*) не ротируется — возможен рост и заполнение диска (см. TODO).
|
||||||
@@ -245,7 +254,7 @@ docker logs vpn-route-check
|
|||||||
|
|
||||||
- [x] **Логи NPM:** Добавить в logrotate ротацию для `fallback_http_access.log`, `fallback_http_error.log` (и при необходимости других fallback_*) по размеру или по дням — настроено в `npm-nginx.conf` (30 дней / ~512 MB).
|
- [x] **Логи NPM:** Добавить в logrotate ротацию для `fallback_http_access.log`, `fallback_http_error.log` (и при необходимости других fallback_*) по размеру или по дням — настроено в `npm-nginx.conf` (30 дней / ~512 MB).
|
||||||
- [x] **Логи AdGuard:** Ограничить хранение логов запросов/статистики — настроено в `AdGuardHome.yaml` (`querylog.interval = 336h`, `statistics.interval = 336h` ≈ 14 дней).
|
- [x] **Логи AdGuard:** Ограничить хранение логов запросов/статистики — настроено в `AdGuardHome.yaml` (`querylog.interval = 336h`, `statistics.interval = 336h` ≈ 14 дней).
|
||||||
- [ ] **VPN Route Check:** Вынести `ROUTER_TELNET_*` в `.env`, подключать в compose через `env_file`, не коммитить .env в репозиторий.
|
- [x] **VPN Route Check:** Секреты из Vaultwarden (объект localhost), деплой через `deploy-vpn-route-check.sh`.
|
||||||
- [ ] **Log-dashboard:** Зафиксировать способ запуска контейнера (отдельный compose или скрипт) и добавить его в документацию/автозапуск при перезагрузке CT.
|
- [ ] **Log-dashboard:** Зафиксировать способ запуска контейнера (отдельный compose или скрипт) и добавить его в документацию/автозапуск при перезагрузке CT.
|
||||||
- [ ] **Мониторинг диска:** Настроить оповещение (например, из Prometheus/Alertmanager или скрипт по крону) при заполнении корня или `/opt/docker` выше порога (например 80%).
|
- [ ] **Мониторинг диска:** Настроить оповещение (например, из Prometheus/Alertmanager или скрипт по крону) при заполнении корня или `/opt/docker` выше порога (например 80%).
|
||||||
- [ ] **Резервное копирование:** Регулярный бэкап критичных папок (оценка размеров на момент документации):
|
- [ ] **Резервное копирование:** Регулярный бэкап критичных папок (оценка размеров на момент документации):
|
||||||
|
|||||||
@@ -43,20 +43,22 @@
|
|||||||
**Порты:** 3000 (хост) → 3000 (контейнер). NPM (контейнер 100) проксирует https://video.katykhin.ru → 192.168.1.107:3000.
|
**Порты:** 3000 (хост) → 3000 (контейнер). NPM (контейнер 100) проксирует https://video.katykhin.ru → 192.168.1.107:3000.
|
||||||
|
|
||||||
**Тома и конфиги:**
|
**Тома и конфиги:**
|
||||||
- Invidious не использует отдельные bind‑тома для конфигов/данных — данные хранятся в PostgreSQL (`invidious_postgresdata`), а конфиг задаётся через переменную `INVIDIOUS_CONFIG` в compose (inline YAML).
|
- Invidious не использует отдельные bind‑тома для конфигов/данных — данные хранятся в PostgreSQL (`invidious_postgresdata`), а конфиг задаётся через переменную `INVIDIOUS_CONFIG` в compose.
|
||||||
- Отдельных каталогов с логами Invidious на хосте нет — логи идут в stdout контейнера (см. раздел «Логи и ротация»).
|
- Отдельных каталогов с логами Invidious на хосте нет — логи идут в stdout контейнера (см. раздел «Логи и ротация»).
|
||||||
|
|
||||||
**Основная конфигурация (в docker-compose.yml, секция `environment / INVIDIOUS_CONFIG`):**
|
**Секреты:** `POSTGRES_USER`, `POSTGRES_PASSWORD`, `INVIDIOUS_COMPANION_KEY`, `HMAC_KEY` берутся из Vaultwarden (объект **INVIDIOUS**). Деплой с хоста Proxmox:
|
||||||
- `db`: dbname=invidious, user=kemal, password=kemal, host=invidious-db, port=5432, check_tables=true.
|
```bash
|
||||||
- `invidious_companion`: URL сервиса companion (`http://companion:8282/companion`).
|
/root/scripts/deploy-invidious-credentials.sh
|
||||||
- `invidious_companion_key` и `SERVER_SECRET_KEY` (в companion) — общий секрет между Invidious и Companion (сейчас заданы прямо в compose; **не выкладывать в публичный репозиторий**).
|
```
|
||||||
- `external_port: 443`, `domain: "video.katykhin.ru"`, `https_only: true` — Invidious знает про внешний домен и порт, отдаёт ссылки на https.
|
Скрипт генерирует `.env` из Vaultwarden, атомарно пушит в CT 107, запускает `docker compose up -d --force-recreate`. **Ротация:** сменил пароль/ключи в Vaultwarden → запустил скрипт.
|
||||||
- Прочие опции (feeds, captions, hmac_key, default_user_preferences и т.д.).
|
|
||||||
|
|
||||||
**Команды:**
|
**Команды:**
|
||||||
```bash
|
```bash
|
||||||
cd /opt/invidious && docker compose up -d
|
# Деплой (с хоста Proxmox)
|
||||||
docker logs invidious-invidious-1
|
/root/scripts/deploy-invidious-credentials.sh
|
||||||
|
|
||||||
|
# Логи
|
||||||
|
pct exec 107 -- docker logs invidious-invidious-1
|
||||||
curl -s http://127.0.0.1:3000/api/v1/stats
|
curl -s http://127.0.0.1:3000/api/v1/stats
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -71,7 +73,7 @@ curl -s http://127.0.0.1:3000/api/v1/stats
|
|||||||
- volume `companioncache` → `/var/tmp/youtubei.js` (кэш js‑ресурсов YouTube / youtubei).
|
- volume `companioncache` → `/var/tmp/youtubei.js` (кэш js‑ресурсов YouTube / youtubei).
|
||||||
|
|
||||||
**Безопасность:**
|
**Безопасность:**
|
||||||
- `SERVER_SECRET_KEY` совпадает с `invidious_companion_key` в конфиге Invidious — это shared secret для обмена.
|
- `SERVER_SECRET_KEY` совпадает с `invidious_companion_key` — оба берутся из `.env` (генерируется из Vaultwarden).
|
||||||
- Контейнер запущен с `read_only: true`, `cap_drop: [ALL]`, `no-new-privileges:true` — хорошая практика sandboxing.
|
- Контейнер запущен с `read_only: true`, `cap_drop: [ALL]`, `no-new-privileges:true` — хорошая практика sandboxing.
|
||||||
|
|
||||||
**Команды:**
|
**Команды:**
|
||||||
@@ -89,7 +91,7 @@ docker logs invidious-companion-1
|
|||||||
- `/opt/invidious/config/sql` → `/config/sql` — SQL‑скрипты инициализации/миграций из репозитория Invidious (~40 KB).
|
- `/opt/invidious/config/sql` → `/config/sql` — SQL‑скрипты инициализации/миграций из репозитория Invidious (~40 KB).
|
||||||
- `/opt/invidious/docker/init-invidious-db.sh` → `/docker-entrypoint-initdb.d/init-invidious-db.sh` — скрипт инициализации БД при первом запуске.
|
- `/opt/invidious/docker/init-invidious-db.sh` → `/docker-entrypoint-initdb.d/init-invidious-db.sh` — скрипт инициализации БД при первом запуске.
|
||||||
|
|
||||||
**Переменные окружения:** POSTGRES_DB=invidious, POSTGRES_USER=kemal, POSTGRES_PASSWORD=kemal (заданы в compose; не публиковать).
|
**Переменные окружения:** из `.env` (генерируется `deploy-invidious-credentials.sh` из Vaultwarden).
|
||||||
|
|
||||||
**Команды:**
|
**Команды:**
|
||||||
```bash
|
```bash
|
||||||
@@ -124,16 +126,10 @@ Companion и PostgreSQL доступны только внутри docker-сет
|
|||||||
|
|
||||||
## Запуск и порядок поднятия
|
## Запуск и порядок поднятия
|
||||||
|
|
||||||
1. Зайти в каталог: `cd /opt/invidious`.
|
1. С хоста Proxmox: `/root/scripts/deploy-invidious-credentials.sh` (генерирует `.env` из Vaultwarden, пушит в CT 107, запускает compose).
|
||||||
2. Проверить/при необходимости подредактировать `docker-compose.yml` (секция `INVIDIOUS_CONFIG`, домен video.katykhin.ru, секреты).
|
2. Порядок: `invidious-db` → `invidious` (depends_on с healthcheck), параллельно Companion.
|
||||||
3. Запуск/перезапуск:
|
|
||||||
```bash
|
|
||||||
docker compose up -d
|
|
||||||
```
|
|
||||||
Порядок: сначала поднимается `invidious-db`, затем `invidious` (depends_on с healthcheck), параллельно Companion.
|
|
||||||
|
|
||||||
После изменения конфигурации (секция `INVIDIOUS_CONFIG` или окружения Companion/DB):
|
После изменения секретов в Vaultwarden: запустить `deploy-invidious-credentials.sh` снова.
|
||||||
`cd /opt/invidious && docker compose up -d` — конфигурация применяется при перезапуске контейнеров.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
|
|
||||||
## Доступ и логины
|
## Доступ и логины
|
||||||
|
|
||||||
- **Debian (CT 108):** логин `root`, пароль `Galene108!`.
|
- **Debian (CT 108):** логин `root`. Пароль — в Vaultwarden (объект **CT_108_ROOT_PASSWORD**).
|
||||||
- **Galene (веб):** https://call.katykhin.ru (через NPM → 192.168.1.108:8443). Вход в группы — по паролям, заданным в конфигах групп в `/opt/galene-data/groups/` (операторы и участники).
|
- **Galene (веб):** https://call.katykhin.ru (через NPM → 192.168.1.108:8443). Вход в группы — по паролям, заданным в конфигах групп в `/opt/galene-data/groups/` (операторы и участники).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -41,6 +41,12 @@
|
|||||||
chmod 600 /root/.secrets/certbot/beget.ini
|
chmod 600 /root/.secrets/certbot/beget.ini
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Homelab (Vaultwarden):** креды хранятся в Vaultwarden (объект **beget**). Деплой с хоста Proxmox:
|
||||||
|
```bash
|
||||||
|
/root/scripts/deploy-beget-credentials.sh
|
||||||
|
```
|
||||||
|
Скрипт генерирует `beget.ini` из Vaultwarden, атомарно пушит в CT 100, ставит права 600 и pre-hook проверки. **Ротация:** сменил пароль в Vaultwarden → запустил `deploy-beget-credentials.sh` → готово.
|
||||||
|
|
||||||
3. **Запрос сертификата:**
|
3. **Запрос сертификата:**
|
||||||
```bash
|
```bash
|
||||||
certbot certonly \
|
certbot certonly \
|
||||||
|
|||||||
@@ -191,6 +191,53 @@ TELEGRAM_CHAT_ID=$(bw get item "RESTIC" | jq -r '.fields[] | select(.name=="TELE
|
|||||||
# Дальше: curl к Telegram API с TELEGRAM_BOT_TOKEN и TELEGRAM_CHAT_ID
|
# Дальше: curl к Telegram API с TELEGRAM_BOT_TOKEN и TELEGRAM_CHAT_ID
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Пример: Beget (certbot DNS-01, CT 100)
|
||||||
|
|
||||||
|
Скрипт `deploy-beget-credentials.sh` на Proxmox генерирует `beget.ini` из объекта **beget** (username → `dns_beget_api_username`, password → `dns_beget_api_password`), атомарно пушит в CT 100 (`beget.ini.tmp` → `mv` → `beget.ini`), ставит chmod 600. Pre-hook certbot проверяет наличие файла и права перед каждым renew. **Ротация:** сменил пароль в Vaultwarden → `deploy-beget-credentials.sh` → готово.
|
||||||
|
|
||||||
|
### Пример: Invidious (CT 107)
|
||||||
|
|
||||||
|
Скрипт `deploy-invidious-credentials.sh` генерирует `.env` из объекта **INVIDIOUS** (username, password, поля `SERVER_SECRET_KEY`, `HMAC_KEY`), атомарно пушит в CT 107, запускает `docker compose up -d --force-recreate`. **Ротация:** сменил пароль/ключи в Vaultwarden → запустил скрипт.
|
||||||
|
|
||||||
|
### Пример: Paperless (CT 104)
|
||||||
|
|
||||||
|
Скрипт `deploy-paperless-credentials.sh` генерирует `docker-compose.env` из объекта **PAPERLESS** (password = POSTGRES_PASSWORD; поля `PAPERLESS_URL`, `PAPERLESS_SECRET_KEY`, `PAPERLESS_TIME_ZONE`, `PAPERLESS_OCR_LANGUAGE`, `PAPERLESS_OCR_LANGUAGES`), пушит compose и env в CT 104, запускает `docker compose up -d --force-recreate`. **Ротация:** сменил пароль/ключи в Vaultwarden → запустил скрипт.
|
||||||
|
|
||||||
|
### Пример: RAG-service (CT 105)
|
||||||
|
|
||||||
|
Скрипт `deploy-rag-credentials.sh` генерирует `.env` из объекта **RAG_SERVICE** (поле `RAG_API_KEY`), атомарно пушит в CT 105, запускает `docker compose up -d --force-recreate`. **Перед первым запуском:** создать в Vaultwarden запись **RAG_SERVICE** (тип Login), добавить кастомное поле `RAG_API_KEY` (hidden) с текущим ключом из `/home/rag-service/.env`. **Ротация:** сменил ключ в Vaultwarden → запустил скрипт.
|
||||||
|
|
||||||
|
### Пример: Gitea (CT 103)
|
||||||
|
|
||||||
|
Скрипт `deploy-gitea-credentials.sh` генерирует `.env` из объекта **GITEA** (password = POSTGRES_PASSWORD; поле `GITEA_RUNNER_REGISTRATION_TOKEN`), пушит compose и env в CT 103, запускает `docker compose up -d --force-recreate`. **Ротация:** сменил пароль/токен в Vaultwarden → запустил скрипт.
|
||||||
|
|
||||||
|
### Пример: Nextcloud (CT 101)
|
||||||
|
|
||||||
|
Скрипт `deploy-nextcloud-credentials.sh` генерирует `.env` и `docker-compose.yml` из объекта **NEXTCLOUD** (password = POSTGRES_PASSWORD; поля `dbpassword`, `secret`, `passwordsalt`, `instanceid`), пушит в CT 101, обновляет config.php через occ, запускает compose. **Ротация:** сменил в Vaultwarden → запустил скрипт.
|
||||||
|
|
||||||
|
### Пример: Galene (CT 108)
|
||||||
|
|
||||||
|
Скрипт `deploy-galene-credentials.sh` берёт поле `config` (JSON ice-servers) из объекта **GALENE**, записывает в `/opt/galene-data/data/ice-servers.json`, перезапускает `galene.service`. **Ротация:** сменил TURN username/credential в Vaultwarden → запустил скрипт.
|
||||||
|
|
||||||
|
### Пример: Immich (VM 200)
|
||||||
|
|
||||||
|
Скрипт `deploy-immich-credentials.sh` генерирует `.env` для Immich и immich-deduper из объектов **IMMICH** и **IMMICH_DEDUPER**, пушит по SSH на VM 200, запускает compose. **Требования:** SSH без пароля root@Proxmox → admin@192.168.1.200. **Ротация:** сменил в Vaultwarden → запустил скрипт.
|
||||||
|
|
||||||
|
### Пример: WireGuard (CT 109)
|
||||||
|
|
||||||
|
Скрипт `deploy-wireguard-credentials.sh` берёт поле `wg0_conf` (полный конфиг) из объекта **LOCAL_VPN_SERVER_WG**, записывает в `/etc/wireguard/wg0.conf`, перезапускает `wg-quick@wg0`. **Перед первым запуском:** создать в Vaultwarden запись **LOCAL_VPN_SERVER_WG**, добавить кастомное поле `wg0_conf` (hidden) с содержимым текущего `/etc/wireguard/wg0.conf` (скопировать с CT 109). **Ротация:** сменил ключи в Vaultwarden → запустил скрипт.
|
||||||
|
|
||||||
|
### Пример: VPN Route Check (деплой с Proxmox в CT 100)
|
||||||
|
|
||||||
|
Скрипт `deploy-vpn-route-check.sh` на хосте Proxmox:
|
||||||
|
|
||||||
|
1. Разблокирует bw (или переиспользует сессию).
|
||||||
|
2. Получает из объекта **localhost**: `ROUTER_TELNET_HOST` (кастомное поле), `ROUTER_TELNET_USER` (username), `ROUTER_TELNET_PASSWORD` (password).
|
||||||
|
3. Генерирует `.env` во временный файл, атомарно (`mv .env.tmp .env`) пушит в CT 100.
|
||||||
|
4. Запускает `docker compose up -d` в каталоге vpn-route-check.
|
||||||
|
|
||||||
|
Режим проверки без записи: `deploy-vpn-route-check.sh --dry-run`. Подробнее: [Контейнер 100](containers/container-100.md#7-vpn-route-check).
|
||||||
|
|
||||||
### Fallback на старые конфиги
|
### Fallback на старые конфиги
|
||||||
|
|
||||||
Если Vaultwarden недоступен или разблокировка не удалась, скрипты могут загружать креды из прежних файлов (например `/root/.telegram-notify.env`, `/root/.restic-yandex.env`). Так можно обеспечить работу бэкапов даже при временной недоступности vault.
|
Если Vaultwarden недоступен или разблокировка не удалась, скрипты могут загружать креды из прежних файлов (например `/root/.telegram-notify.env`, `/root/.restic-yandex.env`). Так можно обеспечить работу бэкапов даже при временной недоступности vault.
|
||||||
@@ -208,14 +255,33 @@ TELEGRAM_CHAT_ID=$(bw get item "RESTIC" | jq -r '.fields[] | select(.name=="TELE
|
|||||||
|
|
||||||
## Инвентаризация записей и полей
|
## Инвентаризация записей и полей
|
||||||
|
|
||||||
В Vaultwarden удобно хранить записи с именами, совпадающими с сервисами: **RESTIC**, **GITEA**, **PAPERLESS**, **NEXTCLOUD**, **HOME_BOT_TOKEN**, **VAULTWARDEN**, **MIRAN_S3** и т.д. У записей типа «логин» — логин/пароль; у записей с множеством значений — кастомные поля (например `RESTIC_REPOSITORY`, `AWS_ACCESS_KEY_ID`).
|
В Vaultwarden удобно хранить записи с именами, совпадающими с сервисами: **RESTIC**, **GITEA**, **PAPERLESS**, **NEXTCLOUD**, **HOME_BOT_TOKEN**, **VAULTWARDEN**, **MIRAN_S3**, **RAG_SERVICE**, **ADGUARD**, **NPM_ADMIN** и т.д. У записей типа «логин» — логин/пароль; у записей с множеством значений — кастомные поля (например `RESTIC_REPOSITORY`, `AWS_ACCESS_KEY_ID`, `RAG_API_KEY`).
|
||||||
|
|
||||||
Полная таблица «где лежат креды сейчас → какой объект в Vaultwarden» и готовые команды `bw get ...` / `jq` по каждому объекту описаны в [Фаза 1: Стратегия бэкапов](backup/proxmox-phase1-backup.md) — разделы «Инвентаризация секретов для переноса в Vaultwarden», «Получение секретов из Vaultwarden» и «Переключение скриптов на секреты из Vaultwarden».
|
**ADGUARD** — веб-интерфейс AdGuard Home (https://adguard.katykhin.ru): username = логин администратора, password = пароль. Тип записи: Login.
|
||||||
|
|
||||||
|
**NPM_ADMIN** — админка Nginx Proxy Manager (http://192.168.1.100:81): username = email (используется как identity при входе), password = пароль. Тип записи: Login. Скрипты `npm-add-proxy.sh`, `npm-add-proxy-vault.sh` используют `NPM_EMAIL` и `NPM_PASSWORD` — брать из этого объекта.
|
||||||
|
|
||||||
|
### Команды bw по объектам (для скриптов бэкапов и деплоя)
|
||||||
|
|
||||||
|
| Объект | Логин / пароль | Кастомные поля |
|
||||||
|
|--------|----------------|----------------|
|
||||||
|
| **ADGUARD** | `bw get username "ADGUARD"`, `bw get password "ADGUARD"` | — |
|
||||||
|
| **beget** | `bw get username "beget"`, `bw get password "beget"` | — |
|
||||||
|
| **GALENE** | — | `bw get item "GALENE" \| jq -r '.fields[] \| select(.name=="config") \| .value'` |
|
||||||
|
| **GITEA** | `bw get username "GITEA"`, `bw get password "GITEA"` | GITEA_RUNNER_REGISTRATION_TOKEN и др. |
|
||||||
|
| **HOME_BOT_TOKEN** | — | пароль = токен: `bw get password "HOME_BOT_TOKEN"` |
|
||||||
|
| **localhost** | `bw get username "localhost"`, `bw get password "localhost"` | ROUTER_TELNET_HOST |
|
||||||
|
| **NEXTCLOUD** | `bw get username "NEXTCLOUD"`, `bw get password "NEXTCLOUD"` | dbpassword, secret, passwordsalt, instanceid |
|
||||||
|
| **NPM_ADMIN** | username = email, `bw get password "NPM_ADMIN"` | — |
|
||||||
|
| **PAPERLESS** | `bw get password "PAPERLESS"` (= POSTGRES_PASSWORD) | PAPERLESS_SECRET_KEY, PAPERLESS_URL и др. |
|
||||||
|
| **RESTIC** | — | RESTIC_BACKUP_KEY, RESTIC_REPOSITORY, AWS_*, TELEGRAM_SELF_CHAT_ID |
|
||||||
|
| **VAULTWARDEN** | — | пароль = ADMIN_TOKEN: `bw get password "VAULTWARDEN"` |
|
||||||
|
|
||||||
|
Универсальный шаблон для поля: `bw get item "ИМЯ" | jq -r '.fields[] | select(.name=="ПОЛЕ") | .value'`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## См. также
|
## См. также
|
||||||
|
|
||||||
- [Контейнер 103 (Gitea, Vaultwarden)](containers/container-103.md) — развёртывание Vaultwarden, порты, домен, NPM.
|
- [Контейнер 103 (Gitea, Vaultwarden)](containers/container-103.md) — развёртывание Vaultwarden, порты, домен, NPM.
|
||||||
- [Фаза 1: Стратегия бэкапов](backup/proxmox-phase1-backup.md) — инвентаризация секретов, команды по объектам, переключение скриптов на Vaultwarden.
|
|
||||||
- [backup-howto](backup/backup-howto.md) — общий план бэкапов и восстановления, в том числе данных Vaultwarden.
|
- [backup-howto](backup/backup-howto.md) — общий план бэкапов и восстановления, в том числе данных Vaultwarden.
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ VPS в ЦОД Миран (Санкт-Петербург). Развёрнуты
|
|||||||
## Доступ и логины
|
## Доступ и логины
|
||||||
|
|
||||||
- **SSH:** `ssh -p 15722 deploy@185.147.80.190` (пользователь deploy, в группе docker). IP: 185.147.80.190, хостнейм vm220416.vds.miran.ru, ОС Ubuntu.
|
- **SSH:** `ssh -p 15722 deploy@185.147.80.190` (пользователь deploy, в группе docker). IP: 185.147.80.190, хостнейм vm220416.vds.miran.ru, ОС Ubuntu.
|
||||||
- **S3 (контент ботов):** URL https://api.s3.miran.ru, порт 443. Access key: `j3tears100@gmail.com`, Secret key: `wQ1-6sZEPs92sbZTSf96` (полная таблица — в разделе «S3» ниже).
|
- **S3 (контент ботов):** URL https://api.s3.miran.ru, порт 443. Access key и Secret key — в Vaultwarden (объект **MIRAN_S3**).
|
||||||
- **Админка Миран (панель хостинга VPS):** логин `j3tears100@gmail.com`, пароль `gonPok-xifrys-4nuxde`.
|
- **Админка Миран (панель хостинга VPS):** логин и пароль — в Vaultwarden (отдельная запись для панели Миран).
|
||||||
- **Grafana, Uptime Kuma, админки ботов:** логины и пароли — в `.env` проекта prod или в менеджере паролей.
|
- **Grafana, Uptime Kuma, админки ботов:** логины и пароли — в `.env` проекта prod или в менеджере паролей.
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -50,8 +50,8 @@ VPS в ЦОД Миран (Санкт-Петербург). Развёрнуты
|
|||||||
|-------------|----------|
|
|-------------|----------|
|
||||||
| URL | https://api.s3.miran.ru |
|
| URL | https://api.s3.miran.ru |
|
||||||
| Порт | 443 (HTTPS) |
|
| Порт | 443 (HTTPS) |
|
||||||
| Access key | j3tears100@gmail.com |
|
| Access key | см. Vaultwarden, объект **MIRAN_S3** |
|
||||||
| Secret key | wQ1-6sZEPs92sbZTSf96 |
|
| Secret key | см. Vaultwarden, объект **MIRAN_S3** |
|
||||||
|
|
||||||
В ботаx (переменные окружения prod) заданы `S3_ENDPOINT_URL=https://api.s3.miran.ru`, регион и креды для загрузки/выдачи контента. Для локальной разработки или других клиентов использовать те же endpoint и ключи.
|
В ботаx (переменные окружения prod) заданы `S3_ENDPOINT_URL=https://api.s3.miran.ru`, регион и креды для загрузки/выдачи контента. Для локальной разработки или других клиентов использовать те же endpoint и ключи.
|
||||||
|
|
||||||
@@ -119,12 +119,6 @@ docker compose logs -f telegram-bot
|
|||||||
**Что нужно на Proxmox:**
|
**Что нужно на Proxmox:**
|
||||||
|
|
||||||
- **SSH:** с хоста (root) должен работать вход без пароля на `deploy@185.147.80.190 -p 15722` (добавить публичный ключ хоста в `~/.ssh/authorized_keys` пользователя deploy на VPS).
|
- **SSH:** с хоста (root) должен работать вход без пароля на `deploy@185.147.80.190 -p 15722` (добавить публичный ключ хоста в `~/.ssh/authorized_keys` пользователя deploy на VPS).
|
||||||
- **S3:** установить `awscli` (`apt install awscli`) и создать файл `/root/.vps-miran-s3.env` с содержимым (подставить свои креды):
|
- **S3:** установить `awscli` (`apt install awscli`). Креды S3 — в Vaultwarden (объект **MIRAN_S3**). Файл `/root/.vps-miran-s3.env` с `S3_ACCESS_KEY`, `S3_SECRET_KEY`, `S3_BUCKET_NAME` генерируется скриптами или создаётся вручную из Vaultwarden. Файл читается только root; в репозиторий не коммитить.
|
||||||
```bash
|
|
||||||
S3_ACCESS_KEY=j3tears100@gmail.com
|
|
||||||
S3_SECRET_KEY=...
|
|
||||||
S3_BUCKET_NAME=9829-telegram-helper-bot
|
|
||||||
```
|
|
||||||
Файл читается только root; в репозиторий не коммитить.
|
|
||||||
|
|
||||||
Подробности и восстановление — в [Бэкапы: как устроены и как восстанавливать](../backup/backup-howto.md).
|
Подробности и восстановление — в [Бэкапы: как устроены и как восстанавливать](../backup/backup-howto.md).
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ OUTPUT="$BACKUP_DIR/nextcloud-db-$DATE.sql.gz"
|
|||||||
ERR=$(mktemp)
|
ERR=$(mktemp)
|
||||||
trap "rm -f '$ERR'" EXIT
|
trap "rm -f '$ERR'" EXIT
|
||||||
|
|
||||||
# PGPASSWORD из Vaultwarden (объект NEXTCLOUD: поле dbpassword или пароль). См. docs/backup/proxmox-phase1-backup.md
|
# PGPASSWORD из Vaultwarden (объект NEXTCLOUD: поле dbpassword или пароль). См. docs/vaultwarden-secrets.md
|
||||||
PG_ENV_ARGS=""
|
PG_ENV_ARGS=""
|
||||||
BW_MASTER_PASSWORD_FILE="${BW_MASTER_PASSWORD_FILE:-/root/.bw-master}"
|
BW_MASTER_PASSWORD_FILE="${BW_MASTER_PASSWORD_FILE:-/root/.bw-master}"
|
||||||
if [ -f "$BW_MASTER_PASSWORD_FILE" ] && command -v bw >/dev/null 2>&1 && command -v jq >/dev/null 2>&1; then
|
if [ -f "$BW_MASTER_PASSWORD_FILE" ] && command -v bw >/dev/null 2>&1 && command -v jq >/dev/null 2>&1; then
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ OUTPUT="$BACKUP_DIR/gitea-db-$DATE.sql.gz"
|
|||||||
ERR=$(mktemp)
|
ERR=$(mktemp)
|
||||||
trap "rm -f '$ERR'" EXIT
|
trap "rm -f '$ERR'" EXIT
|
||||||
|
|
||||||
# PGPASSWORD из Vaultwarden (объект GITEA, пароль). См. docs/backup/proxmox-phase1-backup.md
|
# PGPASSWORD из Vaultwarden (объект GITEA, пароль). См. docs/vaultwarden-secrets.md
|
||||||
PG_ENV_ARGS=""
|
PG_ENV_ARGS=""
|
||||||
BW_MASTER_PASSWORD_FILE="${BW_MASTER_PASSWORD_FILE:-/root/.bw-master}"
|
BW_MASTER_PASSWORD_FILE="${BW_MASTER_PASSWORD_FILE:-/root/.bw-master}"
|
||||||
if [ -f "$BW_MASTER_PASSWORD_FILE" ] && command -v bw >/dev/null 2>&1; then
|
if [ -f "$BW_MASTER_PASSWORD_FILE" ] && command -v bw >/dev/null 2>&1; then
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ OUTPUT="$BACKUP_DIR/paperless-db-$DATE.sql.gz"
|
|||||||
ERR=$(mktemp)
|
ERR=$(mktemp)
|
||||||
trap "rm -f '$ERR'" EXIT
|
trap "rm -f '$ERR'" EXIT
|
||||||
|
|
||||||
# PGPASSWORD из Vaultwarden (объект PAPERLESS, пароль). См. docs/backup/proxmox-phase1-backup.md
|
# PGPASSWORD из Vaultwarden (объект PAPERLESS, пароль). См. docs/vaultwarden-secrets.md
|
||||||
PG_ENV_ARGS=""
|
PG_ENV_ARGS=""
|
||||||
BW_MASTER_PASSWORD_FILE="${BW_MASTER_PASSWORD_FILE:-/root/.bw-master}"
|
BW_MASTER_PASSWORD_FILE="${BW_MASTER_PASSWORD_FILE:-/root/.bw-master}"
|
||||||
if [ -f "$BW_MASTER_PASSWORD_FILE" ] && command -v bw >/dev/null 2>&1; then
|
if [ -f "$BW_MASTER_PASSWORD_FILE" ] && command -v bw >/dev/null 2>&1; then
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ fi
|
|||||||
# Секреты из Vaultwarden (объект RESTIC)
|
# Секреты из Vaultwarden (объект RESTIC)
|
||||||
BW_MASTER_PASSWORD_FILE="${BW_MASTER_PASSWORD_FILE:-/root/.bw-master}"
|
BW_MASTER_PASSWORD_FILE="${BW_MASTER_PASSWORD_FILE:-/root/.bw-master}"
|
||||||
if [ ! -f "$BW_MASTER_PASSWORD_FILE" ]; then
|
if [ ! -f "$BW_MASTER_PASSWORD_FILE" ]; then
|
||||||
echo "Нет файла с мастер-паролем Vaultwarden: $BW_MASTER_PASSWORD_FILE (chmod 600). См. docs/backup/proxmox-phase1-backup.md"
|
echo "Нет файла с мастер-паролем Vaultwarden: $BW_MASTER_PASSWORD_FILE (chmod 600). См. docs/vaultwarden-secrets.md"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
if ! command -v bw >/dev/null 2>&1 || ! command -v jq >/dev/null 2>&1; then
|
if ! command -v bw >/dev/null 2>&1 || ! command -v jq >/dev/null 2>&1; then
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ fi
|
|||||||
# Секреты из Vaultwarden (объект RESTIC)
|
# Секреты из Vaultwarden (объект RESTIC)
|
||||||
BW_MASTER_PASSWORD_FILE="${BW_MASTER_PASSWORD_FILE:-/root/.bw-master}"
|
BW_MASTER_PASSWORD_FILE="${BW_MASTER_PASSWORD_FILE:-/root/.bw-master}"
|
||||||
if [ ! -f "$BW_MASTER_PASSWORD_FILE" ]; then
|
if [ ! -f "$BW_MASTER_PASSWORD_FILE" ]; then
|
||||||
echo "Нет файла с мастер-паролем Vaultwarden: $BW_MASTER_PASSWORD_FILE (chmod 600). См. docs/backup/proxmox-phase1-backup.md"
|
echo "Нет файла с мастер-паролем Vaultwarden: $BW_MASTER_PASSWORD_FILE (chmod 600). См. docs/vaultwarden-secrets.md"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
if ! command -v bw >/dev/null 2>&1 || ! command -v jq >/dev/null 2>&1; then
|
if ! command -v bw >/dev/null 2>&1 || ! command -v jq >/dev/null 2>&1; then
|
||||||
|
|||||||
26
scripts/certbot-hooks/check-beget-credentials.sh
Normal file
26
scripts/certbot-hooks/check-beget-credentials.sh
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Pre-hook для certbot: проверка beget.ini перед renew
|
||||||
|
# Путь: /etc/letsencrypt/renewal-hooks/pre/check-beget-credentials.sh
|
||||||
|
# При отсутствии файла или неверных правах — exit 1, certbot не выполнит renew.
|
||||||
|
|
||||||
|
BEGET_INI="/root/.secrets/certbot/beget.ini"
|
||||||
|
|
||||||
|
if [ ! -f "$BEGET_INI" ]; then
|
||||||
|
echo "check-beget-credentials: $BEGET_INI not found" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mode=$(stat -c '%a' "$BEGET_INI" 2>/dev/null)
|
||||||
|
owner=$(stat -c '%u' "$BEGET_INI" 2>/dev/null)
|
||||||
|
|
||||||
|
if [ "$mode" != "600" ]; then
|
||||||
|
echo "check-beget-credentials: $BEGET_INI has mode $mode, expected 600" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$owner" != "0" ]; then
|
||||||
|
echo "check-beget-credentials: $BEGET_INI owner $owner, expected root (0)" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit 0
|
||||||
116
scripts/deploy-beget-credentials.sh
Normal file
116
scripts/deploy-beget-credentials.sh
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# deploy-beget-credentials.sh — деплой кредов Beget для certbot DNS-01 в CT 100
|
||||||
|
# Секреты из Vaultwarden (объект beget). Атомарная запись beget.ini.
|
||||||
|
#
|
||||||
|
# Использование:
|
||||||
|
# /root/scripts/deploy-beget-credentials.sh # деплой
|
||||||
|
# /root/scripts/deploy-beget-credentials.sh --dry-run # проверка без записи
|
||||||
|
#
|
||||||
|
# Ротация: сменил пароль в Vaultwarden → запустил скрипт → готово.
|
||||||
|
#
|
||||||
|
# Требования: bw, jq, /root/.bw-master (chmod 600)
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
CT_ID=100
|
||||||
|
BEGET_INI_PATH="/root/.secrets/certbot/beget.ini"
|
||||||
|
BW_MASTER_FILE="${BW_MASTER_PASSWORD_FILE:-/root/.bw-master}"
|
||||||
|
DRY_RUN=false
|
||||||
|
|
||||||
|
for arg in "$@"; do
|
||||||
|
case "$arg" in
|
||||||
|
--dry-run) DRY_RUN=true ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
export PATH="/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:${PATH}"
|
||||||
|
|
||||||
|
log() { echo "[$(date -Iseconds)] $*"; }
|
||||||
|
err() { echo "[$(date -Iseconds)] ERROR: $*" >&2; }
|
||||||
|
|
||||||
|
ensure_bw_unlocked() {
|
||||||
|
local status
|
||||||
|
status=$(bw status 2>/dev/null | jq -r '.status' 2>/dev/null || echo "unknown")
|
||||||
|
if [ "$status" = "unlocked" ]; then
|
||||||
|
log "bw already unlocked, reusing session"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if [ ! -f "$BW_MASTER_FILE" ]; then
|
||||||
|
err "Missing $BW_MASTER_FILE"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
export BW_SESSION=$(bw unlock --passwordfile "$BW_MASTER_FILE" --raw 2>/dev/null) || {
|
||||||
|
err "bw unlock failed"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
log "bw unlocked"
|
||||||
|
}
|
||||||
|
|
||||||
|
get_secrets() {
|
||||||
|
BEGET_USER=$(bw get username "beget" 2>/dev/null)
|
||||||
|
BEGET_PASS=$(bw get password "beget" 2>/dev/null)
|
||||||
|
if [ -z "$BEGET_USER" ] || [ -z "$BEGET_PASS" ]; then
|
||||||
|
err "beget: missing username or password in Vaultwarden"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
gen_ini() {
|
||||||
|
local tmp
|
||||||
|
tmp=$(mktemp)
|
||||||
|
cat > "$tmp" << EOF
|
||||||
|
dns_beget_api_username = ${BEGET_USER}
|
||||||
|
dns_beget_api_password = ${BEGET_PASS}
|
||||||
|
EOF
|
||||||
|
echo "$tmp"
|
||||||
|
}
|
||||||
|
|
||||||
|
push_ini_atomic() {
|
||||||
|
local tmp="$1"
|
||||||
|
local dir
|
||||||
|
dir=$(dirname "$BEGET_INI_PATH")
|
||||||
|
pct exec "$CT_ID" -- mkdir -p "$dir"
|
||||||
|
pct push "$CT_ID" "$tmp" "${BEGET_INI_PATH}.tmp"
|
||||||
|
pct exec "$CT_ID" -- bash -c "mv ${BEGET_INI_PATH}.tmp ${BEGET_INI_PATH} && chmod 600 ${BEGET_INI_PATH} && chown root:root ${BEGET_INI_PATH}"
|
||||||
|
log "beget.ini written (atomic), chmod 600, owner root"
|
||||||
|
}
|
||||||
|
|
||||||
|
deploy_pre_hook() {
|
||||||
|
local hook_path="/etc/letsencrypt/renewal-hooks/pre/check-beget-credentials.sh"
|
||||||
|
local hook_src
|
||||||
|
hook_src="$(cd "$(dirname "$0")" && pwd)/certbot-hooks/check-beget-credentials.sh"
|
||||||
|
if [ ! -f "$hook_src" ]; then
|
||||||
|
log "pre-hook source not found ($hook_src), skip"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if pct exec "$CT_ID" -- test -f "$hook_path" 2>/dev/null; then
|
||||||
|
pct push "$CT_ID" "$hook_src" "$hook_path"
|
||||||
|
pct exec "$CT_ID" -- chmod +x "$hook_path"
|
||||||
|
log "pre-hook updated"
|
||||||
|
else
|
||||||
|
pct push "$CT_ID" "$hook_src" "$hook_path"
|
||||||
|
pct exec "$CT_ID" -- chmod +x "$hook_path"
|
||||||
|
log "pre-hook deployed"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
log "deploy-beget-credentials start (dry_run=$DRY_RUN)"
|
||||||
|
ensure_bw_unlocked
|
||||||
|
get_secrets
|
||||||
|
|
||||||
|
if [ "$DRY_RUN" = true ]; then
|
||||||
|
log "DRY-RUN: would push beget.ini and deploy pre-hook"
|
||||||
|
log " dns_beget_api_username=$BEGET_USER"
|
||||||
|
log " dns_beget_api_password=***"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
tmp=$(gen_ini)
|
||||||
|
trap "rm -f $tmp" EXIT
|
||||||
|
push_ini_atomic "$tmp"
|
||||||
|
deploy_pre_hook
|
||||||
|
log "done"
|
||||||
|
}
|
||||||
|
|
||||||
|
main
|
||||||
94
scripts/deploy-galene-credentials.sh
Normal file
94
scripts/deploy-galene-credentials.sh
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# deploy-galene-credentials.sh — деплой TURN-кредов Galene в CT 108
|
||||||
|
# Секреты из Vaultwarden (объект GALENE, поле config — JSON ice-servers).
|
||||||
|
#
|
||||||
|
# Использование:
|
||||||
|
# /root/scripts/deploy-galene-credentials.sh
|
||||||
|
# /root/scripts/deploy-galene-credentials.sh --dry-run
|
||||||
|
#
|
||||||
|
# Ротация: сменил TURN username/credential в Vaultwarden → запустил скрипт → systemctl restart galene
|
||||||
|
#
|
||||||
|
# Требования: bw, jq, /root/.bw-master (chmod 600)
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
CT_ID=108
|
||||||
|
ICE_SERVERS_PATH="/opt/galene-data/data/ice-servers.json"
|
||||||
|
BW_MASTER_FILE="${BW_MASTER_PASSWORD_FILE:-/root/.bw-master}"
|
||||||
|
DRY_RUN=false
|
||||||
|
|
||||||
|
for arg in "$@"; do
|
||||||
|
case "$arg" in
|
||||||
|
--dry-run) DRY_RUN=true ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
export PATH="/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:${PATH}"
|
||||||
|
|
||||||
|
log() { echo "[$(date -Iseconds)] $*"; }
|
||||||
|
err() { echo "[$(date -Iseconds)] ERROR: $*" >&2; }
|
||||||
|
|
||||||
|
ensure_bw_unlocked() {
|
||||||
|
local status
|
||||||
|
status=$(bw status 2>/dev/null | jq -r '.status' 2>/dev/null || echo "unknown")
|
||||||
|
if [ "$status" = "unlocked" ]; then
|
||||||
|
log "bw already unlocked, reusing session"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if [ ! -f "$BW_MASTER_FILE" ]; then
|
||||||
|
err "Missing $BW_MASTER_FILE"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
export BW_SESSION=$(bw unlock --passwordfile "$BW_MASTER_FILE" --raw 2>/dev/null) || {
|
||||||
|
err "bw unlock failed"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
log "bw unlocked"
|
||||||
|
}
|
||||||
|
|
||||||
|
get_secrets() {
|
||||||
|
local config
|
||||||
|
config=$(bw get item "GALENE" 2>/dev/null | jq -r '.fields[] | select(.name=="config") | .value // empty')
|
||||||
|
if [ -z "$config" ]; then
|
||||||
|
err "GALENE: missing config field (JSON ice-servers)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if ! echo "$config" | jq . >/dev/null 2>&1; then
|
||||||
|
err "GALENE config: invalid JSON"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
ICE_CONFIG="$config"
|
||||||
|
}
|
||||||
|
|
||||||
|
push_ice_servers() {
|
||||||
|
local tmp
|
||||||
|
tmp=$(mktemp)
|
||||||
|
echo "$ICE_CONFIG" | jq -c . > "$tmp"
|
||||||
|
pct push "$CT_ID" "$tmp" "${ICE_SERVERS_PATH}.tmp"
|
||||||
|
rm -f "$tmp"
|
||||||
|
pct exec "$CT_ID" -- bash -c "chmod 600 ${ICE_SERVERS_PATH}.tmp && mv ${ICE_SERVERS_PATH}.tmp ${ICE_SERVERS_PATH}"
|
||||||
|
log "ice-servers.json written (atomic), chmod 600"
|
||||||
|
}
|
||||||
|
|
||||||
|
restart_galene() {
|
||||||
|
pct exec "$CT_ID" -- systemctl restart galene
|
||||||
|
log "galene restarted"
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
log "deploy-galene-credentials start (dry_run=$DRY_RUN)"
|
||||||
|
ensure_bw_unlocked
|
||||||
|
get_secrets
|
||||||
|
|
||||||
|
if [ "$DRY_RUN" = true ]; then
|
||||||
|
log "DRY-RUN: would push ice-servers.json and restart galene"
|
||||||
|
log " config: $(echo "$ICE_CONFIG" | jq -c .)"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
push_ice_servers
|
||||||
|
restart_galene
|
||||||
|
log "done"
|
||||||
|
}
|
||||||
|
|
||||||
|
main
|
||||||
117
scripts/deploy-gitea-credentials.sh
Normal file
117
scripts/deploy-gitea-credentials.sh
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# deploy-gitea-credentials.sh — деплой кредов Gitea в CT 103
|
||||||
|
# Секреты из Vaultwarden (объект GITEA). Атомарная запись .env.
|
||||||
|
#
|
||||||
|
# Использование:
|
||||||
|
# /root/scripts/deploy-gitea-credentials.sh
|
||||||
|
# /root/scripts/deploy-gitea-credentials.sh --dry-run
|
||||||
|
#
|
||||||
|
# Ротация: сменил пароль/токен в Vaultwarden → запустил скрипт → docker compose up -d --force-recreate
|
||||||
|
#
|
||||||
|
# Требования: bw, jq, /root/.bw-master (chmod 600)
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
CT_ID=103
|
||||||
|
GITEA_PATH="/opt/gitea"
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
BW_MASTER_FILE="${BW_MASTER_PASSWORD_FILE:-/root/.bw-master}"
|
||||||
|
DRY_RUN=false
|
||||||
|
|
||||||
|
for arg in "$@"; do
|
||||||
|
case "$arg" in
|
||||||
|
--dry-run) DRY_RUN=true ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
export PATH="/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:${PATH}"
|
||||||
|
|
||||||
|
log() { echo "[$(date -Iseconds)] $*"; }
|
||||||
|
err() { echo "[$(date -Iseconds)] ERROR: $*" >&2; }
|
||||||
|
|
||||||
|
ensure_bw_unlocked() {
|
||||||
|
local status
|
||||||
|
status=$(bw status 2>/dev/null | jq -r '.status' 2>/dev/null || echo "unknown")
|
||||||
|
if [ "$status" = "unlocked" ]; then
|
||||||
|
log "bw already unlocked, reusing session"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if [ ! -f "$BW_MASTER_FILE" ]; then
|
||||||
|
err "Missing $BW_MASTER_FILE"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
export BW_SESSION=$(bw unlock --passwordfile "$BW_MASTER_FILE" --raw 2>/dev/null) || {
|
||||||
|
err "bw unlock failed"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
log "bw unlocked"
|
||||||
|
}
|
||||||
|
|
||||||
|
get_secrets() {
|
||||||
|
local item
|
||||||
|
item=$(bw get item "GITEA" 2>/dev/null)
|
||||||
|
POSTGRES_PASSWORD=$(bw get password "GITEA" 2>/dev/null)
|
||||||
|
GITEA_RUNNER_REGISTRATION_TOKEN=$(echo "$item" | jq -r '.fields[] | select(.name=="GITEA_RUNNER_REGISTRATION_TOKEN") | .value // empty')
|
||||||
|
|
||||||
|
if [ -z "$POSTGRES_PASSWORD" ]; then
|
||||||
|
err "GITEA: missing password (POSTGRES_PASSWORD)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [ -z "$GITEA_RUNNER_REGISTRATION_TOKEN" ]; then
|
||||||
|
err "GITEA: missing GITEA_RUNNER_REGISTRATION_TOKEN field"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
gen_env() {
|
||||||
|
local tmp
|
||||||
|
tmp=$(mktemp)
|
||||||
|
cat > "$tmp" << EOF
|
||||||
|
POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
|
||||||
|
GITEA_RUNNER_REGISTRATION_TOKEN=${GITEA_RUNNER_REGISTRATION_TOKEN}
|
||||||
|
EOF
|
||||||
|
echo "$tmp"
|
||||||
|
}
|
||||||
|
|
||||||
|
push_env_atomic() {
|
||||||
|
local tmp="$1"
|
||||||
|
< "$tmp" pct exec "$CT_ID" -- bash -c "cat > ${GITEA_PATH}/.env.tmp && chmod 600 ${GITEA_PATH}/.env.tmp && mv ${GITEA_PATH}/.env.tmp ${GITEA_PATH}/.env"
|
||||||
|
log ".env written (atomic), chmod 600"
|
||||||
|
}
|
||||||
|
|
||||||
|
push_compose() {
|
||||||
|
local compose_src="${SCRIPT_DIR}/gitea/docker-compose.yml"
|
||||||
|
if [ -f "$compose_src" ]; then
|
||||||
|
pct push "$CT_ID" "$compose_src" "${GITEA_PATH}/docker-compose.yml"
|
||||||
|
log "docker-compose.yml pushed"
|
||||||
|
else
|
||||||
|
log "WARN: ${compose_src} not found, skipping compose push"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
run_compose() {
|
||||||
|
pct exec "$CT_ID" -- bash -c "cd ${GITEA_PATH} && docker compose up -d --force-recreate"
|
||||||
|
log "Gitea started"
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
log "deploy-gitea-credentials start (dry_run=$DRY_RUN)"
|
||||||
|
ensure_bw_unlocked
|
||||||
|
get_secrets
|
||||||
|
|
||||||
|
if [ "$DRY_RUN" = true ]; then
|
||||||
|
log "DRY-RUN: would push .env and run compose"
|
||||||
|
log " POSTGRES_PASSWORD=***"
|
||||||
|
log " GITEA_RUNNER_REGISTRATION_TOKEN=***"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
tmp=$(gen_env)
|
||||||
|
trap "rm -f $tmp" EXIT
|
||||||
|
push_env_atomic "$tmp"
|
||||||
|
push_compose
|
||||||
|
run_compose
|
||||||
|
log "done"
|
||||||
|
}
|
||||||
|
|
||||||
|
main
|
||||||
176
scripts/deploy-immich-credentials.sh
Normal file
176
scripts/deploy-immich-credentials.sh
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# deploy-immich-credentials.sh — деплой кредов Immich и immich-deduper на VM 200
|
||||||
|
# Секреты из Vaultwarden (объекты IMMICH, IMMICH_DEDUPER).
|
||||||
|
#
|
||||||
|
# Использование:
|
||||||
|
# /root/scripts/deploy-immich-credentials.sh
|
||||||
|
# /root/scripts/deploy-immich-credentials.sh --dry-run
|
||||||
|
#
|
||||||
|
# Требования: bw, jq, /root/.bw-master, SSH без пароля root@host → admin@192.168.1.200
|
||||||
|
#
|
||||||
|
# Vaultwarden: IMMICH — поля DB_PASSWORD, IMMICH_API_KEY, GEMINI_API_KEY и др. (см. .env).
|
||||||
|
# IMMICH_DEDUPER — поля PSQL_PASS, DEDUP_*, IMMICH_PATH, PSQL_*.
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
VM_SSH="admin@192.168.1.200"
|
||||||
|
IMMICH_PATH="/opt/immich"
|
||||||
|
DEDUPER_PATH="/opt/immich-deduper"
|
||||||
|
BW_MASTER_FILE="${BW_MASTER_PASSWORD_FILE:-/root/.bw-master}"
|
||||||
|
DRY_RUN=false
|
||||||
|
|
||||||
|
for arg in "$@"; do
|
||||||
|
case "$arg" in
|
||||||
|
--dry-run) DRY_RUN=true ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
export PATH="/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:${PATH}"
|
||||||
|
|
||||||
|
log() { echo "[$(date -Iseconds)] $*"; }
|
||||||
|
err() { echo "[$(date -Iseconds)] ERROR: $*" >&2; }
|
||||||
|
|
||||||
|
ensure_bw_unlocked() {
|
||||||
|
local status
|
||||||
|
status=$(bw status 2>/dev/null | jq -r '.status' 2>/dev/null || echo "unknown")
|
||||||
|
if [ "$status" = "unlocked" ]; then
|
||||||
|
log "bw already unlocked, reusing session"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if [ ! -f "$BW_MASTER_FILE" ]; then
|
||||||
|
err "Missing $BW_MASTER_FILE"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
export BW_SESSION=$(bw unlock --passwordfile "$BW_MASTER_FILE" --raw 2>/dev/null) || {
|
||||||
|
err "bw unlock failed"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
log "bw unlocked"
|
||||||
|
}
|
||||||
|
|
||||||
|
get_field() {
|
||||||
|
local item="$1" name="$2"
|
||||||
|
echo "$item" | jq -r ".fields[] | select(.name==\"$name\") | .value // empty"
|
||||||
|
}
|
||||||
|
|
||||||
|
get_immich_secrets() {
|
||||||
|
local id
|
||||||
|
id=$(bw list items --search IMMICH 2>/dev/null | jq -r '.[] | select(.name=="IMMICH") | .id' | head -1) || true
|
||||||
|
[ -z "$id" ] && id=$(bw list items 2>/dev/null | jq -r '.[] | select(.name=="IMMICH") | .id' | head -1) || true
|
||||||
|
[ -z "$id" ] && { err "IMMICH not found in Vaultwarden"; exit 1; }
|
||||||
|
IMMICH_ITEM=$(bw get item "$id" 2>/dev/null) || { err "IMMICH get item failed for id=$id"; exit 1; }
|
||||||
|
DB_PASSWORD=$(get_field "$IMMICH_ITEM" "DB_PASSWORD")
|
||||||
|
IMMICH_API_KEY=$(get_field "$IMMICH_ITEM" "IMMICH_API_KEY")
|
||||||
|
GEMINI_API_KEY=$(get_field "$IMMICH_ITEM" "GEMINI_API_KEY")
|
||||||
|
if [ -z "$DB_PASSWORD" ]; then err "IMMICH: missing DB_PASSWORD field"; exit 1; fi
|
||||||
|
if [ -z "$IMMICH_API_KEY" ]; then err "IMMICH: missing IMMICH_API_KEY field"; exit 1; fi
|
||||||
|
}
|
||||||
|
|
||||||
|
get_deduper_secrets() {
|
||||||
|
local id
|
||||||
|
id=$(bw list items 2>/dev/null | jq -r '.[] | select(.name=="IMMICH_DEDUPER") | .id' | head -1)
|
||||||
|
[ -z "$id" ] && { err "IMMICH_DEDUPER not found in Vaultwarden"; exit 1; }
|
||||||
|
DEDUP_ITEM=$(bw get item "$id" 2>/dev/null) || {
|
||||||
|
err "IMMICH_DEDUPER not found in Vaultwarden"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
PSQL_PASS=$(get_field "$DEDUP_ITEM" "PSQL_PASS")
|
||||||
|
[ -z "$PSQL_PASS" ] && PSQL_PASS=$(echo "$DEDUP_ITEM" | jq -r '.login.password // empty')
|
||||||
|
DEDUP_PORT=$(get_field "$DEDUP_ITEM" "DEDUP_PORT")
|
||||||
|
DEDUP_DATA=$(get_field "$DEDUP_ITEM" "DEDUP_DATA")
|
||||||
|
DEDUP_IMAGE=$(get_field "$DEDUP_ITEM" "DEDUP_IMAGE")
|
||||||
|
IMMICH_PATH_FIELD=$(get_field "$DEDUP_ITEM" "IMMICH_PATH")
|
||||||
|
PSQL_HOST=$(get_field "$DEDUP_ITEM" "PSQL_HOST")
|
||||||
|
PSQL_PORT=$(get_field "$DEDUP_ITEM" "PSQL_PORT")
|
||||||
|
PSQL_DB=$(get_field "$DEDUP_ITEM" "PSQL_DB")
|
||||||
|
[ -z "$PSQL_PASS" ] && PSQL_PASS="${DB_PASSWORD:-}"
|
||||||
|
DEDUP_PORT="${DEDUP_PORT:-8086}"
|
||||||
|
DEDUP_DATA="${DEDUP_DATA:-/opt/immich-deduper/data}"
|
||||||
|
DEDUP_IMAGE="${DEDUP_IMAGE:-razgrizhsu/immich-deduper:latest-cpu}"
|
||||||
|
IMMICH_PATH_FIELD="${IMMICH_PATH_FIELD:-/mnt/data/library}"
|
||||||
|
PSQL_HOST="${PSQL_HOST:-database}"
|
||||||
|
PSQL_PORT="${PSQL_PORT:-5432}"
|
||||||
|
PSQL_DB="${PSQL_DB:-immich}"
|
||||||
|
}
|
||||||
|
|
||||||
|
gen_immich_env() {
|
||||||
|
local tmp
|
||||||
|
tmp=$(mktemp)
|
||||||
|
cat > "$tmp" << EOF
|
||||||
|
# Immich .env (generated from Vaultwarden)
|
||||||
|
UPLOAD_LOCATION=/mnt/data/library
|
||||||
|
DB_DATA_LOCATION=/mnt/data/postgres
|
||||||
|
IMMICH_VERSION=v2
|
||||||
|
DB_PASSWORD=${DB_PASSWORD}
|
||||||
|
DB_USERNAME=postgres
|
||||||
|
DB_DATABASE_NAME=immich
|
||||||
|
IMMICH_URL=http://immich-server:2283
|
||||||
|
IMMICH_API_KEY=${IMMICH_API_KEY}
|
||||||
|
DB_HOST=immich_postgres
|
||||||
|
DB_PORT=5432
|
||||||
|
EXTERNAL_IMMICH_URL=https://immich.katykhin.ru
|
||||||
|
GEMINI_API_KEY=${GEMINI_API_KEY}
|
||||||
|
EOF
|
||||||
|
echo "$tmp"
|
||||||
|
}
|
||||||
|
|
||||||
|
gen_deduper_env() {
|
||||||
|
local tmp
|
||||||
|
tmp=$(mktemp)
|
||||||
|
cat > "$tmp" << EOF
|
||||||
|
# Deduper .env (generated from Vaultwarden)
|
||||||
|
DEDUP_PORT=${DEDUP_PORT}
|
||||||
|
DEDUP_DATA=${DEDUP_DATA}
|
||||||
|
DEDUP_IMAGE=${DEDUP_IMAGE}
|
||||||
|
IMMICH_PATH=${IMMICH_PATH_FIELD}
|
||||||
|
PSQL_HOST=${PSQL_HOST}
|
||||||
|
PSQL_PORT=${PSQL_PORT}
|
||||||
|
PSQL_DB=${PSQL_DB}
|
||||||
|
PSQL_USER=postgres
|
||||||
|
PSQL_PASS=${PSQL_PASS}
|
||||||
|
EOF
|
||||||
|
echo "$tmp"
|
||||||
|
}
|
||||||
|
|
||||||
|
push_to_vm() {
|
||||||
|
local local_file="$1" remote_path="$2"
|
||||||
|
scp -o BatchMode=yes -o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new -q "$local_file" "${VM_SSH}:/tmp/deploy-env.tmp" || {
|
||||||
|
err "scp to ${VM_SSH} failed. Ensure SSH key from Proxmox: ssh-copy-id ${VM_SSH}"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
ssh -o BatchMode=yes -o ConnectTimeout=10 "$VM_SSH" "sudo mv /tmp/deploy-env.tmp ${remote_path} && sudo chmod 600 ${remote_path}" || {
|
||||||
|
err "ssh to ${VM_SSH} failed"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
run_compose() {
|
||||||
|
ssh -o BatchMode=yes "$VM_SSH" "cd ${IMMICH_PATH} && sudo docker compose up -d --force-recreate"
|
||||||
|
ssh -o BatchMode=yes "$VM_SSH" "cd ${DEDUPER_PATH} && sudo docker compose up -d --force-recreate"
|
||||||
|
log "Immich and deduper started"
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
log "deploy-immich-credentials start (dry_run=$DRY_RUN)"
|
||||||
|
ensure_bw_unlocked
|
||||||
|
get_immich_secrets
|
||||||
|
get_deduper_secrets
|
||||||
|
|
||||||
|
if [ "$DRY_RUN" = true ]; then
|
||||||
|
log "DRY-RUN: would push .env files and run compose"
|
||||||
|
log " DB_PASSWORD=*** IMMICH_API_KEY=***"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
tmp_immich=$(gen_immich_env)
|
||||||
|
tmp_deduper=$(gen_deduper_env)
|
||||||
|
trap "rm -f $tmp_immich $tmp_deduper" EXIT
|
||||||
|
push_to_vm "$tmp_immich" "${IMMICH_PATH}/.env"
|
||||||
|
log "Immich .env written"
|
||||||
|
push_to_vm "$tmp_deduper" "${DEDUPER_PATH}/.env"
|
||||||
|
log "Deduper .env written"
|
||||||
|
run_compose
|
||||||
|
log "done"
|
||||||
|
}
|
||||||
|
|
||||||
|
main
|
||||||
116
scripts/deploy-invidious-credentials.sh
Normal file
116
scripts/deploy-invidious-credentials.sh
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# deploy-invidious-credentials.sh — деплой кредов Invidious в CT 107
|
||||||
|
# Секреты из Vaultwarden (объект INVIDIOUS). Атомарная запись .env.
|
||||||
|
#
|
||||||
|
# Использование:
|
||||||
|
# /root/scripts/deploy-invidious-credentials.sh
|
||||||
|
# /root/scripts/deploy-invidious-credentials.sh --dry-run
|
||||||
|
#
|
||||||
|
# Ротация: сменил пароль/ключи в Vaultwarden → запустил скрипт → docker compose up -d --force-recreate
|
||||||
|
#
|
||||||
|
# Требования: bw, jq, /root/.bw-master (chmod 600)
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
CT_ID=107
|
||||||
|
INVIDIOUS_PATH="/opt/invidious"
|
||||||
|
BW_MASTER_FILE="${BW_MASTER_PASSWORD_FILE:-/root/.bw-master}"
|
||||||
|
DRY_RUN=false
|
||||||
|
|
||||||
|
for arg in "$@"; do
|
||||||
|
case "$arg" in
|
||||||
|
--dry-run) DRY_RUN=true ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
export PATH="/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:${PATH}"
|
||||||
|
|
||||||
|
log() { echo "[$(date -Iseconds)] $*"; }
|
||||||
|
err() { echo "[$(date -Iseconds)] ERROR: $*" >&2; }
|
||||||
|
|
||||||
|
ensure_bw_unlocked() {
|
||||||
|
local status
|
||||||
|
status=$(bw status 2>/dev/null | jq -r '.status' 2>/dev/null || echo "unknown")
|
||||||
|
if [ "$status" = "unlocked" ]; then
|
||||||
|
log "bw already unlocked, reusing session"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if [ ! -f "$BW_MASTER_FILE" ]; then
|
||||||
|
err "Missing $BW_MASTER_FILE"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
export BW_SESSION=$(bw unlock --passwordfile "$BW_MASTER_FILE" --raw 2>/dev/null) || {
|
||||||
|
err "bw unlock failed"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
log "bw unlocked"
|
||||||
|
}
|
||||||
|
|
||||||
|
get_secrets() {
|
||||||
|
local item
|
||||||
|
item=$(bw get item "INVIDIOUS" 2>/dev/null)
|
||||||
|
POSTGRES_USER=$(echo "$item" | jq -r '.login.username // empty')
|
||||||
|
POSTGRES_PASSWORD=$(bw get password "INVIDIOUS" 2>/dev/null)
|
||||||
|
INVIDIOUS_COMPANION_KEY=$(echo "$item" | jq -r '.fields[] | select(.name=="SERVER_SECRET_KEY") | .value // empty')
|
||||||
|
HMAC_KEY=$(echo "$item" | jq -r '.fields[] | select(.name=="HMAC_KEY") | .value // empty')
|
||||||
|
|
||||||
|
if [ -z "$POSTGRES_USER" ] || [ -z "$POSTGRES_PASSWORD" ]; then
|
||||||
|
err "INVIDIOUS: missing username or password"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [ -z "$INVIDIOUS_COMPANION_KEY" ]; then
|
||||||
|
err "INVIDIOUS: missing SERVER_SECRET_KEY field"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [ -z "$HMAC_KEY" ]; then
|
||||||
|
err "INVIDIOUS: missing HMAC_KEY field"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
gen_env() {
|
||||||
|
local tmp
|
||||||
|
tmp=$(mktemp)
|
||||||
|
cat > "$tmp" << EOF
|
||||||
|
POSTGRES_USER=${POSTGRES_USER}
|
||||||
|
POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
|
||||||
|
POSTGRES_DB=invidious
|
||||||
|
INVIDIOUS_COMPANION_KEY=${INVIDIOUS_COMPANION_KEY}
|
||||||
|
HMAC_KEY=${HMAC_KEY}
|
||||||
|
EOF
|
||||||
|
echo "$tmp"
|
||||||
|
}
|
||||||
|
|
||||||
|
push_env_atomic() {
|
||||||
|
local tmp="$1"
|
||||||
|
< "$tmp" pct exec "$CT_ID" -- bash -c "cat > ${INVIDIOUS_PATH}/.env.tmp && chmod 600 ${INVIDIOUS_PATH}/.env.tmp && mv ${INVIDIOUS_PATH}/.env.tmp ${INVIDIOUS_PATH}/.env"
|
||||||
|
log ".env written (atomic), chmod 600"
|
||||||
|
}
|
||||||
|
|
||||||
|
run_compose() {
|
||||||
|
pct exec "$CT_ID" -- bash -c "cd ${INVIDIOUS_PATH} && docker compose up -d --force-recreate"
|
||||||
|
log "Invidious started"
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
log "deploy-invidious-credentials start (dry_run=$DRY_RUN)"
|
||||||
|
ensure_bw_unlocked
|
||||||
|
get_secrets
|
||||||
|
|
||||||
|
if [ "$DRY_RUN" = true ]; then
|
||||||
|
log "DRY-RUN: would push .env and run compose"
|
||||||
|
log " POSTGRES_USER=$POSTGRES_USER"
|
||||||
|
log " POSTGRES_PASSWORD=***"
|
||||||
|
log " INVIDIOUS_COMPANION_KEY=***"
|
||||||
|
log " HMAC_KEY=***"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
tmp=$(gen_env)
|
||||||
|
trap "rm -f $tmp" EXIT
|
||||||
|
push_env_atomic "$tmp"
|
||||||
|
run_compose
|
||||||
|
log "done"
|
||||||
|
}
|
||||||
|
|
||||||
|
main
|
||||||
125
scripts/deploy-nextcloud-credentials.sh
Normal file
125
scripts/deploy-nextcloud-credentials.sh
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# deploy-nextcloud-credentials.sh — деплой кредов Nextcloud в CT 101
|
||||||
|
# Секреты из Vaultwarden (объект NEXTCLOUD). Атомарная запись .env, обновление config.php через occ.
|
||||||
|
#
|
||||||
|
# Использование:
|
||||||
|
# /root/scripts/deploy-nextcloud-credentials.sh
|
||||||
|
# /root/scripts/deploy-nextcloud-credentials.sh --dry-run
|
||||||
|
#
|
||||||
|
# Ротация: сменил пароль/ключи в Vaultwarden → запустил скрипт → docker compose up -d --force-recreate
|
||||||
|
#
|
||||||
|
# Требования: bw, jq, /root/.bw-master (chmod 600)
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
CT_ID=101
|
||||||
|
NEXTCLOUD_PATH="/opt/nextcloud"
|
||||||
|
CONFIG_PATH="/mnt/nextcloud-data/html/config/config.php"
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
BW_MASTER_FILE="${BW_MASTER_PASSWORD_FILE:-/root/.bw-master}"
|
||||||
|
DRY_RUN=false
|
||||||
|
|
||||||
|
for arg in "$@"; do
|
||||||
|
case "$arg" in
|
||||||
|
--dry-run) DRY_RUN=true ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
export PATH="/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:${PATH}"
|
||||||
|
|
||||||
|
log() { echo "[$(date -Iseconds)] $*"; }
|
||||||
|
err() { echo "[$(date -Iseconds)] ERROR: $*" >&2; }
|
||||||
|
|
||||||
|
ensure_bw_unlocked() {
|
||||||
|
local status
|
||||||
|
status=$(bw status 2>/dev/null | jq -r '.status' 2>/dev/null || echo "unknown")
|
||||||
|
if [ "$status" = "unlocked" ]; then
|
||||||
|
log "bw already unlocked, reusing session"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if [ ! -f "$BW_MASTER_FILE" ]; then
|
||||||
|
err "Missing $BW_MASTER_FILE"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
export BW_SESSION=$(bw unlock --passwordfile "$BW_MASTER_FILE" --raw 2>/dev/null) || {
|
||||||
|
err "bw unlock failed"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
log "bw unlocked"
|
||||||
|
}
|
||||||
|
|
||||||
|
get_secrets() {
|
||||||
|
local item
|
||||||
|
item=$(bw get item "NEXTCLOUD" 2>/dev/null)
|
||||||
|
POSTGRES_PASSWORD=$(bw get password "NEXTCLOUD" 2>/dev/null)
|
||||||
|
NEXTCLOUD_TRUSTED_DOMAINS=$(echo "$item" | jq -r '.fields[] | select(.name=="NEXTCLOUD_TRUSTED_DOMAINS") | .value // empty')
|
||||||
|
DBPASSWORD=$(echo "$item" | jq -r '.fields[] | select(.name=="dbpassword") | .value // empty')
|
||||||
|
SECRET=$(echo "$item" | jq -r '.fields[] | select(.name=="secret") | .value // empty')
|
||||||
|
PASSWORDSALT=$(echo "$item" | jq -r '.fields[] | select(.name=="passwordsalt") | .value // empty')
|
||||||
|
INSTANCEID=$(echo "$item" | jq -r '.fields[] | select(.name=="instanceid") | .value // empty')
|
||||||
|
|
||||||
|
if [ -z "$POSTGRES_PASSWORD" ]; then
|
||||||
|
err "NEXTCLOUD: missing password (POSTGRES_PASSWORD)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
NEXTCLOUD_TRUSTED_DOMAINS="${NEXTCLOUD_TRUSTED_DOMAINS:-cloud.katykhin.ru 192.168.1.101}"
|
||||||
|
}
|
||||||
|
|
||||||
|
gen_env() {
|
||||||
|
local tmp
|
||||||
|
tmp=$(mktemp)
|
||||||
|
cat > "$tmp" << EOF
|
||||||
|
POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
|
||||||
|
NEXTCLOUD_TRUSTED_DOMAINS=${NEXTCLOUD_TRUSTED_DOMAINS}
|
||||||
|
EOF
|
||||||
|
echo "$tmp"
|
||||||
|
}
|
||||||
|
|
||||||
|
push_env_atomic() {
|
||||||
|
local tmp="$1"
|
||||||
|
< "$tmp" pct exec "$CT_ID" -- bash -c "cat > ${NEXTCLOUD_PATH}/.env.tmp && chmod 600 ${NEXTCLOUD_PATH}/.env.tmp && mv ${NEXTCLOUD_PATH}/.env.tmp ${NEXTCLOUD_PATH}/.env"
|
||||||
|
log ".env written (atomic), chmod 600"
|
||||||
|
}
|
||||||
|
|
||||||
|
push_compose() {
|
||||||
|
local compose_src="${SCRIPT_DIR}/nextcloud/docker-compose.yml"
|
||||||
|
if [ -f "$compose_src" ]; then
|
||||||
|
pct push "$CT_ID" "$compose_src" "${NEXTCLOUD_PATH}/docker-compose.yml"
|
||||||
|
log "docker-compose.yml pushed"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
update_config_occ() {
|
||||||
|
[ -n "$DBPASSWORD" ] && pct exec "$CT_ID" -- docker exec nextcloud-nextcloud-1 php /var/www/html/occ config:system:set dbpassword --value="$DBPASSWORD" 2>/dev/null || true
|
||||||
|
[ -n "$SECRET" ] && pct exec "$CT_ID" -- docker exec nextcloud-nextcloud-1 php /var/www/html/occ config:system:set secret --value="$SECRET" 2>/dev/null || true
|
||||||
|
[ -n "$PASSWORDSALT" ] && pct exec "$CT_ID" -- docker exec nextcloud-nextcloud-1 php /var/www/html/occ config:system:set passwordsalt --value="$PASSWORDSALT" 2>/dev/null || true
|
||||||
|
[ -n "$INSTANCEID" ] && pct exec "$CT_ID" -- docker exec nextcloud-nextcloud-1 php /var/www/html/occ config:system:set instanceid --value="$INSTANCEID" 2>/dev/null || true
|
||||||
|
log "config.php updated via occ"
|
||||||
|
}
|
||||||
|
|
||||||
|
run_compose() {
|
||||||
|
pct exec "$CT_ID" -- bash -c "cd ${NEXTCLOUD_PATH} && docker compose up -d --force-recreate"
|
||||||
|
log "Nextcloud started"
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
log "deploy-nextcloud-credentials start (dry_run=$DRY_RUN)"
|
||||||
|
ensure_bw_unlocked
|
||||||
|
get_secrets
|
||||||
|
|
||||||
|
if [ "$DRY_RUN" = true ]; then
|
||||||
|
log "DRY-RUN: would push .env, compose, update config, run compose"
|
||||||
|
log " POSTGRES_PASSWORD=***"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
tmp=$(gen_env)
|
||||||
|
trap "rm -f $tmp" EXIT
|
||||||
|
push_env_atomic "$tmp"
|
||||||
|
push_compose
|
||||||
|
run_compose
|
||||||
|
update_config_occ
|
||||||
|
log "done"
|
||||||
|
}
|
||||||
|
|
||||||
|
main
|
||||||
129
scripts/deploy-paperless-credentials.sh
Normal file
129
scripts/deploy-paperless-credentials.sh
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# deploy-paperless-credentials.sh — деплой кредов Paperless в CT 104
|
||||||
|
# Секреты из Vaultwarden (объект PAPERLESS). Атомарная запись docker-compose.env.
|
||||||
|
#
|
||||||
|
# Использование:
|
||||||
|
# /root/scripts/deploy-paperless-credentials.sh
|
||||||
|
# /root/scripts/deploy-paperless-credentials.sh --dry-run
|
||||||
|
#
|
||||||
|
# Ротация: сменил пароль/ключи в Vaultwarden → запустил скрипт → docker compose up -d --force-recreate
|
||||||
|
#
|
||||||
|
# Требования: bw, jq, /root/.bw-master (chmod 600)
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
CT_ID=104
|
||||||
|
PAPERLESS_PATH="/opt/paperless"
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
BW_MASTER_FILE="${BW_MASTER_PASSWORD_FILE:-/root/.bw-master}"
|
||||||
|
DRY_RUN=false
|
||||||
|
|
||||||
|
for arg in "$@"; do
|
||||||
|
case "$arg" in
|
||||||
|
--dry-run) DRY_RUN=true ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
export PATH="/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:${PATH}"
|
||||||
|
|
||||||
|
log() { echo "[$(date -Iseconds)] $*"; }
|
||||||
|
err() { echo "[$(date -Iseconds)] ERROR: $*" >&2; }
|
||||||
|
|
||||||
|
ensure_bw_unlocked() {
|
||||||
|
local status
|
||||||
|
status=$(bw status 2>/dev/null | jq -r '.status' 2>/dev/null || echo "unknown")
|
||||||
|
if [ "$status" = "unlocked" ]; then
|
||||||
|
log "bw already unlocked, reusing session"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if [ ! -f "$BW_MASTER_FILE" ]; then
|
||||||
|
err "Missing $BW_MASTER_FILE"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
export BW_SESSION=$(bw unlock --passwordfile "$BW_MASTER_FILE" --raw 2>/dev/null) || {
|
||||||
|
err "bw unlock failed"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
log "bw unlocked"
|
||||||
|
}
|
||||||
|
|
||||||
|
get_secrets() {
|
||||||
|
local item
|
||||||
|
item=$(bw get item "PAPERLESS" 2>/dev/null)
|
||||||
|
POSTGRES_PASSWORD=$(bw get password "PAPERLESS" 2>/dev/null)
|
||||||
|
PAPERLESS_URL=$(echo "$item" | jq -r '.fields[] | select(.name=="PAPERLESS_URL") | .value // empty')
|
||||||
|
PAPERLESS_SECRET_KEY=$(echo "$item" | jq -r '.fields[] | select(.name=="PAPERLESS_SECRET_KEY") | .value // empty')
|
||||||
|
PAPERLESS_TIME_ZONE=$(echo "$item" | jq -r '.fields[] | select(.name=="PAPERLESS_TIME_ZONE") | .value // empty')
|
||||||
|
PAPERLESS_OCR_LANGUAGE=$(echo "$item" | jq -r '.fields[] | select(.name=="PAPERLESS_OCR_LANGUAGE") | .value // empty')
|
||||||
|
PAPERLESS_OCR_LANGUAGES=$(echo "$item" | jq -r '.fields[] | select(.name=="PAPERLESS_OCR_LANGUAGES") | .value // empty')
|
||||||
|
|
||||||
|
if [ -z "$POSTGRES_PASSWORD" ]; then
|
||||||
|
err "PAPERLESS: missing password (POSTGRES_PASSWORD)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [ -z "$PAPERLESS_SECRET_KEY" ]; then
|
||||||
|
err "PAPERLESS: missing PAPERLESS_SECRET_KEY field"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
PAPERLESS_URL="${PAPERLESS_URL:-https://docs.katykhin.ru}"
|
||||||
|
PAPERLESS_TIME_ZONE="${PAPERLESS_TIME_ZONE:-Europe/Moscow}"
|
||||||
|
PAPERLESS_OCR_LANGUAGE="${PAPERLESS_OCR_LANGUAGE:-rus+eng}"
|
||||||
|
PAPERLESS_OCR_LANGUAGES="${PAPERLESS_OCR_LANGUAGES:-rus}"
|
||||||
|
}
|
||||||
|
|
||||||
|
gen_env() {
|
||||||
|
local tmp
|
||||||
|
tmp=$(mktemp)
|
||||||
|
cat > "$tmp" << EOF
|
||||||
|
POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
|
||||||
|
PAPERLESS_URL=${PAPERLESS_URL}
|
||||||
|
PAPERLESS_SECRET_KEY=${PAPERLESS_SECRET_KEY}
|
||||||
|
PAPERLESS_TIME_ZONE=${PAPERLESS_TIME_ZONE}
|
||||||
|
PAPERLESS_OCR_LANGUAGE=${PAPERLESS_OCR_LANGUAGE}
|
||||||
|
PAPERLESS_OCR_LANGUAGES=${PAPERLESS_OCR_LANGUAGES}
|
||||||
|
EOF
|
||||||
|
echo "$tmp"
|
||||||
|
}
|
||||||
|
|
||||||
|
push_env_atomic() {
|
||||||
|
local tmp="$1"
|
||||||
|
< "$tmp" pct exec "$CT_ID" -- bash -c "cat > ${PAPERLESS_PATH}/docker-compose.env.tmp && chmod 600 ${PAPERLESS_PATH}/docker-compose.env.tmp && mv ${PAPERLESS_PATH}/docker-compose.env.tmp ${PAPERLESS_PATH}/docker-compose.env"
|
||||||
|
log "docker-compose.env written (atomic), chmod 600"
|
||||||
|
}
|
||||||
|
|
||||||
|
push_compose() {
|
||||||
|
local compose_src="${SCRIPT_DIR}/paperless/docker-compose.yml"
|
||||||
|
if [ -f "$compose_src" ]; then
|
||||||
|
pct push "$CT_ID" "$compose_src" "${PAPERLESS_PATH}/docker-compose.yml"
|
||||||
|
log "docker-compose.yml pushed"
|
||||||
|
else
|
||||||
|
log "WARN: ${compose_src} not found, skipping compose push"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
run_compose() {
|
||||||
|
pct exec "$CT_ID" -- bash -c "cd ${PAPERLESS_PATH} && docker compose up -d --force-recreate"
|
||||||
|
log "Paperless started"
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
log "deploy-paperless-credentials start (dry_run=$DRY_RUN)"
|
||||||
|
ensure_bw_unlocked
|
||||||
|
get_secrets
|
||||||
|
|
||||||
|
if [ "$DRY_RUN" = true ]; then
|
||||||
|
log "DRY-RUN: would push docker-compose.env and run compose"
|
||||||
|
log " POSTGRES_PASSWORD=***"
|
||||||
|
log " PAPERLESS_URL=$PAPERLESS_URL"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
tmp=$(gen_env)
|
||||||
|
trap "rm -f $tmp" EXIT
|
||||||
|
push_env_atomic "$tmp"
|
||||||
|
push_compose
|
||||||
|
run_compose
|
||||||
|
log "done"
|
||||||
|
}
|
||||||
|
|
||||||
|
main
|
||||||
130
scripts/deploy-rag-credentials.sh
Normal file
130
scripts/deploy-rag-credentials.sh
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# deploy-rag-credentials.sh — деплой кредов RAG-service в CT 105
|
||||||
|
# Секреты из Vaultwarden (объект RAG_SERVICE). Атомарная запись .env.
|
||||||
|
#
|
||||||
|
# Использование:
|
||||||
|
# /root/scripts/deploy-rag-credentials.sh
|
||||||
|
# /root/scripts/deploy-rag-credentials.sh --dry-run
|
||||||
|
#
|
||||||
|
# Ротация: сменил RAG_API_KEY в Vaultwarden → запустил скрипт → docker compose up -d --force-recreate
|
||||||
|
#
|
||||||
|
# Требования: bw, jq, /root/.bw-master (chmod 600)
|
||||||
|
# Vaultwarden: создать запись RAG_SERVICE с полем RAG_API_KEY (тип hidden).
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
CT_ID=105
|
||||||
|
RAG_PATH="/home/rag-service"
|
||||||
|
BW_MASTER_FILE="${BW_MASTER_PASSWORD_FILE:-/root/.bw-master}"
|
||||||
|
DRY_RUN=false
|
||||||
|
|
||||||
|
for arg in "$@"; do
|
||||||
|
case "$arg" in
|
||||||
|
--dry-run) DRY_RUN=true ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
export PATH="/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:${PATH}"
|
||||||
|
|
||||||
|
log() { echo "[$(date -Iseconds)] $*"; }
|
||||||
|
err() { echo "[$(date -Iseconds)] ERROR: $*" >&2; }
|
||||||
|
|
||||||
|
ensure_bw_unlocked() {
|
||||||
|
local status
|
||||||
|
status=$(bw status 2>/dev/null | jq -r '.status' 2>/dev/null || echo "unknown")
|
||||||
|
if [ "$status" = "unlocked" ]; then
|
||||||
|
log "bw already unlocked, reusing session"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if [ ! -f "$BW_MASTER_FILE" ]; then
|
||||||
|
err "Missing $BW_MASTER_FILE"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
export BW_SESSION=$(bw unlock --passwordfile "$BW_MASTER_FILE" --raw 2>/dev/null) || {
|
||||||
|
err "bw unlock failed"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
log "bw unlocked"
|
||||||
|
}
|
||||||
|
|
||||||
|
get_secrets() {
|
||||||
|
local item
|
||||||
|
item=$(bw get item "RAG_SERVICE" 2>/dev/null) || {
|
||||||
|
err "RAG_SERVICE not found in Vaultwarden. Create it: type Login, add custom field RAG_API_KEY (hidden)."
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
RAG_API_KEY=$(echo "$item" | jq -r '.fields[] | select(.name=="RAG_API_KEY") | .value // empty')
|
||||||
|
if [ -z "$RAG_API_KEY" ]; then
|
||||||
|
err "RAG_SERVICE: missing RAG_API_KEY field"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
gen_env() {
|
||||||
|
local tmp
|
||||||
|
tmp=$(mktemp)
|
||||||
|
cat > "$tmp" << EOF
|
||||||
|
# RAG Service Configuration (generated from Vaultwarden)
|
||||||
|
|
||||||
|
# Модель
|
||||||
|
RAG_MODEL=sentence-transformers/all-MiniLM-L12-v2
|
||||||
|
RAG_CACHE_DIR=data/models
|
||||||
|
|
||||||
|
# VectorStore
|
||||||
|
RAG_VECTORS_PATH=data/vectors/vectors.npz
|
||||||
|
RAG_MAX_EXAMPLES=10000
|
||||||
|
RAG_SCORE_MULTIPLIER=5.0
|
||||||
|
|
||||||
|
# Батч-обработка
|
||||||
|
RAG_BATCH_SIZE=16
|
||||||
|
|
||||||
|
# Минимальная длина текста
|
||||||
|
RAG_MIN_TEXT_LENGTH=3
|
||||||
|
|
||||||
|
# API настройки
|
||||||
|
RAG_API_HOST=0.0.0.0
|
||||||
|
RAG_API_PORT=8000
|
||||||
|
|
||||||
|
# Безопасность
|
||||||
|
RAG_API_KEY=${RAG_API_KEY}
|
||||||
|
RAG_ALLOW_NO_AUTH=false
|
||||||
|
|
||||||
|
# Автосохранение векторов
|
||||||
|
RAG_AUTOSAVE_INTERVAL=600
|
||||||
|
|
||||||
|
# Логирование
|
||||||
|
LOG_LEVEL=INFO
|
||||||
|
EOF
|
||||||
|
echo "$tmp"
|
||||||
|
}
|
||||||
|
|
||||||
|
push_env_atomic() {
|
||||||
|
local tmp="$1"
|
||||||
|
< "$tmp" pct exec "$CT_ID" -- bash -c "cat > ${RAG_PATH}/.env.tmp && chmod 600 ${RAG_PATH}/.env.tmp && mv ${RAG_PATH}/.env.tmp ${RAG_PATH}/.env"
|
||||||
|
log ".env written (atomic), chmod 600"
|
||||||
|
}
|
||||||
|
|
||||||
|
run_compose() {
|
||||||
|
pct exec "$CT_ID" -- bash -c "cd ${RAG_PATH} && docker compose up -d --force-recreate"
|
||||||
|
log "RAG-service started"
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
log "deploy-rag-credentials start (dry_run=$DRY_RUN)"
|
||||||
|
ensure_bw_unlocked
|
||||||
|
get_secrets
|
||||||
|
|
||||||
|
if [ "$DRY_RUN" = true ]; then
|
||||||
|
log "DRY-RUN: would push .env and run compose"
|
||||||
|
log " RAG_API_KEY=***"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
tmp=$(gen_env)
|
||||||
|
trap "rm -f $tmp" EXIT
|
||||||
|
push_env_atomic "$tmp"
|
||||||
|
run_compose
|
||||||
|
log "done"
|
||||||
|
}
|
||||||
|
|
||||||
|
main
|
||||||
41
scripts/deploy-ssh-keys-homelab.sh
Normal file
41
scripts/deploy-ssh-keys-homelab.sh
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Deploy SSH public key to all LXC containers and VM 200 in homelab.
|
||||||
|
# Run from machine that can reach Proxmox (192.168.1.150).
|
||||||
|
# Usage: ./deploy-ssh-keys-homelab.sh [path-to-public-key]
|
||||||
|
# Default: ~/.ssh/id_rsa.pub or ~/.ssh/id_ed25519.pub
|
||||||
|
|
||||||
|
set -e
|
||||||
|
PROXMOX="${PROXMOX:-root@192.168.1.150}"
|
||||||
|
KEY_FILE="${1:-$HOME/.ssh/id_rsa.pub}"
|
||||||
|
[ -f "$HOME/.ssh/id_ed25519.pub" ] && [ ! -f "$KEY_FILE" ] && KEY_FILE="$HOME/.ssh/id_ed25519.pub"
|
||||||
|
|
||||||
|
if [ ! -f "$KEY_FILE" ]; then
|
||||||
|
echo "Usage: $0 [path-to-public-key]"
|
||||||
|
echo "No key found at $KEY_FILE"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
CT_IDS="100 101 103 104 105 107 108 109"
|
||||||
|
|
||||||
|
echo "Deploying key from $KEY_FILE to homelab hosts..."
|
||||||
|
|
||||||
|
# Copy key to Proxmox temp, then deploy from there
|
||||||
|
TMP_KEY="/tmp/deploy-ssh-key-$$.pub"
|
||||||
|
scp -q "$KEY_FILE" "$PROXMOX:$TMP_KEY"
|
||||||
|
trap "ssh $PROXMOX 'rm -f $TMP_KEY'" EXIT
|
||||||
|
|
||||||
|
# Proxmox host
|
||||||
|
echo "Proxmox (192.168.1.150)..."
|
||||||
|
ssh "$PROXMOX" "mkdir -p /root/.ssh && chmod 700 /root/.ssh && grep -qF \"\$(cat $TMP_KEY)\" /root/.ssh/authorized_keys 2>/dev/null || cat $TMP_KEY >> /root/.ssh/authorized_keys && chmod 600 /root/.ssh/authorized_keys"
|
||||||
|
|
||||||
|
# LXC containers
|
||||||
|
for id in $CT_IDS; do
|
||||||
|
echo "CT $id (192.168.1.$id)..."
|
||||||
|
ssh "$PROXMOX" "pct exec $id -- bash -c 'mkdir -p /root/.ssh && chmod 700 /root/.ssh' && pct push $id $TMP_KEY /tmp/key.pub && pct exec $id -- bash -c 'grep -qF \"\$(cat /tmp/key.pub)\" /root/.ssh/authorized_keys 2>/dev/null || cat /tmp/key.pub >> /root/.ssh/authorized_keys && chmod 600 /root/.ssh/authorized_keys && rm /tmp/key.pub'"
|
||||||
|
done
|
||||||
|
|
||||||
|
# VM 200 (admin user; root may be disabled)
|
||||||
|
echo "VM 200 (admin@192.168.1.200)..."
|
||||||
|
ssh "$PROXMOX" "scp -o StrictHostKeyChecking=accept-new $TMP_KEY admin@192.168.1.200:/tmp/key.pub && ssh admin@192.168.1.200 'mkdir -p /home/admin/.ssh /root/.ssh && chmod 700 /home/admin/.ssh /root/.ssh 2>/dev/null; grep -qF \"\$(cat /tmp/key.pub)\" /home/admin/.ssh/authorized_keys 2>/dev/null || cat /tmp/key.pub >> /home/admin/.ssh/authorized_keys; echo \"\$(cat /tmp/key.pub)\" | sudo tee -a /root/.ssh/authorized_keys >/dev/null; chmod 600 /home/admin/.ssh/authorized_keys /root/.ssh/authorized_keys 2>/dev/null; rm /tmp/key.pub'"
|
||||||
|
|
||||||
|
echo "Done. Connect: ssh root@192.168.1.{100,101,103,104,105,107,108,109}, ssh admin@192.168.1.200"
|
||||||
111
scripts/deploy-vpn-route-check.sh
Normal file
111
scripts/deploy-vpn-route-check.sh
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# deploy-vpn-route-check.sh — идемпотентный деплой vpn-route-check на CT 100
|
||||||
|
# Секреты берутся из Vaultwarden (объект localhost), .env генерируется на Proxmox и пушится в CT.
|
||||||
|
#
|
||||||
|
# Использование:
|
||||||
|
# /root/scripts/deploy-vpn-route-check.sh # деплой
|
||||||
|
# /root/scripts/deploy-vpn-route-check.sh --dry-run # только проверка, без записи и compose
|
||||||
|
#
|
||||||
|
# Требования: bw, jq, /root/.bw-master (chmod 600)
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
CT_ID=100
|
||||||
|
CT_PATH="/opt/docker/vpn-route-check"
|
||||||
|
BW_MASTER_FILE="${BW_MASTER_PASSWORD_FILE:-/root/.bw-master}"
|
||||||
|
DRY_RUN=false
|
||||||
|
|
||||||
|
for arg in "$@"; do
|
||||||
|
case "$arg" in
|
||||||
|
--dry-run) DRY_RUN=true ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
export PATH="/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:${PATH}"
|
||||||
|
|
||||||
|
log() { echo "[$(date -Iseconds)] $*"; }
|
||||||
|
err() { echo "[$(date -Iseconds)] ERROR: $*" >&2; }
|
||||||
|
|
||||||
|
# --- 1. Разблокировка bw (reuse session если возможно)
|
||||||
|
ensure_bw_unlocked() {
|
||||||
|
local status
|
||||||
|
status=$(bw status 2>/dev/null | jq -r '.status' 2>/dev/null || echo "unknown")
|
||||||
|
if [ "$status" = "unlocked" ]; then
|
||||||
|
log "bw already unlocked, reusing session"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if [ ! -f "$BW_MASTER_FILE" ]; then
|
||||||
|
err "Missing $BW_MASTER_FILE"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
export BW_SESSION=$(bw unlock --passwordfile "$BW_MASTER_FILE" --raw 2>/dev/null) || {
|
||||||
|
err "bw unlock failed"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
log "bw unlocked"
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- 2. Получить секреты из Vaultwarden (localhost)
|
||||||
|
get_secrets() {
|
||||||
|
local host user pass
|
||||||
|
host=$(bw get item "localhost" 2>/dev/null | jq -r '.fields[] | select(.name=="ROUTER_TELNET_HOST") | .value // empty')
|
||||||
|
user=$(bw get username "localhost" 2>/dev/null)
|
||||||
|
pass=$(bw get password "localhost" 2>/dev/null)
|
||||||
|
|
||||||
|
if [ -z "$user" ] || [ -z "$pass" ]; then
|
||||||
|
err "localhost: missing username or password in Vaultwarden"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
host="${host:-192.168.1.1}"
|
||||||
|
ROUTER_TELNET_HOST="$host"
|
||||||
|
ROUTER_TELNET_USER="$user"
|
||||||
|
ROUTER_TELNET_PASSWORD="$pass"
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- 3. Сгенерировать .env во временный файл
|
||||||
|
gen_env() {
|
||||||
|
local tmp
|
||||||
|
tmp=$(mktemp)
|
||||||
|
cat > "$tmp" << EOF
|
||||||
|
ROUTER_TELNET_HOST=${ROUTER_TELNET_HOST}
|
||||||
|
ROUTER_TELNET_USER=${ROUTER_TELNET_USER}
|
||||||
|
ROUTER_TELNET_PASSWORD=${ROUTER_TELNET_PASSWORD}
|
||||||
|
EOF
|
||||||
|
echo "$tmp"
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- 4. Атомарно записать .env в CT 100
|
||||||
|
push_env_to_ct() {
|
||||||
|
local tmp="$1"
|
||||||
|
< "$tmp" pct exec "$CT_ID" -- bash -c "cat > ${CT_PATH}/.env.tmp && chmod 600 ${CT_PATH}/.env.tmp && mv ${CT_PATH}/.env.tmp ${CT_PATH}/.env"
|
||||||
|
log ".env written to CT $CT_ID (atomic)"
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- 5. docker compose up -d
|
||||||
|
run_compose() {
|
||||||
|
pct exec "$CT_ID" -- bash -c "cd ${CT_PATH} && docker compose up -d --force-recreate"
|
||||||
|
log "vpn-route-check started"
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- main
|
||||||
|
main() {
|
||||||
|
log "deploy-vpn-route-check start (dry_run=$DRY_RUN)"
|
||||||
|
ensure_bw_unlocked
|
||||||
|
get_secrets
|
||||||
|
|
||||||
|
if [ "$DRY_RUN" = true ]; then
|
||||||
|
log "DRY-RUN: would push .env and run compose"
|
||||||
|
log " ROUTER_TELNET_HOST=$ROUTER_TELNET_HOST"
|
||||||
|
log " ROUTER_TELNET_USER=$ROUTER_TELNET_USER"
|
||||||
|
log " ROUTER_TELNET_PASSWORD=***"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
tmp=$(gen_env)
|
||||||
|
trap "rm -f $tmp" EXIT
|
||||||
|
push_env_to_ct "$tmp"
|
||||||
|
run_compose
|
||||||
|
log "done"
|
||||||
|
}
|
||||||
|
|
||||||
|
main
|
||||||
95
scripts/deploy-wireguard-credentials.sh
Normal file
95
scripts/deploy-wireguard-credentials.sh
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# deploy-wireguard-credentials.sh — деплой конфига WireGuard в CT 109
|
||||||
|
# Секреты из Vaultwarden (объект LOCAL_VPN_SERVER_WG, поле wg0_conf — полный конфиг).
|
||||||
|
#
|
||||||
|
# Использование:
|
||||||
|
# /root/scripts/deploy-wireguard-credentials.sh
|
||||||
|
# /root/scripts/deploy-wireguard-credentials.sh --dry-run
|
||||||
|
#
|
||||||
|
# Перед первым запуском: создать в Vaultwarden запись LOCAL_VPN_SERVER_WG,
|
||||||
|
# добавить кастомное поле wg0_conf (hidden) с полным содержимым /etc/wireguard/wg0.conf.
|
||||||
|
#
|
||||||
|
# Ротация: сменил ключи в Vaultwarden → запустил скрипт → systemctl restart wg-quick@wg0
|
||||||
|
#
|
||||||
|
# Требования: bw, jq, /root/.bw-master (chmod 600)
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
CT_ID=109
|
||||||
|
WG_CONF_PATH="/etc/wireguard/wg0.conf"
|
||||||
|
BW_MASTER_FILE="${BW_MASTER_PASSWORD_FILE:-/root/.bw-master}"
|
||||||
|
DRY_RUN=false
|
||||||
|
|
||||||
|
for arg in "$@"; do
|
||||||
|
case "$arg" in
|
||||||
|
--dry-run) DRY_RUN=true ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
export PATH="/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:${PATH}"
|
||||||
|
|
||||||
|
log() { echo "[$(date -Iseconds)] $*"; }
|
||||||
|
err() { echo "[$(date -Iseconds)] ERROR: $*" >&2; }
|
||||||
|
|
||||||
|
ensure_bw_unlocked() {
|
||||||
|
local status
|
||||||
|
status=$(bw status 2>/dev/null | jq -r '.status' 2>/dev/null || echo "unknown")
|
||||||
|
if [ "$status" = "unlocked" ]; then
|
||||||
|
log "bw already unlocked, reusing session"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if [ ! -f "$BW_MASTER_FILE" ]; then
|
||||||
|
err "Missing $BW_MASTER_FILE"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
export BW_SESSION=$(bw unlock --passwordfile "$BW_MASTER_FILE" --raw 2>/dev/null) || {
|
||||||
|
err "bw unlock failed"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
log "bw unlocked"
|
||||||
|
}
|
||||||
|
|
||||||
|
get_secrets() {
|
||||||
|
WG_CONF=$(bw get item "LOCAL_VPN_SERVER_WG" 2>/dev/null | jq -r '.fields[] | select(.name=="wg0_conf") | .value // empty')
|
||||||
|
if [ -z "$WG_CONF" ]; then
|
||||||
|
err "LOCAL_VPN_SERVER_WG not found or missing wg0_conf field. Create it in Vaultwarden, add field wg0_conf with full wg0.conf content."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if ! echo "$WG_CONF" | grep -q '\[Interface\]'; then
|
||||||
|
err "wg0_conf: invalid format (expected [Interface] section)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
push_conf() {
|
||||||
|
local tmp
|
||||||
|
tmp=$(mktemp)
|
||||||
|
echo "$WG_CONF" > "$tmp"
|
||||||
|
pct push "$CT_ID" "$tmp" "${WG_CONF_PATH}.tmp"
|
||||||
|
rm -f "$tmp"
|
||||||
|
pct exec "$CT_ID" -- bash -c "chmod 600 ${WG_CONF_PATH}.tmp && mv ${WG_CONF_PATH}.tmp ${WG_CONF_PATH}"
|
||||||
|
log "wg0.conf written (atomic), chmod 600"
|
||||||
|
}
|
||||||
|
|
||||||
|
restart_wg() {
|
||||||
|
pct exec "$CT_ID" -- systemctl restart wg-quick@wg0
|
||||||
|
log "wg-quick@wg0 restarted"
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
log "deploy-wireguard-credentials start (dry_run=$DRY_RUN)"
|
||||||
|
ensure_bw_unlocked
|
||||||
|
get_secrets
|
||||||
|
|
||||||
|
if [ "$DRY_RUN" = true ]; then
|
||||||
|
log "DRY-RUN: would push wg0.conf and restart WireGuard"
|
||||||
|
log " wg0_conf: $(echo "$WG_CONF" | head -3)..."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
push_conf
|
||||||
|
restart_wg
|
||||||
|
log "done"
|
||||||
|
}
|
||||||
|
|
||||||
|
main
|
||||||
74
scripts/gitea/docker-compose.yml
Normal file
74
scripts/gitea/docker-compose.yml
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
# Шаблон для /opt/gitea/ на CT 103
|
||||||
|
# Секреты в .env (генерируется deploy-gitea-credentials.sh из Vaultwarden).
|
||||||
|
# .env не коммитить.
|
||||||
|
|
||||||
|
services:
|
||||||
|
db:
|
||||||
|
image: docker.io/library/postgres:16-alpine
|
||||||
|
restart: unless-stopped
|
||||||
|
env_file: .env
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: gitea
|
||||||
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||||
|
POSTGRES_DB: gitea
|
||||||
|
volumes:
|
||||||
|
- gitea-postgres:/var/lib/postgresql/data
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U gitea"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
|
||||||
|
server:
|
||||||
|
image: docker.gitea.com/gitea:1.25
|
||||||
|
container_name: gitea
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
db:
|
||||||
|
condition: service_healthy
|
||||||
|
env_file: .env
|
||||||
|
environment:
|
||||||
|
USER_UID: 1000
|
||||||
|
USER_GID: 1000
|
||||||
|
GITEA__database__DB_TYPE: postgres
|
||||||
|
GITEA__database__HOST: db:5432
|
||||||
|
GITEA__database__NAME: gitea
|
||||||
|
GITEA__database__USER: gitea
|
||||||
|
GITEA__database__PASSWD: ${POSTGRES_PASSWORD}
|
||||||
|
GITEA__server__DOMAIN: 192.168.1.103
|
||||||
|
GITEA__server__ROOT_URL: http://192.168.1.103:3000/
|
||||||
|
GITEA__server__SSH_PORT: 2222
|
||||||
|
volumes:
|
||||||
|
- gitea-data:/data
|
||||||
|
- /etc/timezone:/etc/timezone:ro
|
||||||
|
- /etc/localtime:/etc/localtime:ro
|
||||||
|
ports:
|
||||||
|
- "3000:3000"
|
||||||
|
- "2222:22"
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "curl", "-f", "http://localhost:3000/"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 3
|
||||||
|
start_period: 30s
|
||||||
|
|
||||||
|
runner:
|
||||||
|
image: docker.io/gitea/act_runner:latest
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
server:
|
||||||
|
condition: service_healthy
|
||||||
|
env_file: .env
|
||||||
|
environment:
|
||||||
|
GITEA_INSTANCE_URL: http://server:3000
|
||||||
|
GITEA_RUNNER_REGISTRATION_TOKEN: ${GITEA_RUNNER_REGISTRATION_TOKEN}
|
||||||
|
GITEA_RUNNER_NAME: gitea-103-runner
|
||||||
|
GITEA_RUNNER_LABELS: docker:docker://alpine:latest
|
||||||
|
volumes:
|
||||||
|
- runner-data:/data
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
gitea-data:
|
||||||
|
gitea-postgres:
|
||||||
|
runner-data:
|
||||||
84
scripts/invidious/docker-compose.yml
Normal file
84
scripts/invidious/docker-compose.yml
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
# Шаблон для /opt/invidious/docker-compose.yml на CT 107
|
||||||
|
# Секреты в .env (генерируется deploy-invidious-credentials.sh из Vaultwarden).
|
||||||
|
# .env не коммитить.
|
||||||
|
|
||||||
|
services:
|
||||||
|
invidious:
|
||||||
|
image: quay.io/invidious/invidious:latest
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "3000:3000"
|
||||||
|
env_file: .env
|
||||||
|
environment:
|
||||||
|
INVIDIOUS_CONFIG: |
|
||||||
|
db:
|
||||||
|
dbname: invidious
|
||||||
|
user: ${POSTGRES_USER}
|
||||||
|
password: ${POSTGRES_PASSWORD}
|
||||||
|
host: invidious-db
|
||||||
|
port: 5432
|
||||||
|
check_tables: true
|
||||||
|
invidious_companion:
|
||||||
|
- private_url: "http://companion:8282/companion"
|
||||||
|
invidious_companion_key: "${INVIDIOUS_COMPANION_KEY}"
|
||||||
|
external_port: 443
|
||||||
|
domain: "video.katykhin.ru"
|
||||||
|
https_only: true
|
||||||
|
use_pubsub_feeds: true
|
||||||
|
use_innertube_for_captions: true
|
||||||
|
hmac_key: "${HMAC_KEY}"
|
||||||
|
default_user_preferences:
|
||||||
|
default_home: Popular
|
||||||
|
dark_mode: "light"
|
||||||
|
player_style: "youtube"
|
||||||
|
vr_mode: false
|
||||||
|
automatic_instance_redirect: false
|
||||||
|
healthcheck:
|
||||||
|
test: wget -nv --tries=1 --spider http://127.0.0.1:3000/api/v1/stats || exit 1
|
||||||
|
interval: 30s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 2
|
||||||
|
logging:
|
||||||
|
options:
|
||||||
|
max-size: "1G"
|
||||||
|
max-file: "4"
|
||||||
|
depends_on:
|
||||||
|
invidious-db:
|
||||||
|
condition: service_healthy
|
||||||
|
|
||||||
|
companion:
|
||||||
|
image: quay.io/invidious/invidious-companion:latest
|
||||||
|
env_file: .env
|
||||||
|
environment:
|
||||||
|
SERVER_SECRET_KEY: ${INVIDIOUS_COMPANION_KEY}
|
||||||
|
restart: unless-stopped
|
||||||
|
logging:
|
||||||
|
options:
|
||||||
|
max-size: "1G"
|
||||||
|
max-file: "4"
|
||||||
|
cap_drop:
|
||||||
|
- ALL
|
||||||
|
read_only: true
|
||||||
|
volumes:
|
||||||
|
- companioncache:/var/tmp/youtubei.js:rw
|
||||||
|
security_opt:
|
||||||
|
- no-new-privileges:true
|
||||||
|
|
||||||
|
invidious-db:
|
||||||
|
image: docker.io/library/postgres:14
|
||||||
|
restart: unless-stopped
|
||||||
|
volumes:
|
||||||
|
- postgresdata:/var/lib/postgresql/data
|
||||||
|
- ./config/sql:/config/sql
|
||||||
|
- ./docker/init-invidious-db.sh:/docker-entrypoint-initdb.d/init-invidious-db.sh
|
||||||
|
env_file: .env
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: invidious
|
||||||
|
POSTGRES_USER: ${POSTGRES_USER}
|
||||||
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"]
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
postgresdata:
|
||||||
|
companioncache:
|
||||||
52
scripts/nextcloud/docker-compose.yml
Normal file
52
scripts/nextcloud/docker-compose.yml
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
# Шаблон для /opt/nextcloud/ на CT 101
|
||||||
|
# Секреты в .env (генерируется deploy-nextcloud-credentials.sh из Vaultwarden).
|
||||||
|
# .env не коммитить.
|
||||||
|
|
||||||
|
services:
|
||||||
|
db:
|
||||||
|
image: docker.io/library/postgres:16
|
||||||
|
restart: unless-stopped
|
||||||
|
volumes:
|
||||||
|
- /mnt/nextcloud-data/pgdata:/var/lib/postgresql/data
|
||||||
|
env_file: .env
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: nextcloud
|
||||||
|
POSTGRES_USER: nextcloud
|
||||||
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U nextcloud"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
|
||||||
|
redis:
|
||||||
|
image: docker.io/library/redis:7-alpine
|
||||||
|
restart: unless-stopped
|
||||||
|
command: redis-server --appendonly yes
|
||||||
|
|
||||||
|
nextcloud:
|
||||||
|
image: docker.io/nextcloud:latest
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
db:
|
||||||
|
condition: service_healthy
|
||||||
|
redis:
|
||||||
|
condition: service_started
|
||||||
|
ports:
|
||||||
|
- "8080:80"
|
||||||
|
volumes:
|
||||||
|
- /mnt/nextcloud-data/html:/var/www/html
|
||||||
|
- /mnt/nextcloud-extra:/mnt/nextcloud-extra
|
||||||
|
- /opt/nextcloud/php-uploads.ini:/usr/local/etc/php/conf.d/zz-uploads.ini:ro
|
||||||
|
env_file: .env
|
||||||
|
environment:
|
||||||
|
APACHE_BODY_LIMIT: "0"
|
||||||
|
NEXTCLOUD_TRUSTED_DOMAINS: ${NEXTCLOUD_TRUSTED_DOMAINS}
|
||||||
|
OVERWRITEPROTOCOL: https
|
||||||
|
OVERWRITEHOST: cloud.katykhin.ru
|
||||||
|
OVERWRITECLIURL: https://cloud.katykhin.ru
|
||||||
|
REDIS_HOST: redis
|
||||||
|
POSTGRES_HOST: db
|
||||||
|
POSTGRES_DB: nextcloud
|
||||||
|
POSTGRES_USER: nextcloud
|
||||||
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||||
@@ -1,15 +1,17 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# Add vault.katykhin.ru → 192.168.1.103:8280 via NPM API + Access List (LAN + VPN only)
|
# Add vault.katykhin.ru → 192.168.1.103:8280 via NPM API + Access List (LAN + VPN only)
|
||||||
# Usage: NPM_EMAIL=j3tears100@gmail.com NPM_PASSWORD=xxx ./npm-add-proxy-vault.sh
|
# Usage: NPM_EMAIL=... NPM_PASSWORD=... ./npm-add-proxy-vault.sh
|
||||||
|
# NPM credentials: Vaultwarden, объект NPM_ADMIN (username=email, password)
|
||||||
# Run from host that can reach NPM, or: ssh root@192.168.1.150 "pct exec 100 -- bash -s" < scripts/npm-add-proxy-vault.sh
|
# Run from host that can reach NPM, or: ssh root@192.168.1.150 "pct exec 100 -- bash -s" < scripts/npm-add-proxy-vault.sh
|
||||||
# (then set NPM_URL=http://127.0.0.1:81 and NPM_EMAIL/NPM_PASSWORD in env or below)
|
# (then set NPM_URL=http://127.0.0.1:81 and NPM_EMAIL/NPM_PASSWORD in env)
|
||||||
# NPM credentials: see docs/containers/container-100.md
|
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
NPM_URL="${NPM_URL:-http://192.168.1.100:81}"
|
NPM_URL="${NPM_URL:-http://192.168.1.100:81}"
|
||||||
API="$NPM_URL/api"
|
API="$NPM_URL/api"
|
||||||
NPM_EMAIL="${NPM_EMAIL:-j3tears100@gmail.com}"
|
if [ -z "$NPM_EMAIL" ] || [ -z "$NPM_PASSWORD" ]; then
|
||||||
NPM_PASSWORD="${NPM_PASSWORD:-kqEUubVq02DJTS8}"
|
echo "Set NPM_EMAIL and NPM_PASSWORD (from Vaultwarden, объект NPM_ADMIN)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
echo "1. Getting token..."
|
echo "1. Getting token..."
|
||||||
TOKEN=$(curl -s -X POST "$API/tokens" \
|
TOKEN=$(curl -s -X POST "$API/tokens" \
|
||||||
|
|||||||
41
scripts/paperless/docker-compose.yml
Normal file
41
scripts/paperless/docker-compose.yml
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
# Шаблон для /opt/paperless/ на CT 104
|
||||||
|
# Секреты в docker-compose.env (генерируется deploy-paperless-credentials.sh из Vaultwarden).
|
||||||
|
# docker-compose.env не коммитить.
|
||||||
|
|
||||||
|
services:
|
||||||
|
broker:
|
||||||
|
image: docker.io/library/redis:8
|
||||||
|
restart: unless-stopped
|
||||||
|
volumes:
|
||||||
|
- redisdata:/data
|
||||||
|
|
||||||
|
db:
|
||||||
|
image: docker.io/library/postgres:18
|
||||||
|
restart: unless-stopped
|
||||||
|
volumes:
|
||||||
|
- /mnt/paperless-data/pgdata:/var/lib/postgresql
|
||||||
|
env_file: docker-compose.env
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: paperless
|
||||||
|
POSTGRES_USER: paperless
|
||||||
|
|
||||||
|
webserver:
|
||||||
|
image: ghcr.io/paperless-ngx/paperless-ngx:latest
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
- db
|
||||||
|
- broker
|
||||||
|
ports:
|
||||||
|
- "8000:8000"
|
||||||
|
volumes:
|
||||||
|
- /mnt/paperless-data/data:/usr/src/paperless/data
|
||||||
|
- /mnt/paperless-data/media:/usr/src/paperless/media
|
||||||
|
- ./export:/usr/src/paperless/export
|
||||||
|
- ./consume:/usr/src/paperless/consume
|
||||||
|
env_file: docker-compose.env
|
||||||
|
environment:
|
||||||
|
PAPERLESS_REDIS: redis://broker:6379
|
||||||
|
PAPERLESS_DBHOST: db
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
redisdata:
|
||||||
@@ -30,7 +30,7 @@ fi
|
|||||||
# Секреты из Vaultwarden (объект RESTIC)
|
# Секреты из Vaultwarden (объект RESTIC)
|
||||||
BW_MASTER_PASSWORD_FILE="${BW_MASTER_PASSWORD_FILE:-/root/.bw-master}"
|
BW_MASTER_PASSWORD_FILE="${BW_MASTER_PASSWORD_FILE:-/root/.bw-master}"
|
||||||
if [ ! -f "$BW_MASTER_PASSWORD_FILE" ]; then
|
if [ ! -f "$BW_MASTER_PASSWORD_FILE" ]; then
|
||||||
echo "Нет файла с мастер-паролем Vaultwarden: $BW_MASTER_PASSWORD_FILE (chmod 600). См. docs/backup/proxmox-phase1-backup.md"
|
echo "Нет файла с мастер-паролем Vaultwarden: $BW_MASTER_PASSWORD_FILE (chmod 600). См. docs/vaultwarden-secrets.md"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
if ! command -v bw >/dev/null 2>&1 || ! command -v jq >/dev/null 2>&1; then
|
if ! command -v bw >/dev/null 2>&1 || ! command -v jq >/dev/null 2>&1; then
|
||||||
|
|||||||
16
scripts/vpn-route-check/docker-compose.yml
Normal file
16
scripts/vpn-route-check/docker-compose.yml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# Шаблон для /opt/docker/vpn-route-check/docker-compose.yml на CT 100
|
||||||
|
# Секреты в .env (генерируется deploy-vpn-route-check.sh из Vaultwarden).
|
||||||
|
# .env не коммитить.
|
||||||
|
|
||||||
|
services:
|
||||||
|
vpn-route-check:
|
||||||
|
build: .
|
||||||
|
container_name: vpn-route-check
|
||||||
|
network_mode: host
|
||||||
|
env_file: .env
|
||||||
|
volumes:
|
||||||
|
- vpn-route-check-data:/data
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
vpn-route-check-data:
|
||||||
Reference in New Issue
Block a user