Running Ansible¶
Comandos canónicos para ejecutar playbooks desde la Mac. Ansible se usa
en modo híbrido: el modo principal es ansible-pull (cada nodo se
auto-aplica vía un systemd timer ansible-pull.timer, cada 15min), pero
también se puede correr ansible-playbook
push-mode desde la Mac para iteración rápida y dry-runs.
Setup local (una sola vez)¶
Instalación¶
Keys + env¶
mkdir -p ~/.config/sops/age
# Si ya tienes una key generada, copiarla aquí. Si no:
age-keygen -o ~/.config/sops/age/keys.txt
chmod 600 ~/.config/sops/age/keys.txt
Añadir a ~/.zshrc (o ~/.zshenv):
export SOPS_AGE_KEY_FILE="$HOME/.config/sops/age/keys.txt"
export ANSIBLE_CONFIG="$HOME/docs-repo/ansible/ansible.cfg"
source ~/.zshrc.
SSH¶
Los nodos asumen autenticación por key (no password). Verifica:
ssh pmx-50 'hostname'
ssh pmx-51 'hostname'
ssh [email protected] 'hostname'
Si falla, añadir tu pubkey a ~/.ssh/authorized_keys del usuario destino.
No subir keys a Vaultwarden ni al repo.
Colecciones Ansible¶
requirements.yml incluye community.sops, community.general,
ansible.posix, y community.postgresql (usado por el rol wp_pulse_host).
Comandos canónicos¶
Dry-run global¶
--check: no aplica, solo simula. --diff: muestra diff de archivos cambiados.
Dry-run limitado a un grupo/host¶
Los nombres de host en --limit son los del inventario, NO los alias SSH
~/.ssh/config en la Mac tiene alias como pmx-50/pmx-51/vm208 que
apuntan a las IPs correctas — pero Ansible no los conoce. El
inventory_hostname real (lo único válido en --limit) es proxmox,
proxmox2, media-208, etc. — así están las claves en inventory.yml,
a propósito, para que ansible-pull --limit %H (que usa hostname -s,
ver más abajo) siga funcionando. Verificado contra ansible/inventory.yml
(2026-08-01).
ansible-playbook playbook.yml --check --diff --limit proxmox_nodes
ansible-playbook playbook.yml --check --diff --limit proxmox # pmx-50, NO "pmx-50"
ansible-playbook playbook.yml --check --diff --limit media-208 # NO "vm208"
Grupos definidos en inventory.yml (verificado 2026-08-01, algunos no
existían cuando se escribió esta guía por primera vez):
| Grupo | Hosts (inventory_hostname) |
Qué es |
|---|---|---|
proxmox_nodes |
proxmox (pmx-50, .50), proxmox2 (pmx-51, .51) |
Nodos físicos Proxmox |
docker_hubs |
media-208 (VM 208, .208) |
Hub Docker |
caddy_lxc |
caddy-primary (LXC 270, .40), caddy-secondary (LXC 271, .41) |
Caddy HA |
rp_servers |
rp-server (LXC 280, .205) |
Remote-Pulse server |
wp_servers |
wp-pulse-host (LXC 281, .211) |
WP-Pulse host |
direct_lxcs |
(vacío) | Reservado para LXCs con SSH directo; vacío desde que se destruyó hermesbot/LXC 101 el 2026-07-05 |
all |
todos | — |
Aplicar (push)¶
Quitar --check:
Push mode + ansible-pull timer
Si haces push manual y el timer ansible-pull.timer corre justo después con
una rama remota distinta, sobrescribe tu cambio. Para evitarlo:
1. Empuja primero a Git (git push).
2. Luego corre ansible-playbook (que tira del estado de Git).
Solo tareas con un tag¶
ansible-playbook playbook.yml --tags caddy
ansible-playbook playbook.yml --tags observability --check
Tags actuales (verificado leyendo ansible/playbook.yml completo, 2026-08-01):
Nota corregida:
hermesbot/LXC 101 sí se desplegó y luego se destruyó el 2026-07-05 (estado final archivado en el NAS,backups/hermes/hermes_state_final_20260705.tgz— ver tambiénansible/inventory.yml, comentario endirect_lxcs). No es que "nunca existiera": el agente vivo hoy es OpenClaw/Vera en CT100, un proyecto distinto. Los roleshermes_*siguen en el repo con tagneverporque no hay ningún host llamadohermesboten el inventario (grupodirect_lxcsvacío a propósito) — aplicarlos hoy es un no-op, no algo "aspiracional que nunca corrió".
baseline— usuarios, ssh, motd, base packages (todos los hosts,always)proxmox— config PVE enproxmox_nodesobservability— collectors SMART, pvesr enproxmox_nodespromtail— log shipper Promtail v3.6.10 (manual,never)nut— Network UPS Tools primary/secondary (manual,never)caddy_lxc— Caddy + keepalived full-routing en LXC 270/271 (manual,never)caddy_managed_config— wrapper Ansible dehomelab-ctl.py syncen VM 208 (manual,never)caddy_full_routing_stage/caddy_full_routing_apply— F4.f cutover steps (manual,never)hermes_agent— systemd unit +/opt/mcps/rsync, hosthermesbotya no existe en el inventario (manual,never, no-op)docker_env_render/docker_env_swap— render/home/monxas/stacks/.enven VM 208 desde SOPSpmx_env_render/pmx_env_swap— render/root/.morning-report.enven pmx-50 desde SOPShermes_env_render/hermes_env_swap— render/etc/hermes-secrets/mcp.env, hosthermesbotya no existe (no-op)alertmanager_config— despliegaalertmanager.ymla VM 208 + SIGHUP manual (manual,never)grafana_config— dashboards + contact points + notification policy + alert rules en Grafana (VM 208), vía API (manual,never)prometheus_config_render/prometheus_config_swap— render+swap deprometheus.yml/alerts.ymlcon rollback automático sipromtool checkfalla (manual,never)asus_exporter— exporter Prometheus del router ASUS, enmedia-208(manual,never)unifi_clients_exporter— exporter Prometheus de clientes UniFi, enmedia-208(manual,never)remote_pulse— despliegaremote_pulse_serverenrp_servers(LXC 280) (manual,never)
Esta lista de tags no estaba completa en versiones previas de esta guía
(faltaban caddy_managed_config, alertmanager_config, grafana_config,
prometheus_config_*, asus_exporter, unifi_clients_exporter,
remote_pulse) — todos verificados grepeando tags: en playbook.yml.
Forzar pull en un nodo (lo que hace el timer)¶
Comando corregido 2026-08-01 contra el .service real
El comando de abajo estaba desactualizado en dos cosas, verificadas
leyendo systemctl cat ansible-pull.service en pmx-50/pmx-51: (1) el
repo es privado, el pull usa una URL SSH con deploy key dedicada
([email protected]:... + --private-key /root/.ssh/id_homelab_infra), no
https://github.com/...; (2) --limit usa %H = hostname -s, que en
estos nodos es literalmente proxmox/proxmox2 — no pmx-50/pmx-51.
ssh pmx-50 'ansible-pull \
-U [email protected]:monxas/homelab-infra.git \
--accept-host-key \
--private-key /root/.ssh/id_homelab_infra \
-i ansible/inventory.yml ansible/playbook.yml \
--limit proxmox'
(Para pmx-51, mismo comando con --limit proxmox2 sobre esa máquina.)
Útil para validar que el repo está reflejado en disk sin tener que esperar el ciclo de 15min del timer.
Comprobar el timer
El pull automático lo dispara systemd, no cron. Verifica estado y próxima ejecución con:
(No aparece en crontab -l — no es una entrada de cron.)
Roles existentes¶
Tabla ampliada 2026-08-01 — faltaban 6 roles
ls ansible/roles/ tiene 16 roles hoy, no 10. Faltaban en esta guía:
caddy_managed_config, remote_pulse_server, wp_pulse_host,
asus_exporter, unifi_clients_exporter, tailscaled_rfrobredo. La
cifra de cobertura "~70%" no se pudo re-verificar (no hay un cálculo
documentado de qué es el 100%) — TODO: re-auditar cobertura real o
quitar la cifra si nadie sabe cómo se calculó.
| Role | Hosts | Qué hace |
|---|---|---|
baseline |
all | Usuarios, sudoers, SSH config, NTP, base packages |
proxmox_node |
proxmox_nodes | /etc/pve/datacenter.cfg, jobs.cfg, sysctl tuning, kernel pinning |
observability_collector |
proxmox_nodes | SMART collectors, pvesr metrics, node_exporter scrape config |
promtail |
proxmox_nodes | Promtail v3.6.10 + systemd unit + per-host LXC jobs |
nut |
proxmox_nodes | NUT primary (pmx-50, USB UPS) + secondary (pmx-51, netclient) |
caddy_lxc |
caddy_lxc (LXC 270/271) | Caddy binary + keepalived + Caddyfile + managed.caddy sync (full-routing default) |
caddy_managed_config |
docker_hubs (VM 208) | Wrapper Ansible para homelab-ctl.py sync |
hermes_agent |
hermesbot (host ya no existe en el inventario) | systemd unit hermes-gateway.service + /opt/mcps/ rsync — no-op hoy |
docker_host_env |
docker_hubs (VM 208) | Render ~/stacks/.env desde SOPS (F3.c) |
hermes_mcp_env |
hermesbot (host ya no existe) | Render /etc/hermes-secrets/mcp.env desde SOPS — no-op hoy |
morning_report_env |
proxmox (pmx-50) | Render /root/.morning-report.env desde SOPS |
remote_pulse_server |
rp_servers (LXC 280) | Deploy Remote-Pulse server (ADR-0008) |
wp_pulse_host |
wp_servers (LXC 281) | Deploy WP-Pulse host (ADR-0010) |
asus_exporter |
docker_hubs (VM 208) | Exporter Prometheus del router ASUS TUF-AX6000 |
unifi_clients_exporter |
docker_hubs (VM 208) | Exporter Prometheus de clientes UniFi |
tailscaled_rfrobredo |
— | No referenciado hoy en playbook.yml ni en ansible/playbooks/*.yml — TODO confirmar si es un rol huérfano o se invoca desde otro sitio no revisado en esta auditoría |
Out-of-scope deliberado: provisioning VMs/LXCs desde cero, Docker compose
stacks (gestionados con homelab-ctl.py), configs runtime apps (Grafana
dashboards/alertas y Prometheus config SÍ tienen tags dedicados hoy —
grafana_config, prometheus_config_render/_swap — pero corren --connection
local contra la API/bind-mount de VM 208, no gestión de paquetes/servicio).
Workflow típico¶
- Edita un template/config en el repo:
vim ansible/templates/grafana.ini.j2. - Dry-run:
ansible-playbook playbook.yml --check --diff --limit media-208 --tags observability. - Revisa el diff. Si pinta bien:
- Aplica: misma línea sin
--check. - Verifica:
ssh media-208 'systemctl status grafana'(o el nombre de host SSH que tengas en tu~/.ssh/config). - Commit + push:
git add ansible/templates/grafana.ini.j2 && git commit -m '...' && git push.
Troubleshooting¶
| Síntoma | Fix |
|---|---|
community.sops not found |
ansible-galaxy collection install community.sops |
Failed to get the data key en lookup SOPS |
Tu key no es recipient del archivo. Ver SOPS key rotation |
Permission denied (publickey) a un nodo |
SSH agent sin la key cargada: ssh-add ~/.ssh/id_ed25519 |
Cambios no se aplican aunque corra sin --check |
Tag equivocado, o when: filtro excluye el host. -vvv para ver decisiones |
kernel of pve-1 changed warning |
Reboot pendiente del host. Ver Kernel jump |