Commit the accumulated infrastructure work that was living only in the working tree: monitoring stack, emergency access/bot, gyro allocator, grimmory, adguard, backup audit and the OpenCode agent definitions. Also ignore Python bytecode, local archives and Nix/direnv artifacts. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GTocXkGUUazHdKKd3r9k71
29 KiB
План развертывания Grimmory
Целевая архитектура
- Отдельный unprivileged LXC
grimmoryнаcloud-pc. - Итоговые значения: CTID
149, IP192.168.1.34. Первоначальный.33оказался занят после создания LXC. - RootFS на storage
data, первоначально64 GBс возможностью расширения. - Ресурсы:
2 vCPU,4 GB RAM,1 GB swap. - Docker с
fuse-overlayfsвнутри LXC. - Docker Compose: Grimmory и MariaDB.
- Публичный адрес:
https://books.ada-dev.ru. - Книги и данные находятся внутри диска LXC.
- Daily PBS snapshot всего CT.
- Offsite restic: дамп MariaDB, конфигурация и
/app/data, но не книги. - Образы фиксируются на версиях:
grimmory/grimmory:v3.2.4;lscr.io/linuxserver/mariadb:11.4.8.
Размер диска нужно скорректировать до развертывания, если текущая библиотека уже приближается к 40-50 GB.
1. Предварительные проверки
- Проверить, что CTID
149свободен:- в Proxmox API;
- в
pct listна обоих узлах; - в PBS среди исторических VMID.
- Проверить IP
192.168.1.34:- отсутствие в inventory;
- отсутствие DHCP reservation;
- отсутствие ответа на ARP/ping;
- отсутствие DNS-записи с другим назначением.
- Проверить емкость
cloud-pc:data. - Проверить работоспособность PBS datastore и последнего backup job
cloud-pc. - Проверить возможность создать DNS-запись
books.ada-dev.ru. - Зафиксировать ожидаемый объем существующей коллекции и темп роста.
- Получить digest образов после выбора версий. В первой итерации допустим pinned tag; digest предпочтительнее для полностью воспроизводимого состояния.
Проверка должна завершиться без создания LXC. При конфликте CTID/IP выбрать следующие свободные значения и использовать их согласованно во всех файлах.
2. Inventory
Изменить ansible/inventory/hosts.yml:
- добавить
grimmoryвlxc_infra; - указать
ansible_host: 192.168.1.34; - указать
expected_lan_ip; - указать
ansible_user: root; - использовать существующий ProxyJump через
ru-vps; - добавить в
monitoring_exporters, если Node Exporter разворачивается сразу.
После изменения проверить:
ansible-inventory --graph
ansible-inventory --host grimmory
Edge cases:
- не использовать IP за пределами маршрутизируемого service range
.5-.40; - убедиться, что имя
grimmoryразрешается Ansible именно в новый IP; - не добавлять host в inventory до выбора окончательных CTID/IP во всех playbooks.
3. Provisioning LXC
Создать ansible/playbooks/pve-grimmory.yml, используя роль pve_lxc.
Параметры:
pve_lxc_vmid: 149
pve_lxc_node: cloud-pc
pve_lxc_hostname: grimmory
pve_lxc_ip: 192.168.1.34/24
pve_lxc_gateway: 192.168.1.1
pve_lxc_storage: data
pve_lxc_disk: 64
pve_lxc_cores: 2
pve_lxc_memory: 4096
pve_lxc_swap: 1024
pve_lxc_unprivileged: true
pve_lxc_features:
- nesting=1
- keyctl=1
Дополнительно настроить Docker-in-LXC по существующему паттерну:
- разрешить
/dev/fuse; - добавить bind устройства
/dev/fuse; - перезапустить LXC только при изменении его PVE-конфигурации;
- дождаться SSH;
- проверить наличие пользовательского SSH-ключа.
Текущая роль pve_lxc не управляет /dev/fuse, поэтому эти задачи остаются в playbook, аналогично существующим Docker LXC.
Проверки:
ansible-playbook --syntax-check playbooks/pve-grimmory.yml
ansible-playbook playbooks/pve-grimmory.yml --check
--check для Proxmox API не гарантирует полноценной симуляции, поэтому его результат использовать только как дополнительную проверку.
После создания проверить:
pct status 149;- unprivileged mode включен;
- rootfs действительно находится на
data; onboot=1;- SSH работает через ProxyJump;
/dev/fuseсуществует внутри LXC;- повторный запуск playbook не создает изменений.
4. Docker Runtime
Внутри LXC установить:
docker.io;- Docker Compose v2, если доступен как
docker-compose-plugin; fuse-overlayfs;ca-certificates;curl;mariadb-clientдля backup/restore-проверок;prometheus-node-exporter.
Настроить /etc/docker/daemon.json:
{
"storage-driver": "fuse-overlayfs"
}
Проверить:
docker info
docker compose version
docker run --rm hello-world
Критерий успеха: Docker сообщает fuse-overlayfs как storage driver и переживает перезапуск LXC.
5. Persistent Data
Создать структуру:
/opt/grimmory/
├── compose.yml
├── .env
├── data/
├── books/
├── bookdrop/
├── mariadb/
└── backup-staging/
Назначение:
data->/app/data;books->/books;bookdrop->/bookdrop;mariadb->/config;backup-staging-> временные согласованные дампы.
Права каталогов должны соответствовать APP_USER_ID, APP_GROUP_ID, MariaDB PUID/PGID.
Edge cases:
bookdropне является библиотекой: после успешного импорта исходники удаляются оттуда;- не размещать важные необработанные оригиналы только в
bookdrop; - не вкладывать bind mounts друг в друга;
- не использовать
DISK_TYPE=NETWORK, поскольку файлы находятся на локальном rootfs; - оставлять минимум 15-20% диска свободным: импорт и обработка могут временно удваивать размер книги;
- учитывать крупные PDF/CBR и возможные heap dump при OOM.
6. Compose
Управлять двумя контейнерами через Compose, поскольку Grimmory зависит от MariaDB. Для этого сервиса Compose логичнее отдельных systemd docker run.
Основные параметры:
DISK_TYPE=LOCAL;TZсоответствует HomeLab;ALLOWED_ORIGINS=https://books.ada-dev.ru;- MariaDB доступна только во внутренней Docker network;
- наружу публикуется только Grimmory
6060; - порт привязывается к LAN IP LXC, а не
0.0.0.0, если Compose это позволяет; - healthcheck использует
/api/v1/healthcheck; restart: unless-stopped.
Создать systemd unit-обертку для:
docker compose -f /opt/grimmory/compose.yml up -d --remove-orphans
Не использовать --pull always: обновление должно быть отдельной контролируемой операцией.
Секреты:
- случайные отдельные пароли
DB_PASSWORDиMYSQL_ROOT_PASSWORD; .envс mode0600;- реальные значения не добавляются в Git;
.env.exampleсодержит только имена переменных и фиктивные значения;- пароль первого администратора создается вручную через UI.
Проверки:
docker compose config
docker compose pull
systemctl start grimmory
docker compose ps
docker compose logs
curl -fsS http://127.0.0.1:6060/api/v1/healthcheck
Критерии:
- оба контейнера healthy;
- MariaDB не публикует порт на LAN;
- после рестарта LXC оба контейнера возвращаются в healthy;
- логи не содержат Flyway errors, authentication failures и циклических рестартов.
7. Первый запуск
- Открыть сервис сначала по LAN.
- Создать локального администратора.
- Сохранить пароль в password manager.
- Отключить неиспользуемую публичную регистрацию.
- Создать отдельного обычного пользователя для чтения, если административная учетная запись не должна использоваться на BOOX.
- Включить OPDS.
- Создать отдельные OPDS credentials.
- Выдать пользователю разрешение
Access OPDS. - Создать тестовую библиотеку в
/books. - Импортировать одну тестовую EPUB через BookDrop.
Важно: OPDS credentials отделены от обычного логина Grimmory. Пароль OPDS после создания нельзя получить повторно.
8. Reverse Proxy
Создать ansible/playbooks/reverse-proxy-grimmory.yml по паттерну Gitea/Vaultwarden.
Caddy site:
books.ada-dev.ru {
reverse_proxy 192.168.1.34:6060
}
Caddy автоматически поддерживает WebSocket upgrade, но это нужно проверить для /ws.
Не добавлять дополнительный Caddy Basic Auth: он конфликтует с Basic Auth OPDS.
Проверки до переключения DNS:
- Caddy может достучаться до
192.168.1.34:6060через OpenVPN; - upstream healthcheck возвращает
2xx; - Caddyfile проходит
caddy validate; - конфигурация действительно смонтирована в контейнер Caddy.
После DNS:
curl -fsS https://books.ada-dev.ru/api/v1/healthcheck
Дополнительно проверить:
- валидную цепочку TLS;
- WebSocket
/ws; - правильные внешние
Host, scheme и port; - отсутствие redirect на внутренний
http://192.168.1.34:6060; - загрузку файла размером больше стандартного proxy limit;
- доступ с мобильной сети, а не только из домашнего LAN.
9. OPDS и BOOX Go 7
Каталог:
https://books.ada-dev.ru/api/v1/opds
Настроить в NeoReader:
- URL корневого каталога;
- отдельный OPDS username/password;
- HTTPS;
- без второго слоя proxy-аутентификации.
Тестовый сценарий:
- Каталог открывается.
- Отображаются разделы и обложки.
- Работает поиск.
- EPUB скачивается.
- Книга открывается в NeoReader.
- Кириллица в названии, авторе и описании отображается корректно.
- Повторная загрузка не создает поврежденный файл.
- Проверить EPUB, PDF и FB2 отдельно.
- Проверить работу из домашнего Wi-Fi и внешней сети.
- Проверить поведение после смены OPDS-пароля.
Позиция чтения NeoReader обратно в Grimmory через OPDS не синхронизируется. Это ожидаемое поведение, а не дефект сервера.
10. Firewall
Минимальная политика:
- SSH
22только из LAN и OpenVPN; 6060только из LAN/OpenVPN и от нужного reverse-proxy маршрута;- MariaDB не публикуется;
- остальные входящие соединения запрещены.
Edge case: Docker published ports могут обходить UFW. Поэтому проверить фактические nftables/iptables rules и доступ к 6060 с нежелательных сегментов, а не полагаться только на вывод ufw status.
Публично сервис должен быть доступен только через Caddy на ru-vps, не через router port-forward.
11. Monitoring
Добавить:
- Node Exporter target
192.168.1.34:9100; - LAN blackbox HTTP probe
/api/v1/healthcheck; - публичный HTTPS probe
https://books.ada-dev.ru/api/v1/healthcheck; - TLS expiry check;
- базовые алерты по availability, CPU, RAM, disk и backup freshness.
Не считать один health endpoint достаточным. Он может отвечать при частично неисправной функции OPDS.
Добавить синтетическую OPDS-проверку:
- Basic Auth тестового monitoring-пользователя;
- ответ
200; - ожидаемый OPDS content type;
- корректный XML/Atom;
- секреты только из runtime env, не из шаблона в Git.
Если хранить OPDS credential в monitoring нежелательно, ограничиться публичным healthcheck и ручным OPDS smoke test после обновлений.
12. PBS Backup
Добавить CTID в:
ansible/playbooks/pve-backup-jobs.yml, jobhomelab-pbs-daily-cloud;ansible/roles/backup_audit/defaults/main.yml.
Сохранить исключение Docker merged layers:
/var/lib/docker/fuse-overlayfs/*/merged
Проверки:
- Применить backup job.
- Запустить первый backup вручную.
- Убедиться, что snapshot завершен без warnings.
- Проверить наличие VMID
149в PBS. - Проверить прохождение backup audit.
- Проверить размер backup: слишком маленький может означать ошибочное исключение данных.
- Выполнить тестовое file-level восстановление нескольких файлов.
- Запланировать полный restore drill в отдельный временный CTID.
PBS snapshot MariaDB считается crash-consistent, но не заменяет логический dump.
13. Offsite Restic
Offsite должен содержать:
- согласованный
mariadb-dump; /opt/grimmory/data;compose.yml;- зашифрованную или отдельно защищенную конфигурацию;
- manifest с версиями Grimmory и MariaDB.
Не включать:
/opt/grimmory/books;/opt/grimmory/bookdrop;/opt/grimmory/mariadbкак raw-copy работающей БД;- Docker layers;
- временные heap dump и cache.
Для дампа:
- использовать
mariadb-dump --single-transaction; - сначала писать во временный файл;
- проверять exit code;
- атомарно переносить завершенный dump в staging;
- только после этого запускать restic;
- удалять старый staging после успешного snapshot.
Расширить существующий restic-паттерн MariaDB-aware логикой, а не использовать SQLite backup helper.
Проверки:
mariadbможет прочитать dump;- restic snapshot содержит dump и
/app/data; - backup audit отслеживает профиль Grimmory;
- выполнить пробное восстановление в отдельную временную MariaDB;
- убедиться, что восстановленная схема проходит Flyway startup на той же версии Grimmory.
14. Обновления
Обновлять только явно, например v3.2.4 -> v3.2.5.
Процесс:
- Прочитать release notes и migration warnings.
- Проверить свободное место.
- Сделать MariaDB dump.
- Сделать offsite restic snapshot.
- Убедиться в наличии свежего PBS backup.
- Изменить pinned image tag/digest.
- Pull и restart.
- Проверить Flyway logs.
- Проверить health, UI, BookDrop и OPDS с BOOX.
- Зафиксировать версию в документации.
Не выполнять code-only downgrade после применения Flyway migration. Для отката возвращать одновременно старый image и pre-upgrade backup базы.
15. Edge Cases
Обязательно проверить:
- заполнение rootfs во время массового импорта;
- дубликаты и одинаковые ISBN;
- книги без ISBN;
- кириллические имена и длинные пути;
- два файла одной книги в разных форматах;
- поврежденный EPUB/CBR;
- файлы больше 1 GB;
- cross-filesystem BookDrop move;
- перезапуск во время импорта;
- падение MariaDB во время обработки;
- недоступность Google Books/Open Library/Amazon;
- истечение TLS;
- смена внешнего домена;
- потеря OPDS-пароля;
- потеря admin-пароля;
- OOM и появление большого heap dump;
- несовместимая Flyway migration;
- неожиданное изменение floating Docker tag;
- частичный backup: БД без
/app/dataлибо наоборот; - восстановление БД с файлами библиотеки другого состояния.
16. Финальная приемка
Развертывание считается завершенным, когда:
- Ansible запускается повторно без изменений;
- LXC автоматически стартует;
- контейнеры healthy после reboot;
- MariaDB не доступна из LAN;
- HTTPS и WebSocket работают;
- NeoReader открывает OPDS и скачивает тестовые книги;
- импорт через BookDrop проходит полностью;
- Node Exporter и blackbox targets находятся в состоянии
UP; - создан и проверен PBS backup;
- создан и тестово восстановлен restic snapshot;
- секреты отсутствуют в Git diff;
- Obsidian-документация обновлена.
17. Ожидаемые файлы
Основные изменения:
ansible/inventory/hosts.yml;ansible/playbooks/pve-grimmory.yml;ansible/playbooks/reverse-proxy-grimmory.yml;ansible/playbooks/pve-backup-jobs.yml;ansible/roles/backup_audit/defaults/main.yml;ansible/playbooks/offsite-restic-yadisk.yml;ansible/playbooks/backup-audit.yml;ansible/roles/monitoring_server/templates/prometheus.yml.j2;- возможно
ansible/roles/monitoring_blackbox/templates/push-metrics.sh.j2; ansible/.env.example, только если потребуются новые runtime secrets.
Документация:
Notes/Текущее состояние HomeLab после миграции на Proxmox.md;Log.md;Context.md;- при необходимости
HomeLab.md.
18. Открытый параметр
Перед реализацией подтвердить 64 GB как начальный размер диска или выбрать его по текущему объему коллекции.
Решение: текущая коллекция меньше 20 GB, начальный rootfs оставлен 64 GB.
Журнал выполнения
2026-07-28 — preflight и проектные решения
- Прочитаны актуальные HomeLab notes и существующие Ansible-паттерны LXC, Caddy, monitoring, PBS и restic.
- CTID
149отсутствует вpct listнаcloud-pcиmini-pc, а также среди исторических snapshot в PBS. - IP
192.168.1.33отсутствует в inventory и AdGuard config, не отвечает на ping, ARP-состояние сcloud-pc—INCOMPLETE; пользователь подтвердил отсутствие DHCP reservation. - Storage
cloud-pc:dataактивен: доступно около836 GB, занято3.67%. - PBS storage и datastore
pbsактивны; последние snapshots текущих cloud-pc CT созданы2026-07-27. books.ada-dev.ruуже разрешается в157.22.231.198(ru-vps); случайное имя в зоне не разрешается, поэтому это не wildcard. До добавления Caddy site TLS handshake не завершается.- Подтверждены
grimmory/grimmory:v3.2.4иlscr.io/linuxserver/mariadb:11.4.8; выбраны OCI index digestssha256:dfa7afdfcf25d649fd664497a62385dd00cd9678c37546e182c172e41c8e80cbиsha256:91de7f701bc7fc3a424b81beafca7a7c6c4c5b7c8be6afd2ae148698695c0b0c. - Rootfs подтвержден как
data:64, timezone —Europe/Moscow. - Пароли MariaDB будут случайно созданы один раз внутри LXC и сохранены только в
/opt/grimmory/.envс mode0600. - В первой итерации автоматизируются health/TLS probes; OPDS smoke test остается ручным после создания credentials.
- Offsite audit первой итерации: атомарный MariaDB dump, проверка наличия dump в snapshot и
restic check; автоматический restore в MariaDB отложен. - Первый apply остановился до создания из-за quoting условия VMID guard; второй — из-за отсутствия прямого маршрута к Proxmox API. Для API использован временный SSH local-forward через
ru-vps. - После создания CT выяснился IP conflict: с
mini-pcадрес.33резолвился какYandexStationMini_1DD2.locи отклонял TCP/22, тогда как локальный bridgecloud-pcвидел LXC. Выбран следующий адрес192.168.1.34; он не отвечает с обоих PVE-узлов, ARP —INCOMPLETE, DNS отсутствует.
2026-07-28 — реализация и автоматическая приёмка
- Создан unprivileged LXC
grimmoryCT149наcloud-pc:192.168.1.34/24,data:64, 2 vCPU, 4096 MB RAM, 1024 MB swap,onboot=1,nesting=1,keyctl=1,/dev/fuse. - Из-за ограничений API token роль
pve_lxcиспользуется в create-only mode для Grimmory; VMID guard запрещает менять чужой CT, а network/keyctl/FUSE идемпотентно управляются черезpctна PVE node. - Установлены Docker Compose v2
2.26.1, Docker26.1.5,fuse-overlayfs, MariaDB client, Node Exporter и UFW. Docker подтвердил storage driverfuse-overlayfs. - Случайные DB passwords созданы один раз внутри LXC;
/opt/grimmory/.envимеет mode0600, значения не выводились и не добавлялись в Git. - Compose запустил pinned Grimmory
v3.2.4и MariaDB11.4.8; первый startup применил 142 Flyway migrations и занял около 6 минут. Health wait увеличен до 10 минут. - После reboot LXC оба контейнера вернулись в
healthy; MariaDB не слушает host/LAN port, Grimmory привязан к192.168.1.34:6060. - UFW ограничивает SSH/
6060LAN и OpenVPN,9100разрешён только от monitoring LXC. ОтдельнаяGRIMMORY-FILTERchain вDOCKER-USERзакрывает обход UFW через Docker published port. - Caddy site применён. Проверены public health
200, сертификат Let's Encrypt до2026-10-26и WebSocket/STOMP Upgrade101на/ws. - Prometheus targets: Node Exporter
up=1, LAN healthprobe_success=1, public Grimmory probeprobe_success=1; TLS expiry покрыт существующим alert. - CT
149добавлен вhomelab-pbs-daily-cloudи PBS audit. Первый PBS backupct/149/2026-07-28T14:47:32Zуспешен, размер2583749785bytes; audit success, file-level restore трёх объектов проверен. - Ограничение PBS: storage
dataне поддерживает native LXC snapshots, поэтому job с modesnapshotавтоматически перешёл вsuspend; фактическая пауза гостя составила около 1 секунды. - Создан restic snapshot
a3b012abв Yandex Disk. Включены atomic MariaDB dump,/opt/grimmory/data, Compose, manifest и зашифрованная restore-конфигурация; books/BookDrop/raw MariaDB исключены.restic checkи restore непустогоgrimmory.sqlпрошли. - Повторные runs
pve-grimmory.yml,reverse-proxy-grimmory.yml,pve-backup-jobs.yml, Grimmory offsite/audit и limited monitoring завершились сchanged=0. - Обновлены Obsidian current state,
Log.mdиContext.md. - Reverse DNS для
.34содержит stale имя телефона, но ARP на обоих PVE nodes показывает MAC LXCbc:24:11:43:75:31. Нужно исключить.34из DHCP pool или закрепить reservation, чтобы предотвратить будущий конфликт.
Требует участия пользователя
- Создать первого локального администратора и сохранить пароль в password manager.
- Отключить public registration, создать обычного reader-пользователя.
- Создать библиотеку
/books, выполнить тестовый BookDrop import. - Включить OPDS, создать отдельные OPDS credentials и выдать
Access OPDS. - Проверить EPUB, PDF и FB2 на BOOX Go 7 через Wi-Fi и внешнюю сеть.
- После создания OPDS пользователя решить, добавлять ли credential в synthetic monitoring; сейчас OPDS smoke test ручной.
- Создать DHCP reservation/exclusion для
192.168.1.34. - Полный restore drill CT и восстановление dump во временную MariaDB остаются отдельной плановой операцией; file-level restore и dump restore-from-restic уже проверены.
2026-07-28 — Caddy KOReader и OPDS compatibility
reverse-proxy-grimmory.ymlразделяет device endpoints/api/koreader/**и/api/v1/opds/**в отдельный Caddy handler.- Для этих endpoints upstream compression отключён и установлен
Accept-Encoding: identity; Caddy не добавляет proxy Basic Auth и не изменяетAuthorization,X-Auth-*,Range,WWW-Authenticateили download headers. - Запросы с
Accept-Encoding: gzip, brподтверждённо возвращают401безContent-Encodingдля KOReader и OPDS. Caddy validate, Grimmory health и idempotent rerun успешны. - Дополнительно исправлен Grimmory OPDS
500из-заNo acceptable representation: Caddy нормализуетAcceptтолько для Atom/OpenSearch catalog routes, оставляя originalAcceptдля book download/cover routes.