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
@@ -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