feat: infrastructure dashboard (Homepage) generated from the service registry
lint / yamllint + ansible-lint + syntax-check (push) Canceled after 0s

playbooks/dashboard.yml deploys Homepage as a second compose stack on the
monitoring LXC (CT 155) next to Uptime Kuma and renders its config from
homelab_services: one tile per service, link to its UI, grouped by Proxmox
node. Adding a service to the registry is enough — no second service list.

- new registry consumer: playbooks/dashboard.yml + playbooks/templates/homepage-*.j2
- homelab_dashboard_* vars in group_vars/all/services.yml (top-level, like
  homelab_reverse_proxy_*); image pinned by digest, floating tag needs an
  explicit -e dashboard_allow_floating_tag=true
- bootstrap-dashboard-pve-token.yml: read-only homepage@pve!dashboard token
  (PVEAuditor) for the Proxmox widget, secret in the root .env as DASHBOARD_PVE_*
- Makefile: dashboard, dry-dashboard, bootstrap-dashboard-token
- container binds the LAN address only (192.168.1.30:8082), not published via Caddy
- docs: architecture.md Monitoring section, plan.md active task, consumer lists

Deployed to CT 155 on 2026-09-03: container healthy, http://192.168.1.30:8082/
returns 200, `make dashboard` idempotent, `make validate` and `make lint` green.
Pending operator steps: `make bootstrap-dashboard-token` (blocked in the agent
session as credential creation) and an Uptime Kuma status page with slug homelab.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KbuZrUoevfBgCpf5DCF4DG
This commit is contained in:
Dmitry
2026-09-03 09:17:16 +03:00
co-authored by Claude Sonnet 5
parent ca48ef2696
commit 05d8c748ab
13 changed files with 484 additions and 2 deletions
+2 -1
View File
@@ -49,7 +49,8 @@ break-glass восстановления и затем должны быть о
- `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.
- `playbooks/validate.yml` — сверка реестра с фактическим состоянием Proxmox;
- `playbooks/dashboard.yml``services.yaml` для Homepage-обзора инфраструктуры.
Остальное (`pve-*.yml`, `status.yml`, monitoring, SSH config) по-прежнему
дублирует значения и должно меняться согласованно.
+12
View File
@@ -191,6 +191,10 @@ bootstrap-pve-token: ## Выпустить Proxmox API-токен прямо с
bootstrap-monitoring-token: ## Выпустить read-only PVE-токен для мониторинга
$(ANSIBLE_PLAYBOOK) playbooks/bootstrap-monitoring-pve-token.yml $(EXTRA)
.PHONY: bootstrap-dashboard-token
bootstrap-dashboard-token: ## Выпустить read-only PVE-токен homepage@pve!dashboard для виджета Proxmox
$(ANSIBLE_PLAYBOOK) playbooks/bootstrap-dashboard-pve-token.yml $(EXTRA)
.PHONY: bootstrap-ansible-user
bootstrap-ansible-user: ## Завести сервисный аккаунт ansible на shell-хостах (нужен sudo)
$(ANSIBLE_PLAYBOOK) playbooks/bootstrap-ansible-user.yml $(ASK_BECOME) $(EXTRA)
@@ -237,6 +241,14 @@ gyro: ## Настроить gyro-аллокатор в production CT 156 (Tofu-m
uptime-kuma: ## Развернуть/обновить Uptime Kuma на monitoring LXC
$(ANSIBLE_PLAYBOOK) playbooks/uptime-kuma.yml $(EXTRA)
.PHONY: dashboard
dashboard: ## Дашборд-обзор инфраструктуры (Homepage) на monitoring LXC, конфиг из реестра
@$(LOAD_ENV); $(ANSIBLE_PLAYBOOK) playbooks/dashboard.yml $(EXTRA)
.PHONY: dry-dashboard
dry-dashboard: ## Предпросмотр dashboard.yml (--check --diff)
@$(LOAD_ENV); $(ANSIBLE_PLAYBOOK) playbooks/dashboard.yml --check --diff $(EXTRA)
.PHONY: offsite-restic
offsite-restic: ## Настроить offsite restic-бэкапы на Yandex.Disk
@$(LOAD_ENV); $(ANSIBLE_PLAYBOOK) playbooks/offsite-restic-yadisk.yml $(EXTRA)
@@ -19,6 +19,7 @@
# * playbooks/pve-backup-jobs.yml — списки VMID заданий по полю `backup.job`.
# * roles/backup_audit — VMID по флагу `monitoring.backup_audit_vmid`.
# * playbooks/validate.yml — сверка с фактическим состоянием Proxmox.
# * playbooks/dashboard.yml — services.yaml для Homepage-обзора инфры.
# * Человек — как справочник вместо чтения семи playbook'ов по 200-400 строк.
#
# Существующие pve-*.yml пока НЕ читают этот реестр: их перевод на
@@ -76,6 +77,35 @@ homelab_reverse_proxy_unit: homelab-caddy
# Адрес центрального хоста мониторинга (источник scrape для node-exporter).
homelab_monitoring_host_ip: 192.168.1.30
# --- Дашборд-обзор инфраструктуры (Homepage, gethomepage.dev) ----------------
# Потребитель реестра: playbooks/dashboard.yml рендерит services.yaml из
# homelab_services (плитка на сервис, ссылка на его UI, группировка по узлу).
# Живёт compose-стеком на LXC monitoring рядом с Uptime Kuma; наружу НЕ
# публикуется — доступ только из LAN и по OpenVPN.
homelab_dashboard_host: monitoring
homelab_dashboard_dir: /opt/homepage
homelab_dashboard_bind_ip: 192.168.1.30
# Порт на хосте: 3000 в модели реестра занят замороженной Grafana, 3001 —
# Uptime Kuma, поэтому Homepage слушает 8082 -> контейнерный 3000.
homelab_dashboard_port: 8082
# Образ закреплён по digest (закреплён 2026-09-03 после первого pull на CT 155).
# Обновление: docker pull <новый тег> на monitoring, взять digest
# docker inspect --format '{{ index .RepoDigests 0 }}' <image>
# и вписать сюда. playbooks/dashboard.yml падает на assert'е без @sha256
# (разовый обход — `-e dashboard_allow_floating_tag=true`).
homelab_dashboard_image: >-
ghcr.io/gethomepage/homepage:v2.2.0@sha256:753eeb0cc22ab7baad39ed47cbd1aae14e193dd1b264e965f193a9ea1d1e1bdd
# HOMEPAGE_ALLOWED_HOSTS: Homepage >=1.0 отклоняет запросы с прочих Host-ов.
homelab_dashboard_allowed_hosts: "192.168.1.30:8082"
# Виджет proxmox: URL любого узла кластера (без node: -> среднее по кластеру).
# Секрет — read-only токен homepage@pve!dashboard (роль PVEAuditor), выпускается
# playbooks/bootstrap-dashboard-pve-token.yml в корневой .env как DASHBOARD_PVE_*.
# Пустой URL -> плитка Proxmox не рендерится.
homelab_dashboard_proxmox_url: "https://192.168.1.10:8006"
# slug опубликованной Status Page в Uptime Kuma для виджета сводки up/down
# (мониторы и статус-страницы Kuma живут только в её UI — их тут не завести).
homelab_dashboard_kuma_slug: homelab
homelab_services:
# --------------------------------------------------------------------------
@@ -0,0 +1,93 @@
---
# ============================================================================
# Выпуск read-only Proxmox-токена для виджета Proxmox в Homepage-дашборде.
#
# Создаёт пользователя homepage@pve, роль PVEAuditor на / и privsep-токен
# homepage@pve!dashboard. Секрет пишется в КОРНЕВОЙ .env как
# DASHBOARD_PVE_API_USER / DASHBOARD_PVE_API_TOKEN_ID /
# DASHBOARD_PVE_API_TOKEN_SECRET — оттуда его читает playbooks/dashboard.yml
# через lookup('env', ...).
#
# Парный к playbooks/bootstrap-monitoring-pve-token.yml. Отдельный принципал,
# чтобы дашборд не зависел от кредов замороженного стека Prometheus.
#
# Запуск: make bootstrap-dashboard-token
# ============================================================================
- name: Create read-only Proxmox token for the Homepage dashboard
hosts: mini-pc
gather_facts: false
vars:
dashboard_pve_user: homepage@pve
dashboard_pve_token_id: dashboard
# .env лежит в корне репозитория, playbook_dir — это ansible/playbooks.
dashboard_pve_env_file: "{{ playbook_dir }}/../../.env"
dashboard_pve_rotate_existing_token: false
tasks:
- name: Read existing Proxmox users
ansible.builtin.command: pveum user list --output-format json
register: dashboard_pve_users_raw
changed_when: false
- name: Create the dashboard Proxmox user
ansible.builtin.command: >-
pveum user add {{ dashboard_pve_user }}
--comment 'Read-only Homepage dashboard user'
when: dashboard_pve_user not in (dashboard_pve_users_raw.stdout | from_json | map(attribute='userid') | list)
- name: Grant PVEAuditor role to the dashboard user
ansible.builtin.command: >-
pveum acl modify / -user {{ dashboard_pve_user }} -role PVEAuditor
changed_when: false
- name: Read the dashboard user tokens
ansible.builtin.command: >-
pveum user token list {{ dashboard_pve_user }} --output-format json
register: dashboard_pve_tokens_raw
changed_when: false
- name: Refuse to overwrite an existing dashboard token
ansible.builtin.assert:
that:
- dashboard_pve_token_id not in (dashboard_pve_tokens_raw.stdout | from_json | map(attribute='tokenid') | list)
fail_msg: >-
Existing dashboard token secret cannot be recovered safely. Rotate it
explicitly (-e dashboard_pve_rotate_existing_token=true) before rerunning.
when: not dashboard_pve_rotate_existing_token | bool
- name: Rotate the existing dashboard token explicitly
ansible.builtin.command: >-
pveum user token remove {{ dashboard_pve_user }} {{ dashboard_pve_token_id }}
when:
- dashboard_pve_rotate_existing_token | bool
- dashboard_pve_token_id in (dashboard_pve_tokens_raw.stdout | from_json | map(attribute='tokenid') | list)
- name: Create the separated dashboard token
ansible.builtin.command: >-
pveum user token add {{ dashboard_pve_user }} {{ dashboard_pve_token_id }}
--privsep 1 --comment 'Homepage Proxmox widget' --output-format json
register: dashboard_pve_token_created
no_log: true
- name: Grant PVEAuditor role to the separated dashboard token
ansible.builtin.command: >-
pveum acl modify / -token {{ dashboard_pve_user }}!{{ dashboard_pve_token_id }} -role PVEAuditor
changed_when: false
- name: Store the dashboard token variables in the root .env
ansible.builtin.lineinfile:
path: "{{ dashboard_pve_env_file }}"
regexp: "^export {{ item.name }}="
line: "export {{ item.name }}='{{ item.value }}'"
create: false
loop:
- name: DASHBOARD_PVE_API_USER
value: "{{ dashboard_pve_user }}"
- name: DASHBOARD_PVE_API_TOKEN_ID
value: "{{ dashboard_pve_token_id }}"
- name: DASHBOARD_PVE_API_TOKEN_SECRET
value: "{{ (dashboard_pve_token_created.stdout | from_json).value }}"
delegate_to: localhost
vars:
ansible_connection: local
ansible_become: false
no_log: true
+131
View File
@@ -0,0 +1,131 @@
---
# ============================================================================
# Дашборд-обзор всей инфраструктуры HomeLab (Homepage, gethomepage.dev).
#
# ОБЛАСТЬ ОТВЕТСТВЕННОСТИ
# Разворачивает compose-стек Homepage на LXC monitoring (рядом с Uptime
# Kuma) и ГЕНЕРИРУЕТ его конфиги из реестра homelab_services: одна плитка
# на сервис, ссылка на его UI, группировка по узлу Proxmox. Добавили
# сервис в реестр -> плитка появилась сама, второй список вести не нужно.
#
# Наружу дашборд НЕ публикуется: контейнер слушает только LAN-адрес CT 155,
# доступ из локальной сети или по OpenVPN. Внутренняя топология (IP, VMID,
# раскладка по нодам) на публичный периметр не выносится.
#
# ГРАНИЦА С reverse-proxy.yml
# Никакой общей области. Дашборд — отдельный стек на другом хосте, Caddyfile
# он не трогает.
#
# ЗАПУСК
# make dashboard (ansible-playbook playbooks/dashboard.yml)
# make dry-dashboard (--check --diff)
# Первый прогон, пока образ не закреплён по digest:
# ansible-playbook playbooks/dashboard.yml -e dashboard_allow_floating_tag=true
#
# СЕКРЕТЫ
# Виджет proxmox требует read-only токен homepage@pve!dashboard (роль
# PVEAuditor). Выпускается playbooks/bootstrap-dashboard-pve-token.yml,
# секрет кладётся в корневой .env как DASHBOARD_PVE_API_USER /
# DASHBOARD_PVE_API_TOKEN_ID / DASHBOARD_PVE_API_TOKEN_SECRET и подхватывается
# отсюда через lookup('env', ...). Без него дашборд работает — просто плитка
# "Proxmox кластер" показывает ошибку виджета, это не блокер.
#
# ТАРГЕТ
# По умолчанию homelab_dashboard_host (monitoring). Переопределяется ради
# blue-green: `-e dashboard_config_target=monitoring-new --limit monitoring-new`
# (голый --limit play не перенацеливает, а обнуляет — см. uptime-kuma.yml).
# ============================================================================
- name: Deploy the HomeLab infrastructure dashboard (Homepage) on the monitoring LXC
hosts: "{{ dashboard_config_target | default(homelab_dashboard_host | default('monitoring')) }}"
gather_facts: true
vars:
dash_root: "{{ homelab_dashboard_dir | default('/opt/homepage') }}"
dash_config_dir: "{{ (homelab_dashboard_dir | default('/opt/homepage')) ~ '/config' }}"
dash_bind: "{{ homelab_dashboard_bind_ip | default(expected_lan_ip) }}"
dash_port: "{{ homelab_dashboard_port | default(8082) }}"
dash_image: "{{ homelab_dashboard_image | default('') }}"
dash_allowed_hosts: >-
{{ homelab_dashboard_allowed_hosts | default(dash_bind ~ ':' ~ dash_port) }}
# Список узлов Proxmox, встречающихся в реестре, — по нему строятся группы.
dashboard_nodes: >-
{{ homelab_services | dict2items | map(attribute='value.node')
| unique | sort | list }}
# Секрет Proxmox-виджета из окружения (make load .env). Пусто -> плитка
# рендерится без данных. no_log на задаче, которая это пишет.
dash_pve_user: "{{ lookup('ansible.builtin.env', 'DASHBOARD_PVE_API_USER') }}"
dash_pve_token_id: "{{ lookup('ansible.builtin.env', 'DASHBOARD_PVE_API_TOKEN_ID') }}"
dash_pve_token_secret: "{{ lookup('ansible.builtin.env', 'DASHBOARD_PVE_API_TOKEN_SECRET') }}"
pre_tasks:
- name: Require the dashboard registry variables
ansible.builtin.assert:
that:
- dash_image | length > 0
- dash_bind | length > 0
fail_msg: >-
Не заданы homelab_dashboard_* в
inventory/group_vars/all/services.yml.
- name: Require the Homepage image to be pinned by digest
ansible.builtin.assert:
that:
- "'@sha256:' in dash_image or dashboard_allow_floating_tag | default(false) | bool"
fail_msg: >-
{{ dash_image }} не закреплён по digest. Выполни на
{{ inventory_hostname }}:
docker pull {{ dash_image }}
docker inspect --format '{{ '{{' }} index .RepoDigests 0 {{ '}}' }}' {{ dash_image }}
и пропиши tag@sha256 в homelab_dashboard_image. Разовый обход для
первого прогона: -e dashboard_allow_floating_tag=true
- name: Ensure the Homepage config directory exists
ansible.builtin.file:
path: "{{ dash_config_dir }}"
state: directory
owner: root
group: root
mode: "0755"
- name: Render Homepage configuration files from the registry
ansible.builtin.template:
src: "{{ item.src }}"
dest: "{{ dash_config_dir }}/{{ item.dest }}"
owner: root
group: root
mode: "0644"
loop:
- {src: homepage-settings.yaml.j2, dest: settings.yaml}
- {src: homepage-services.yaml.j2, dest: services.yaml}
- {src: homepage-widgets.yaml.j2, dest: widgets.yaml}
- {src: homepage-bookmarks.yaml.j2, dest: bookmarks.yaml}
loop_control:
label: "{{ item.dest }}"
register: dash_config_files
- name: Render the Homepage widget secrets file
ansible.builtin.template:
src: homepage.env.j2
dest: "{{ dash_root }}/homepage.env"
owner: root
group: root
mode: "0600"
register: dash_env_file
no_log: true
roles:
- role: compose_service
compose_service_name: homelab-homepage
compose_service_description: HomeLab infrastructure dashboard (Homepage)
compose_service_root: "{{ dash_root }}"
compose_service_root_mode: "0755"
compose_service_compose_file: docker-compose.yml
compose_service_compose_template: homepage-compose.yml.j2
# | bool обязателен: роль фильтрует триггеры по truthiness, а строка
# "False" из "{{ ... is changed }}" тоже истинна.
compose_service_restart_triggers:
- "{{ (dash_config_files is changed) | bool }}"
- "{{ (dash_env_file is changed) | bool }}"
compose_service_health_url: "http://{{ dash_bind }}:{{ dash_port }}/"
compose_service_health_status: [200]
compose_service_health_follow_redirects: none
@@ -0,0 +1,16 @@
#jinja2: trim_blocks: True, lstrip_blocks: True
# {{ ansible_managed }}
# Статические админ-ссылки (не из реестра). Рендерится playbooks/dashboard.yml.
- Управление:
- Proxmox cloud-pc:
- abbr: PVE
href: https://192.168.1.5:8006
- Proxmox mini-pc:
- abbr: PVE
href: https://192.168.1.10:8006
- Proxmox Backup Server:
- abbr: PBS
href: https://192.168.1.20:8007
- Uptime Kuma:
- abbr: UK
href: http://{{ dash_bind }}:3001
@@ -0,0 +1,21 @@
#jinja2: trim_blocks: True, lstrip_blocks: True
# {{ ansible_managed }}
# Рендерится playbooks/dashboard.yml (роль compose_service). Правь шаблон,
# а не файл на хосте: следующий прогон перезапишет.
services:
homepage:
image: {{ dash_image }}
container_name: homelab-homepage
restart: unless-stopped
env_file:
- ./homepage.env
environment:
HOMEPAGE_ALLOWED_HOSTS: "{{ dash_allowed_hosts | trim }}"
ports:
# Только LAN-адрес CT 155 -> контейнерный 3000. Наружу не публикуется.
- "{{ dash_bind }}:{{ dash_port }}:3000"
volumes:
- ./config:/app/config
# Docker-сокет намеренно НЕ монтируется: сервисы живут на других хостах,
# а лишний доступ к сокету на LAN-видимом контейнере не нужен.
networks: {}
@@ -0,0 +1,70 @@
#jinja2: trim_blocks: True, lstrip_blocks: True
# {{ ansible_managed }}
# Рендерится playbooks/dashboard.yml ИЗ реестра homelab_services. НЕ править
# на хосте: меняй сервисы в ansible/inventory/group_vars/all/services.yml,
# затем `make dashboard`.
#
# Правило ссылки на сервис:
# * есть proxy.domain -> https://<domain> (+ siteMonitor)
# * иначе первый веб-порт -> http(s)://<ip>:<port> (+ siteMonitor для http)
# приоритет имён портов: http > ui > setup > uptime-kuma > grafana >
# controller > pbs-api
# * ни того, ни другого -> группа Headless, только ICMP ping по ip
{% set port_priority = ['http', 'ui', 'setup', 'uptime-kuma', 'grafana', 'controller', 'pbs-api'] %}
{% if homelab_dashboard_proxmox_url | default('') | length > 0 %}
- Infrastructure:
- Proxmox кластер:
description: cloud-pc + mini-pc
widget:
type: proxmox
url: {{ homelab_dashboard_proxmox_url }}
username: "{{ '{{HOMEPAGE_VAR_PROXMOX_USER}}' }}"
password: "{{ '{{HOMEPAGE_VAR_PROXMOX_TOKEN}}' }}"
{% endif %}
{% for node in dashboard_nodes %}
- {{ node }}:
{% for name, svc in homelab_services | dictsort %}
{% if svc.node == node %}
{% set proxy = svc.proxy | default({}) %}
{% set ns = namespace(scheme='', port=0) %}
{% for pref in port_priority %}
{% for p in svc.ports | default([]) %}
{% if ns.port == 0 and p.name == pref %}
{% set ns.port = p.port %}
{% set ns.scheme = 'https' if pref == 'pbs-api' else 'http' %}
{% endif %}
{% endfor %}
{% endfor %}
{% if proxy.domain is defined %}
- {{ name }}:
href: https://{{ proxy.domain }}
description: {{ svc.role }}
siteMonitor: https://{{ proxy.domain }}
{% elif ns.port > 0 %}
- {{ name }}:
href: {{ ns.scheme }}://{{ svc.ip }}:{{ ns.port }}
description: {{ svc.role }}
{% if ns.scheme == 'http' %}
siteMonitor: http://{{ svc.ip }}:{{ ns.port }}
{% endif %}
{% endif %}
{% endif %}
{% endfor %}
{% endfor %}
- Headless:
{% for name, svc in homelab_services | dictsort %}
{% set proxy = svc.proxy | default({}) %}
{% set ns = namespace(web=false) %}
{% for pref in port_priority %}
{% for p in svc.ports | default([]) %}
{% if p.name == pref %}
{% set ns.web = true %}
{% endif %}
{% endfor %}
{% endfor %}
{% if proxy.domain is not defined and not ns.web %}
- {{ name }}:
description: {{ svc.role }}
ping: {{ svc.ip }}
{% endif %}
{% endfor %}
@@ -0,0 +1,21 @@
#jinja2: trim_blocks: True, lstrip_blocks: True
# {{ ansible_managed }}
# Рендерится playbooks/dashboard.yml. Порядок и имена групп ОБЯЗАНЫ совпадать
# с группами в services.yaml — иначе Homepage покажет их в алфавитном порядке.
title: HomeLab
headerStyle: boxed
color: slate
layout:
{% if homelab_dashboard_proxmox_url | default('') | length > 0 %}
Infrastructure:
style: row
columns: 3
{% endif %}
{% for node in dashboard_nodes %}
{{ node }}:
style: row
columns: 4
{% endfor %}
Headless:
style: row
columns: 4
@@ -0,0 +1,18 @@
#jinja2: trim_blocks: True, lstrip_blocks: True
# {{ ansible_managed }}
# Верхняя панель Homepage. Рендерится playbooks/dashboard.yml.
- datetime:
text_size: xl
format:
timeStyle: short
dateStyle: short
- resources:
label: monitoring CT
cpu: true
memory: true
disk: /
- uptimekuma:
# Требует опубликованной Status Page в Uptime Kuma с этим slug
# (мониторы и статус-страницы Kuma живут только в её UI).
url: http://{{ dash_bind }}:3001
slug: {{ homelab_dashboard_kuma_slug | default('homelab') }}
@@ -0,0 +1,12 @@
#jinja2: trim_blocks: True, lstrip_blocks: True
# {{ ansible_managed }}
# Секреты виджетов Homepage. mode 0600, в Git не попадает. Значения берутся
# из корневого .env через lookup('env', ...) в playbooks/dashboard.yml.
# Homepage подставляет их в widgets/services по имени {{ '{{HOMEPAGE_VAR_*}}' }}.
HOMEPAGE_VAR_PROXMOX_URL={{ homelab_dashboard_proxmox_url | default('') }}
{% if dash_pve_user | length > 0 and dash_pve_token_id | length > 0 %}
HOMEPAGE_VAR_PROXMOX_USER={{ dash_pve_user }}!{{ dash_pve_token_id }}
{% else %}
HOMEPAGE_VAR_PROXMOX_USER=
{% endif %}
HOMEPAGE_VAR_PROXMOX_TOKEN={{ dash_pve_token_secret | default('') }}
+15 -1
View File
@@ -21,7 +21,8 @@ gates и вызывает playbooks. `ansible.cfg` выбирает inventory, r
- `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.
- `playbooks/validate.yml` — сверка реестра с фактическим состоянием Proxmox;
- `playbooks/dashboard.yml``services.yaml` для Homepage-обзора инфраструктуры.
Остальное (`pve-*.yml`, `status.yml`, monitoring, SSH config) по-прежнему
дублирует значения и должно меняться согласованно.
@@ -114,6 +115,19 @@ Uptime Kuma в CT 146 является активным monitoring UI. Его р
Exporter roles, groups и backup metrics остаются в репозитории. Hardcoded Prometheus
targets могут расходиться с inventory, пока stack заморожен.
`playbooks/dashboard.yml` (`make dashboard`) поднимает Homepage вторым
compose-стеком на том же LXC `monitoring`: обзорная стартовая страница со
списком всех сервисов, ссылками на их UI и виджетами Proxmox/Uptime Kuma.
`services.yaml`, `settings.yaml`, `widgets.yaml`, `bookmarks.yaml` рендерятся
из `homelab_services` шаблонами `playbooks/templates/homepage-*.j2` — отдельный
список сервисов не ведётся. Контейнер слушает только LAN-адрес CT 155
(`homelab_dashboard_bind_ip`), наружу через Caddy не публикуется. Виджет
Proxmox использует read-only токен `homepage@pve!dashboard` (роль `PVEAuditor`,
`make bootstrap-dashboard-token`), секрет — в корневом `.env` как
`DASHBOARD_PVE_*`. Образ Homepage закрепляется по digest как остальные active
images; до первого закрепления плейбук падает на assert'е (обход —
`-e dashboard_allow_floating_tag=true`).
## Grimmory MCP
`tools/grimmory-mcp/src/server.js` запускает локальный stdio MCP. Grimmory API
+43
View File
@@ -2,6 +2,49 @@
## Активные задачи
### Дашборд-обзор инфраструктуры (Homepage)
Решение от 2026-09-03: поднять стартовую страницу-обзор всей инфры (список
сервисов + переходы + виджеты Proxmox/Uptime Kuma).
Выбор: **Homepage** (gethomepage.dev) вторым compose-стеком на LXC
`monitoring` (CT 155) рядом с Uptime Kuma; доступ только LAN/OpenVPN, наружу
не публикуется. Конфиг Homepage **генерируется из `homelab_services`**
`playbooks/dashboard.yml` + шаблоны `playbooks/templates/homepage-*.j2`,
новый потребитель реестра. Плитка на сервис: `href` = `https://<proxy.domain>`
для публичных, иначе `http(s)://<ip>:<порт>`; беспортовые — в группу Headless
с ICMP ping.
**Развёрнуто на CT 155 2026-09-03.** Контейнер `homelab-homepage` (`Up healthy`),
`homelab-homepage.service` enabled, `http://192.168.1.30:8082/` -> 200. Прогон
`make dashboard` идемпотентен (третий заход `changed=0`), `make validate` и
`make lint` зелёные. Образ закреплён по digest
`sha256:753eeb0c…1e1bdd`. Порт 8082 (3000 в модели реестра занят замороженной
Grafana, 3001 — Kuma) -> контейнерный 3000.
Состав:
- `playbooks/dashboard.yml`, шаблоны `homepage-{settings,services,widgets,bookmarks}.yaml.j2`,
`homepage-compose.yml.j2`, `homepage.env.j2`;
- `playbooks/bootstrap-dashboard-pve-token.yml` — read-only токен
`homepage@pve!dashboard` (роль `PVEAuditor`), пишет `DASHBOARD_PVE_*` в корневой `.env`;
- реестр: блок `homelab_dashboard_*` в `group_vars/all/services.yml`, запись в
списках потребителей (`services.yml`, `AGENTS.md`, `architecture.md`);
- `Makefile`: цели `dashboard`, `dry-dashboard`, `bootstrap-dashboard-token`.
Оставшиеся шаги оператора:
1. `make bootstrap-dashboard-token` — выпустить PVE-токен для виджета Proxmox.
(В сессии-агенте был заблокирован auto-mode-классификатором как создание
credential; репозиторная часть готова, плейбук идемпотентен и сам допишет
`DASHBOARD_PVE_*` в корневой `.env`.) После — `make dashboard` заново, чтобы
`homepage.env` подхватил секрет. До этого плитка «Proxmox кластер» показывает
ошибку авторизации — не блокер.
2. Добавить те же три ключа-заглушки в `.env.example` (в сессии путь `.env*`
заблокирован в permissions).
3. В UI Uptime Kuma опубликовать Status Page со slug `homelab` — иначе виджет
сводки up/down показывает ошибку. Не блокер.
### Вывод memoir-bot (CT 142)
Решение от 2026-09-02: сервис не используется и выводится из эксплуатации.