lint / yamllint + ansible-lint + syntax-check (push) Canceled after 0s
The host table listed 9 hosts against 16 in the inventory, the role tree did not match roles/, and the documented setup path used a venv that no longer works. Describe the current entry points instead: nix develop, make, and the ssh_config include that makes `ssh gitea` work by hand. Point at `make docs` as the way to regenerate the host table rather than editing it, since that is what drifted. Also record what is deliberately incomplete: lxc_docker_host and compose_service exist but are not wired into any playbook, and LXC creation is still split between direct pct create over SSH and the pve_lxc API role. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GTocXkGUUazHdKKd3r9k71
362 lines
16 KiB
Markdown
362 lines
16 KiB
Markdown
# AGENTS.md
|
||
|
||
## Project Summary
|
||
|
||
**HomeLab infras** is a GitOps-like repository for managing home infrastructure through 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)
|
||
|
||
## 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.
|
||
|
||
## Obsidian Integration
|
||
|
||
Project documentation lives in `/home/ada/Documents/Vaults/SecondBrain/02 Projects/HomeLab/`. Use it as wiki:
|
||
|
||
- **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`.
|
||
|
||
When introducing infra changes, update the relevant Obsidian note to keep documentation in sync.
|
||
|
||
## Operating Model
|
||
|
||
- 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
|
||
|
||
Окружение собрано в Nix — venv и системные пакеты не нужны.
|
||
|
||
```bash
|
||
nix develop # или один раз: direnv allow
|
||
|
||
# Один раз на клон: galaxy-коллекции
|
||
ansible-galaxy collection install -r ansible/requirements.yml -p ansible/collections
|
||
```
|
||
|
||
Дальше всё управление идёт через `make` из каталога `ansible/`:
|
||
|
||
```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` без аргументов печатает `help`. Опасные цели (`mihomo-harden`, `monitoring`,
|
||
`update-all`) требуют явного `CONFIRM=1`. Дополнительные флаги — через `EXTRA`:
|
||
|
||
```bash
|
||
make status EXTRA="--limit '!gyro'"
|
||
```
|
||
|
||
`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` и защищает опасные цели.
|
||
|
||
```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 # разложить публичный ключ на все хосты
|
||
```
|
||
|
||
Обновления всегда идут в порядке: свежий бэкап/аудит -> обновление -> health-check.
|
||
Образы закреплены по `tag@sha256:digest`; floating-теги и авто-апдейтеры не используются.
|
||
## Proxmox API Tasks
|
||
|
||
Плейбукам `pve-*.yml` нужны переменные Proxmox API. Держи их в `ansible/.env`
|
||
(файл в `.gitignore`, шаблон — `.env.example`):
|
||
|
||
```bash
|
||
PROXMOX_HOST=<host>
|
||
PROXMOX_USER=<user@realm>
|
||
PROXMOX_TOKEN_ID=<token_id>
|
||
PROXMOX_TOKEN_SECRET=<secret>
|
||
PROXMOX_VALIDATE_CERTS=false
|
||
```
|
||
|
||
`make` подхватывает `.env` сам и падает с внятным сообщением, если его нет —
|
||
руками `. ./.env` делать не нужно:
|
||
|
||
```bash
|
||
make env-check # проверить, что .env на месте и заполнен
|
||
make dry-gitea # сначала всегда --check --diff
|
||
make deploy-gitea
|
||
```
|
||
|
||
Выпустить токен, если его ещё нет:
|
||
|
||
```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 сами разберут шаги.
|