docs: AI project context (docs/ai) and repository documentation refresh

- docs/ai/: stable, repo-verified context - README, architecture, tech-stack,
  edge-cases, plan (confirmed active work only), migration-tofu (the blue-green
  OpenTofu migration runbook and per-service findings), legacy-warning, links.
- AGENTS.md: slimmed to a working contract that points at docs/ai instead of
  restating it; CLAUDE.md is an adapter that @-includes it.
- README.md, ansible/README.md, ansible/roles/README.md,
  roles/lxc_docker_host/README.md: bring wording in line with the current
  control plane (Makefile entry point, registry, tofu, memoir-bot gone).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012uoq5AVK8mkBgg83Mq6o5V
This commit is contained in:
Dmitry
2026-09-03 07:06:10 +03:00
co-authored by Claude Sonnet 5
parent 5e27ba2513
commit d2e1e6876a
13 changed files with 1955 additions and 377 deletions
+118 -325
View File
@@ -1,361 +1,154 @@
# AGENTS.md
## Project Summary
## Назначение
**HomeLab infras** is a GitOps-like repository for managing home infrastructure through Ansible.
HomeLab infras - Ansible-first control plane для домашней инфраструктуры на
Proxmox VE. Желаемое состояние хранится в inventory, vars, roles и playbooks;
прямые изменения на серверах допустимы только для read-only диагностики или
break-glass восстановления и затем должны быть отражены в Ansible.
- Management: `ansible/` (playbooks, roles, inventory)
- Documentation: Obsidian vault `/home/ada/Documents/Vaults/SecondBrain/02 Projects/HomeLab/`
- Active infrastructure: Proxmox VE cluster (`cloud-pc`, `mini-pc`), PBS, OpenVPN, ZeroTier
- Archive: `archive/2026-07-proxmox-migration/` (historical NixOS/Docker configs, reference only)
Стабильный контекст проекта находится в [`docs/ai/`](docs/ai/README.md). Не
загружай весь набор по умолчанию: читай только документы, относящиеся к задаче.
## Design Goal
## Что читать
**Ansible-first infrastructure**: HomeLab configuration is managed through Ansible inventory, roles and playbooks. Direct server changes are allowed only for break-glass recovery or read-only diagnostics; afterwards the intended state must be captured in Ansible.
| Задача | Контекст |
|---|---|
| Любое изменение | `README.md`, [`docs/ai/README.md`](docs/ai/README.md), релевантная секция [`architecture.md`](docs/ai/architecture.md) |
| Inventory или новый service | `ansible/inventory/hosts.yml`, `ansible/inventory/group_vars/all/services.yml`, [`architecture.md`](docs/ai/architecture.md), [`edge-cases.md`](docs/ai/edge-cases.md) |
| Provisioning или update | `ansible/README.md`, relevant playbook/role, [`edge-cases.md`](docs/ai/edge-cases.md), [`legacy-warning.md`](docs/ai/legacy-warning.md) |
| Network, SSH, OpenVPN, Caddy | `ansible/ssh_config`, `ansible/inventory/group_vars/all/main.yml`, [`architecture.md`](docs/ai/architecture.md), [`links.md`](docs/ai/links.md) |
| Backups и recovery | `ansible/playbooks/pve-backup-jobs.yml`, `ansible/roles/backup_audit/`, [`architecture.md`](docs/ai/architecture.md), [`edge-cases.md`](docs/ai/edge-cases.md) |
| Grimmory MCP | `tools/grimmory-mcp/README.md`, [`tech-stack.md`](docs/ai/tech-stack.md), [`edge-cases.md`](docs/ai/edge-cases.md) |
| Legacy/frozen code | [`legacy-warning.md`](docs/ai/legacy-warning.md) |
| Переезд на OpenTofu | [`migration-tofu.md`](docs/ai/migration-tofu.md), `tofu/README.md`, `tofu/*.tf` |
| Текущая работа | [`plan.md`](docs/ai/plan.md); `current-task.md` является историческим планом |
## Obsidian Integration
Перед инфраструктурными изменениями прочитай релевантные заметки в Obsidian:
Project documentation lives in `/home/ada/Documents/Vaults/SecondBrain/02 Projects/HomeLab/`. Use it as wiki:
`/home/ada/Documents/Vaults/SecondBrain/02 Projects/HomeLab/`
- **Read** before making infra changes — context, decisions, constraints.
- **Update** after making changes — document non-obvious decisions, new patterns, lessons learned.
- Key files: `HomeLab.md`, `Notes/Текущее состояние HomeLab после миграции на Proxmox.md`, `Log.md`.
Основные заметки: `HomeLab.md`,
`Notes/Текущее состояние HomeLab после миграции на Proxmox.md`, `Log.md`.
После изменения обнови соответствующую заметку, если изменились topology,
операционная процедура, решение или неочевидное ограничение.
When introducing infra changes, update the relevant Obsidian note to keep documentation in sync.
## Источники правды
## Operating Model
- Hosts и groups: `ansible/inventory/hosts.yml`.
- Shared network/access vars: `ansible/inventory/group_vars/all/main.yml`.
- Service facts: `ansible/inventory/group_vars/all/services.yml`.
- SSH users, keys, ports и ProxyJump: `ansible/ssh_config`.
- Manual operations и safety gates: `ansible/Makefile`.
- Secrets: ignored `.env` в КОРНЕ репозитория; его читают и Make/Ansible, и OpenTofu.
- Human decisions и operations log: HomeLab Obsidian vault.
- User: passwords, SSH access, base network reachability.
- Agent: infrastructure changes via `ansible/` files.
- Prefer adding/updating a role or playbook over ad-hoc commands.
- For every new VM, LXC container, or managed host, provision the user's SSH public key by default unless explicitly told otherwise.
- Run smallest safe Ansible check before broader changes.
- Do not commit secrets (`.env`, tokens, keys, real passwords).
Программные потребители реестра:
## Quick Start
- `playbooks/reverse-proxy.yml` — сборка Caddyfile;
- `playbooks/ru-vps-base.yml` — стек Caddy и его закреплённый образ;
- `playbooks/pve-backup-jobs.yml` — списки VMID заданий PBS (поле `backup.job`);
- `roles/backup_audit` — VMID для аудита (флаг `monitoring.backup_audit_vmid`);
- `playbooks/validate.yml` — сверка реестра с фактическим состоянием Proxmox.
Окружение собрано в Nix — venv и системные пакеты не нужны.
Остальное (`pve-*.yml`, `status.yml`, monitoring, SSH config) по-прежнему
дублирует значения и должно меняться согласованно.
`make validate` — read-only gate, проверяющий это расхождение.
## Границы
- Active control plane находится в `ansible/`.
- `archive/2026-07-proxmox-migration/` - только историческая справка; не редактировать
и не возвращать из него конфигурацию как active implementation.
- Не редактировать generated artifacts, `ansible/collections/`, `.venv/`,
`node_modules/`, `.direnv/` и secret-bearing ignored files.
- `roles/compose_service` подключён в `playbooks/ru-vps-base.yml` (стек Caddy).
`roles/lxc_docker_host` не подключён нигде.
Не считать active service playbooks устаревшими до отдельной миграции.
- Не исправлять найденный technical debt в несвязанной задаче без отдельного решения.
- Для новой VM/LXC/managed host по умолчанию provision public SSH key пользователя,
если пользователь явно не указал иное.
## Setup и команды
Предпочтительное окружение - Nix:
```bash
nix develop # или один раз: direnv allow
# Один раз на клон: galaxy-коллекции
ansible-galaxy collection install -r ansible/requirements.yml -p ansible/collections
nix develop # или один раз: direnv allow
ansible-galaxy collection install \
-r ansible/requirements.yml \
-p ansible/collections # один раз на clone
```
Дальше всё управление идёт через `make` из каталога `ansible/`:
Работай через Make из `ansible/` или с `make -C ansible` из root:
```bash
cd ansible
make help # список всех целей — начинать отсюда
make check # связность и ожидаемые IP
make status # сводное состояние всей инфраструктуры (read-only)
make lint # ansible-lint + yamllint
make docs # актуальная таблица хостов из inventory
make deploy-gitea # playbooks/pve-gitea.yml, .env подхватывается сам
make dry-gitea # то же в режиме --check --diff
make update-gitea # бэкап -> обновление -> health-check
make -C ansible help
make -C ansible check
make -C ansible status EXTRA="--limit '!gyro'"
make -C ansible inventory
make -C ansible docs
make -C ansible lint
```
`make` без аргументов печатает `help`. Опасные цели (`mihomo-harden`, `monitoring`,
`update-all`) требуют явного `CONFIRM=1`. Дополнительные флаги — через `EXTRA`:
Локальный syntax-check всех playbooks, соответствующий CI:
```bash
make status EXTRA="--limit '!gyro'"
nix develop -c sh -c \
'cd ansible && for f in playbooks/*.yml; do ansible-playbook --syntax-check "$f"; done'
```
`gyro` использует шифрованный `host_vars/gyro/vault.yml`, поэтому для него нужен
`--ask-vault-pass` (цель `make gyro` добавляет флаг сама).
### SSH руками
Транспорт описан в `ansible/ssh_config` — один источник правды и для Ansible,
и для терминала. Чтобы заработал `ssh gitea`, добавь в `~/.ssh/config`:
```
Include /home/ada/Documents/Projects/HomeLab/infras/ansible/ssh_config
```
SSH берёт первое совпадение: Include в начале файла — побеждают настройки репозитория,
в конце — личные записи из `~/.ssh/config.d/`. На Ansible порядок не влияет, он ходит
с явным `-F`.
## Active Infrastructure
Каноничный источник — `ansible/inventory/hosts.yml` и реестр сервисов
`ansible/inventory/group_vars/all/services.yml`. Актуальную таблицу всегда можно
получить командой `make docs`, поэтому здесь — только опорная картина.
### Nodes
| Name | Role | LAN IP | Notes |
|---|---|---:|---|
| `ru-vps` | Public VPS, JumpHost, qdevice, OpenVPN server, Caddy | `157.22.231.198:3422` | OpenVPN `10.78.0.1` |
| `cloud-pc` | Proxmox VE node, storage | `192.168.1.5` | Main node |
| `mini-pc` | Proxmox VE node | `192.168.1.10` | Secondary node |
| `pbs` | Proxmox Backup Server LXC (CT 120) | `192.168.1.20` | Прямой доступ, без ProxyJump |
| `ovpn-mini` | OpenVPN gateway LXC (CT 132) | `192.168.1.23` | OpenVPN `10.78.0.2`, без ProxyJump |
| `vaultwarden` | Vaultwarden LXC (CT 140) | `192.168.1.24` | `pass.ada-dev.ru` |
| `gitea` | Gitea LXC (CT 141) | `192.168.1.25` | `git.ada-dev.ru`, SSH `:2222` |
| `memoir-bot` | Telegram memoir bot LXC (CT 142) | `192.168.1.26` | образ собирается локально |
| `mihomo` | Local proxy/UI LXC (CT 143) | `192.168.1.27` | UI `:8080`, API `:9090`, proxy `:7890/:7891` |
| `adguard` | AdGuard Home LXC (CT 144) | `192.168.1.28` | DNS `:53`, UI `:3000` |
| `docker-test` | Песочница/кандидат в CI-раннеры (CT 145) | `192.168.1.29` | |
| `monitoring` | Monitoring LXC (CT 146) | `192.168.1.30` | Uptime Kuma активен, Prometheus заморожен |
| `hermes-ai` | AI-хост (CT 147) | `192.168.1.31` | ходит наружу через mihomo |
| `emergency-bot` | Telegram-бот break-glass доступа (CT 148) | `192.168.1.32` | |
| `grimmory` | Grimmory + MariaDB (CT 149) | `192.168.1.34` | `books.ada-dev.ru` |
| `gyro` | Investment allocator (CT 150) | `192.168.1.35` | vault-переменные, outbound-only |
### Inventory Groups
- `homelab` — ru-vps
- `pve_nodes` — cloud-pc, mini-pc
- `lxc_infra` — все LXC (13 шт.)
- `monitoring_server` — monitoring
- `monitoring_exporters` — хосты с Node Exporter
- `monitoring_smart_exporters` — cloud-pc, mini-pc
- `vpn_openvpn` — ru-vps, ovpn-mini
- `shell_hosts` — ru-vps, cloud-pc, mini-pc, hermes-ai
- `servers` — все управляемые хосты
Общие переменные живут в `inventory/group_vars/`, а не в `hosts.yml`:
`group_vars/all/main.yml` (сеть, доступ, OpenVPN), `group_vars/lxc_infra/main.yml`
(LXC ходят под root без sudo), `group_vars/all/services.yml` (реестр сервисов).
### Transport
- **OpenVPN**: ru-vps (10.78.0.1) ↔ ovpn-mini (10.78.0.2), порт 8443/tcp
- **JumpHost**: SSH к нодам и LXC через ProxyJump `ru-vps`; исключения — `pbs` и
`ovpn-mini`, они доступны напрямую по LAN
- Всё это описано в `ansible/ssh_config`; Ansible подключает его через
`ansible_ssh_common_args` в `group_vars/all/main.yml` (путь считается от
`inventory_dir`, поэтому не зависит от cwd и места клона)
## Directory Structure
```
flake.nix / .envrc # Nix dev-окружение (ansible, линтеры, python-зависимости)
.ansible-lint / .yamllint # Конфигурация линтеров
.gitea/workflows/lint.yml # CI: yamllint + ansible-lint + syntax-check
ansible/
├── Makefile # ЕДИНАЯ точка входа для ручного управления (make help)
├── ansible.cfg
├── ssh_config # Транспорт: ProxyJump, пользователи, ключи
├── scripts/
│ └── gen-inventory-docs.py
├── inventory/
│ ├── hosts.yml # Только адреса и индивидуальные факты хостов
│ ├── group_vars/
│ │ ├── all/main.yml # Общие переменные
│ │ ├── all/services.yml # Реестр сервисов homelab_services
│ │ └── lxc_infra/main.yml
│ └── host_vars/gyro/ # main.yml + шифрованный vault.yml
├── playbooks/
│ ├── check.yml # Связность и ожидаемые IP
│ ├── status.yml # Read-only сводка по всей инфраструктуре
│ ├── reverse-proxy.yml # Единый Caddy-плейбук по реестру сервисов
│ ├── pve-*.yml # Создание LXC (нужен .env)
│ ├── *-update.yml # Обновления: бэкап -> апдейт -> health-check
│ └── openvpn-*.yml
├── roles/
│ ├── lxc_docker_host/ # LXC под Docker: пакеты, fuse-overlayfs, ufw
│ ├── compose_service/ # compose.yml + systemd-юнит + health-check
│ ├── pve_lxc/ # Создание LXC через Proxmox API
│ ├── backup_audit/ # Аудит PBS и restic
│ ├── monitoring_*/ # Prometheus-стек (заморожен), экспортеры
│ ├── uptime_kuma/ # Активный мониторинг
│ ├── emergency_access/ # Break-glass reverse SSH
│ ├── emergency_bot/ # Telegram-бот break-glass
│ ├── gyro/ # Investment allocator
│ ├── openvpn_gateway/ # OpenVPN транспорт
│ └── bash_config/ # Единый bash-конфиг
└── tasks/
archive/2026-07-proxmox-migration/ # Историческое, только как справка
```
Роли `lxc_docker_host` и `compose_service` созданы, но пока не подключены ни к одному
плейбуку — миграция `pve-*.yml` на них не выполнена. Пример использования и перечень
того, что меняется на живом хосте при переходе, — в `roles/compose_service/README.md`.
## Common Playbooks
Предпочитай `make` — он сам грузит `.env` и защищает опасные цели.
Grimmory MCP требует отдельный Node.js `>=22` runtime:
```bash
make check # связность и ожидаемые IP
make status # read-only сводка: хосты, юниты, диски, VPN, бэкапы
make deploy-<service> # playbooks/pve-<service>.yml
make dry-<service> # то же с --check --diff
make update-<service> # gitea | vaultwarden | adguard | mihomo | grimmory
make reverse-proxy # Caddy на ru-vps по реестру сервисов
make backup-audit # аудит PBS + offsite restic
make openvpn-check # проверка транспорта ru-vps <-> ovpn-mini
make bash-config # единый bash-конфиг на shell_hosts
make user-ssh-key # разложить публичный ключ на все хосты
npm install --prefix tools/grimmory-mcp
npm test --prefix tools/grimmory-mcp
```
Обновления всегда идут в порядке: свежий бэкап/аудит -> обновление -> health-check.
Образы закреплены по `tag@sha256:digest`; floating-теги и авто-апдейтеры не используются.
## Proxmox API Tasks
## Safety Contract
Плейбукам `pve-*.yml` нужны переменные Proxmox API. Держи их в `ansible/.env`
(файл в `.gitignore`, шаблон — `.env.example`):
- Сначала выполняй smallest safe local validation, затем bounded live check только
когда он нужен задаче.
- `make dry-<service>` использует `--check --diff`, но не является полной симуляцией
Proxmox API или command-heavy `pct` playbooks.
- `make status` read-only, но его exit code не является health gate.
- `update-all`, `mihomo-harden` и frozen `monitoring` требуют `CONFIRM=1`.
- `gyro` требует Vault password; не обходи `make gyro` без явной причины.
- Service update order: fresh backup/audit -> update -> health check. Backup не
означает automatic rollback.
- Uptime Kuma активен. Prometheus/Alertmanager/Grafana stack заморожен; не запускать
его параллельно без отдельного architecture decision.
- Active remote images обычно pin по `tag@sha256:digest`; frozen Prometheus images
tag-only. Floating auto-updaters не используются.
- Не включать cluster-wide PVE firewall без аудита всех guests с `firewall=1`.
- Не останавливать production services для fault injection без согласованного
maintenance window.
```bash
PROXMOX_HOST=<host>
PROXMOX_USER=<user@realm>
PROXMOX_TOKEN_ID=<token_id>
PROXMOX_TOKEN_SECRET=<secret>
PROXMOX_VALIDATE_CERTS=false
```
## Secrets
`make` подхватывает `.env` сам и падает с внятным сообщением, если его нет —
руками `. ./.env` делать не нужно:
- Никогда не коммить и не цитировать реальные passwords, tokens, private keys,
`.env`, Vault plaintext, PBS/restic credentials или TLS keys.
- Используй ignored `.env` в корне репозитория, Ansible Vault, runtime prompt или
external local secret file.
- `PROXMOX_ROOT_PASSWORD` (root@pam) нужен ТОЛЬКО целям `tofu-*` и только для
привилегированных полей LXC. Ansible им не пользуется. Не логировать и не
передавать в playbook vars.
- В Git допустимы только sanitized `.env.example` и encrypted Vault content.
- Secret-bearing tasks должны использовать `no_log: true`, а files - минимальные
permissions.
```bash
make env-check # проверить, что .env на месте и заполнен
make dry-gitea # сначала всегда --check --diff
make deploy-gitea
```
## Рабочий процесс
Выпустить токен, если его ещё нет:
1. Прочитай минимальный релевантный context и Obsidian notes.
2. Проверь active inventory, vars, playbook/role и связанные consumers.
3. Внеси smallest correct declarative change; не используй ad-hoc server edits.
4. Запусти минимальные local checks, затем только необходимые bounded live checks.
5. Проверь diff на broad targeting, secrets, destructive behavior и docs drift.
6. Обнови repository/Obsidian documentation для изменившихся решений и процедур.
7. В отчете перечисли changed files, проверки, неисполненные live checks и unknowns.
```bash
make bootstrap-pve-token # прямо с ноды, нужен sudo
make bootstrap-monitoring-token # отдельный read-only токен для мониторинга
```
## Secrets Policy
- Never commit real secrets to git.
- Use `.env` files (in `.gitignore`), Ansible vault, or runtime prompt.
- Store `.env.example` templates in repo.
- Keep: passwords, tokens, private keys, PBS secrets, TLS keys outside repo.
## Workflow
1. **Read Obsidian** — understand context, constraints, prior decisions.
2. **Make change** — edit/add role/playbook in `ansible/`.
3. **Test safe** — run smallest applicable check/playbook.
4. **Update Obsidian** — document non-obvious decisions and changes.
## Archive Policy
- `archive/2026-07-proxmox-migration/` contains historical NixOS, Docker Compose, GitOps configs.
- Use archived files only as reference when porting behavior to Ansible.
- Do not edit archived files for active infrastructure.
- Restore services via new Ansible roles/playbooks, not by moving old files back.
## Token Economy
Принципы минимизации токенов при управлении инфраструктурой.
### Delegate Heavy Reading
Когда нужно анализировать большие логи или выводы — делегируй субагенту:
```
agent("Read this log and summarize key errors")
```
**Субагент использует для:**
- Анализа логов (journalctl, docker logs, PBS logs)
- Summarization больших файлов
- Поиска паттернов в выводах
- Диагностики статусов
**Основной агент для:**
- Принятия решений на основе конспекта
- Сложных рассуждений над findings
- Изменения кода и архитектуры
### Prefer Scripts Over LLM
Для повторяющихся задач — скрипты, а не объяснения:
```bash
# Вместо: "проверь статус всех LXC"
ansible/scripts/check-lxc-status.sh
# Вместо: "верифицируй PBS backup jobs"
ansible/scripts/check-pbs-backups.sh
```
Паттерн: написать один раз → переиспользовать. LLM только пишет или вызывает.
### Focused Context — читай только нужное
Вместо чтения всего файла — только релевантные части:
```python
# Вместо всего inventory.yml
Read(file_path, offset=1, limit=50) # только hosts vars
# Или grep для конкретного хоста
grep("mini-pc", "inventory/hosts.yml")
```
### Tool Limits — ограничивай вывод команд
```bash
# Вместо: journalctl -u openvpn (тысячи строк)
journalctl -u openvpn --since "1 hour ago" -n 100
# Вместо: docker logs (весь лог)
docker logs --tail 50 gitea
```
Всегда ограничивай вывод: `--tail`, `--since`, `-n`, `head`.
### Batch Operations — группируй задачи
Вместо нескольких запусков — один с несколькими role:
```python
# Плохо
ansible-playbook playbooks/bash-config.yml
ansible-playbook playbooks/docker.yml
ansible-playbook playbooks/ufw.yml
# Хорошо — один плейбук
ansible-playbook playbooks/base-setup.yml # включает bash, docker, ufw
```
### State Caching — не перечитывай статичное
Структура инфры меняется редко. Не перечитывай `hosts.yml`, если структура не изменилась. Кэшируй в memory стабильные данные: network topology, storage layout.
### Structured Output — избегай повторного парсинга
Когда субагент анализирует логи — проси сразу JSON с findings:
```json
{
"summary": "OpenVPN handshake fails due to certificate expired",
"findings": ["cert expired 2026-07-01", "client retries every 5s"],
"relevant_lines": ["Jul 08 10:23:01 TLS auth error"]
}
```
Работа со структурированным результатом вместо повторного чтения логов.
### Declarative Over Imperative
Описывай желаемое состояние, а не шаги:
```yaml
# Плохо: каждый раз перечислять шаги
"Создай LXC, установи Docker, добавь пользователя..."
# Хорошо: декларативно
lxc_container:
name: "vaultwarden"
features: ["docker", "autostart"]
user: "ansible"
```
Ansible/Terraform сами разберут шаги.
Для read-only reconnaissance, Ansible safety review, syntax validation, network/log
diagnostics, backup audit и Obsidian context используй специализированные агенты из
`.opencode/agents/`. Большие logs и command outputs передавай соответствующему
read-only summarizer и всегда ограничивай `--since`, `-n` или `--tail`.