Retirada reversible de WP-Pulse — plan, 21 octubre¶
Decisión del usuario: retirar WP-Pulse completo. El plan inicial se conserva
como histórico; la ejecución comprobada se registra al final. Fuente retirada
fusionada en main 520b327; target Prometheus y rutas Caddy ya reconciliados.
Las máscaras propias se aplicaron a las 17:22 bajo el nuevo epoch e269; CT281
quedó detenido con onboot0 y readback independiente a las 17:31. No se eliminan
datos, discos ni copias. Jellyfin
y Remote-Pulse son servicios distintos y quedan fuera del alcance.
Evidencia y elección del alcance¶
Lectura nativa 16:25:35 CEST: CT281 pertenece a proxmox/pmx50, running; padre
Hay tres timers enabled/waiting: wp-pulse-backup, wp-pulse-jev-risk y wp-pulse-jev-spam. El backup oneshot estaba inactivo, el siguiente disparo 03:31:41del22oct; los Jev oneshot estaban inactivos. El API contiene además reaper, scheduler, alert_tick y health_cron_tick dentro del lifespan: detener su unidad interrumpe esos bucles; no son cuatro servicios independientes.
Docker:17 contenedores running. Dieciséis pertenecen a cuatro proyectos de preview; sus cuatro config_files están bajo /srv/wp-pulse/sites//_stack//. El restante es dozzle-agent sin proyecto Compose. Todos tienen restart unless-stopped. No hay referencias WP en los crontabs root/wp observados; esto no inspecciona todos los cron.d o schedulers posibles.
| Alcance | Qué obtiene | Límite |
|---|---|---|
| Sólo API/web/worker + timers | Retira control y cron interno | Deja16 previews, Docker, PG y Redis consumiendo RAM; no cierra la retirada completa |
| CT281 dedicado: onboot0 + shutdown ordenado | Retira conjuntamente API, previews, bases locales y agentes de observación de ese CT; conserva discos | Preferido, tras acreditar que no sirve a cargas ajenas, drenaje y backups no activos; no ejecutar aún |
El coordinador observó memory.current1695,14MiB, anon1262,01MiB, swap0 a las16:20. No es una medición posterior al apagado ni una garantía de ahorro exacto. El N-1 fresco de16:15 faltaba1229,57MiB (el de14:38, −2006,68MiB, es histórico): retirar WP mejora capacidad, pero no acredita por sí solo N-1 ni resuelve la causa de los resets.
Fuentes y automatismos que deben quedar reconciliados¶
- Provisionado: ansible/playbooks/wp-pulse-bootstrap.yml invoca wp_pulse_host. El rol no está en playbook.yml ordinario; role_groups del inventario sólo se escribe como metadato por baseline. Su systemd_units.yml habilita API/worker, lxc_create configura el CT y caddy_route despliega snippets: bloquear el entrypoint retirado antes de tareas mutantes/SOPS. Mantener inventario para recuperación y observación, con estado retired explícito.
- Despliegue manual: services/wp-pulse-server/scripts/deploy.sh copia API/web, sincroniza dependencias, migra y reinicia. Debe fallar al inicio por retirada, sin SSH ni acceso a secretos. CI web produce artefactos; no es despliegue automático. Conservar fuentes y locks para recuperación histórica.
- Rearranque oculto: wp_pulse_host/tasks/filesystem.yml genera logrotate con reload-or-restart de API/worker. Stop+disable aislados no bastan si ese hook se ejecuta después. CT onboot0 evita el arranque ordinario completo; también preparar bloqueo persistente de unidades propias o reconciliar ese hook si el CT se vuelve a encender sólo para mantenimiento. No afirmar una máscara activa hasta aplicarla y verificarla; los fragmentos locales /etc requieren conservar su copia antes de sustituirlos por máscaras.
- Rutas fuente: Caddyfile.full-routing importa wp-pulse.caddy y cuatro snippets preview; wp-pulse.caddy define los dos hostnames API/web. Son seis sitios lógicos en cinco imports. Retirar sólo esos imports/objetos, preservando rutas ajenas, validación Caddy y rollback propio. La fuente del renderer central no contiene WP en tunnel-static.yaml; labels efectivos acreditados: ninguno de los 17 contenedores publica tunnel.hostname.
- Monitor de producto: prometheus.yml.j2, blackbox-https-external, contiene sólo el target WP específico; alerts.yml.j2 contiene WPPulseDown. Retirar esos dos objetos propios en fuente; retirar el target runtime mediante transacción admitida, nunca sobrescribir todo CT217 desde un template histórico. La transacción Cloudflared recién integrada sólo admite sus dos reglas y no autoriza este cambio lateral. El exportador genérico CT281 de host_vars/proxmox.yml no es un checker de aplicación: distinguir retired/stopped de avería y conservar observación útil.
- No se encontró contrato WP registrado en los manifiestos fuente de scripts/service-onboarding; no retirar otros contratos VM208 por asociación.
- Agentes: host_vars/wp-pulse-host.yml habilita Trove Docker local. Dozzle vive dentro del CT y no es el servidor Dozzle de otros nodos. La lectura de las 16:36 acredita un único mount del socket Docker local y DOCKER_HOST ausente o Unix local; no observa otros nodos.
- Datos: /srv/wp-pulse/sites, versions, releases, secrets, backups; /opt/wp-pulse-server, PG local16/base wp_pulse y Redis se conservan. PG escucha sólo localhost y no tiene bases no-WP ni clientes externos/no-WP observados; Redis está limitado a loopback, sin clientes externos y sin claves. Son observaciones fechadas, no garantía permanente hasta la parada.
- Backup: timer propio ejecuta backup.sh (PG + secrets), puede publicar offsite o heartbeat si está configurado. No ejecutarlo ni leer/exportar esos valores. Conservar artefactos y cobertura PBS/ZFS existentes; retirar sólo expectativas del servicio deliberadamente retirado, sin borrar snapshots ni cambiar retención de otros huéspedes. PBS puede respaldar un CT detenido: no se ha probado aquí ni se debe forzar una copia para justificar el apagado.
Gates concretos antes de la única parada¶
- Revalidar padre/boot/owner/guest y estado PVE onboot, HA y tareas en curso que nombren CT281. Confirmar que no hay backup/restore/migración activos. No matar tareas ni disparar copias.
- Metadatos seleccionados de servicios productivos activos, Docker17 y mounts del único Dozzle agent: confirmar los16 proyectos WP y agente sólo Docker local; sin env/args/raw config. Confirmar ausencia de dependencias productivas ajenas (PG/Redis clientes o bases no-WP) con metadata/SELECT read-only, sin secretos exportados. Un nombre de CT no basta como prueba de dedicación.
- Reusar el lector de quiescencia WP ya revisado: contar trabajos activos de pull/push/fleet/rehearsal/report/diff/search-replace/lighthouse y cron/schedules próximos. La lectura antigua de4sites/no activos no es un lease actual. Si aparece trabajo en curso, drenar de forma explícita sin crear otros.
- Seleccionar rutas Caddy efectivas y sus productores (imports/labels/renderer), el checker WP cargado y cualquier heartbeat propio de backup; no HTTP público ni modificar identidades/credenciales. Guardar baselines/rollback locales en los huéspedes correspondientes, no traer configuraciones privadas al Mac.
Secuencia propuesta, después de revisión¶
- Publicar/reconciliar fuente retired sin restaurar ni instalar componentes; preparar retirada exacta de rutas/checks antes de apagado para evitar avisos esperables. Ningún merge de fuente sustituye readback runtime.
- Guardar configuración PVE selected/onboot previo y estados enabled de unidades y timers; ningún secreto. onboot0 sólo para281, con CAS/digest actualizado.
- Deshabilitar los timers propios y los arranques WP dentro del CT; conservar fragmentos y sus estados para recuperación. No parar Docker/Postgres de otros CTs ni tocar el Dozzle central. No borrar volúmenes ni ejecutar compose down.
- Una única petición de shutdown ordenado de281, con timeout explícito y sin force-stop automático. Si no termina: estado UNKNOWN/parcial, no repetir ni convertirlo en pct stop. Readback independiente PVE stopped/onboot0 y ausencia de tarea activa de apagado; los demás invitados/HA conservados.
- Captura conjunta posterior de memoria y N-1 con el mismo método. Acredita sólo el ahorro observado y el presupuesto resultante, no estabilidad del hardware.
Recuperación: conservar CT/discos/config/secrets/backups intactos. Revertir sólo fuente y objetos propios desde su backup con CAS; restaurar onboot/estados previos mediante revisión explícita. Encender API dispara recuperación de jobs y cron, por lo que no es una operación inocua de prueba. Revisar timers Persistent antes activarlos para no lanzar una copia pendiente ni trabajos Jev. No reinstalar, forzar restore o ejecutar deploy.sh para simplemente recuperar el servicio.
Recibos privados de esta fase¶
Directorio0700 wp-pulse-retirement-ro-20261021-f5rnigwj bajo ~/.local/state; archivos0600, sólo proyecciones, sin env ni configuración privada exportados ni mutaciones. Las consultas HTTP fueron exclusivamente API administrativa local de Caddy/Prometheus con socket acreditado, nunca productos públicos:
| Archivo | SHA256 |
|---|---|
| receipt.json, unidades y Docker16:25:35 | |
| containers-cron.json, Compose/restart y cron16:28:28 |
La segunda lectura terminó correctamente y guardó el recibo; su primer resumen local falló al ordenar una clave None (Dozzle sin proyecto). Se corrigió sólo el resumen leyendo el recibo existente; no se repitió SSH. Ninguna evidencia de esta fase representa una retirada aplicada.
Gates cerrados por lectura, 16:36–16:43 CEST¶
Los resultados siguientes reemplazan los pendientes de investigación anteriores; se deben revalidar inmediatamente antes de la actuación. No son una reserva de inactividad ni una retirada aplicada.
- CT281 sigue running, onboot=1; digest PVE
<REDACTED>; cero tareas PVE activas para 281. Su backup PBS dedicadobackup-wp-pulse-281sigue enabled, snapshot, 02:00, almacenamiento pbs; se conserva sin ejecutarlo. No se acreditó ninguna expectativa externa de heartbeat opcional: no afirmar que quedó reconciliada. - Los 17 contenedores pertenecen a previews WP y Dozzle local. PostgreSQL sólo tiene la base de aplicación wp_pulse, aparte de las bases administrativas; cero clientes externos/no-WP. Redis loopback tiene cero claves y cero clientes externos. No apareció otra carga productiva en las unidades activas leídas.
- Cuatro sites y cuatro schedules enabled; cero due o próximos en cinco minutos, cero trabajos activos de las categorías inspeccionadas. Hay cinco comandos pendientes, ninguno claimed: tres tail_log y dos two_fa_audit. No se ejecutan, cancelan ni borran para fabricar drenaje. La recuperación debe revisar esa cola antes de arrancar API, además de los timers Persistent.
- Ambos Caddy nativos contienen exactamente los cinco imports y seis hosts WP;
ningún otro host observado apunta a .211. Sus configuraciones activas tienen
SHA256
<REDACTED>. El Caddyfile vivo de ambos tiene SHA256<REDACTED>, distinto de la fuente Git: no copiar el fichero fuente completo al vivo. La retirada debe eliminar únicamente las cinco líneas exactas sobre bytes vivos fijados, validar Caddy y comparar semánticamente el resto de rutas antes de recargar un nodo cada vez; rollback propio con CAS de esos bytes. - Prometheus CT217 conserva 26 jobs y exactamente un target WP en
blackbox-https-external. WPPulseDown está inactive/health ok, duración 300 s;
format_query confirma la expresión esperada
probe_success == 0con sus selectores exactos y sin absent. No es una evaluación del producto.
Bindings de la intervención pendiente¶
CT281: padre
CONFIG /opt/observa/conf/prometheus.yml:
SHA256 <REDACTED>,
identidad full9 [54,1107915,0,0,33188,1,5987,1792526771540111738,1792526771540111738].
RULES /home/monxas/appdata/prometheus/alerts.yml, inmutable para esta retirada:
SHA256 <REDACTED>,
full9 [54,1029537,1000,1000,33204,1,40979,1792500038537142496,1792500038537142496].
Usar el helper existente services/observa/prometheus_config.py en modo normal
26→26, con token HA conservado sólo en RAM del CT. El helper genérico no acredita
por sí solo que el único delta sea un target: la preparación debe comparar el
candidato con la configuración viva fijada y demostrar eliminación únicamente
de ese target, todos los demás campos idénticos; promtool antes de escribir,
inode/CAS, reload verificado y rollback con los mecanismos existentes.
La secuencia concreta propuesta es: fuentes anti-revive primero; retirada selectiva y verificada de ruta/check; revalidación fresca de quiescencia y tareas; backup privado de unidades/estados; bloquear arranques propios y timers, incluida la vía logrotate; CAS onboot 1→0; una petición de shutdown ordenado, sin force ni retry; readback independiente stopped/onboot=0 y memoria. El bloqueo persistente de unidades/hook requiere preparación revisada antes de ejecutar: disable sólo no impide reload-or-restart ni starts manuales. No improvisar máscaras sobre fragmentos locales sin backup. Ante guard cambiado o timeout, estado parcial o UNKNOWN, lectura independiente y ninguna segunda parada automática.
El guard fuente del rol es incondicional y always antes de mutaciones; no garantiza
bloqueo frente a --skip-tags always o inclusión directa deliberada de tareas.
El deploy retirado sale con rc2 antes de SSH incluso con --check. Ninguno de esos
cambios ejecuta Ansible ni instala una unidad en runtime al fusionarse.
| Recibo adicional | Hora CEST | SHA256 |
|---|---|---|
| gates.json | 16:36:56 | |
| commands.json | 16:39:03 | |
| caddy270.json | 16:40:56 | |
| caddy271.json | 16:40:57 | |
| prometheus-check.json | 16:43:04 |
Ejecución admitida tras el nuevo arranque e269¶
El arranque de pmx50 a las 17:07:26 invalidó los planes del boot bff. No se habían
aplicado Caddy, máscaras, onboot0 ni shutdown. Se preparó una nueva admisión
explícita, conservando los artefactos anteriores sin ejecutar: padre e269,
CT281 <REDACTED_UUID>, API870 y web871. No es una
reutilización automática de bindings antiguos.
- Fuente PR114/main
520b327: guardas Ansible/deploy y eliminación de objetos WP, CI 7/7 PASS. El contrato global de alertas pasó de 92 a 91 por la única regla retirada; los contratos PBS se conservaron. - Prometheus/pmx51 a las 17:06:44–46: un target WP eliminado, 26 jobs, todo otro
campo y token HA preservados. RULES bytes/full9 intactos. A las 17:08:17–19:
cero series WP y WPPulseDown inactive/health ok. La regla runtime residual es
inerte (
==0, sinabsent); se conserva hasta limpieza específica futura. - Caddy270 a las 17:17:50 y Caddy271 a las 17:18:47: cinco imports retirados del archivo vivo y seis rutas WP del JSON administrativo. Se conserva exactamente todo otro objeto cargado, incluidos autenticación y logging. Un POST por nodo, sin incertidumbre. Backups privados durables en cada CT; ningún reinicio.
- La adaptación directa del archivo tenía logging global derivado de WP; no se relajó su comparación ni se recargó ese archivo sobre las rutas vivas. Se preservaron por separado los objetos ajenos del archivo y del admin JSON. Una futura adaptación desde archivo puede cambiar ese logging derivado; no se declara equivalencia completa file/admin ni se modificó el logging ajeno.
- Máscaras a las 17:22:36: diez nombres propios masked, tres timers inactive,
API870/web871 todavía activos, enablement links intactos. Backup duradero de
unidades/estados y recuperación parcial revisada con cuatro pruebas. Nunca se
ejecutó
mask --force, compose down ni borrado de volúmenes. - La cola pasó naturalmente de cinco a ocho comandos: cuatro tail_log y cuatro two_fa_audit, ninguno claimed. Se conservaron, sin ejecutarlos/cancelarlos para fabricar drenaje. La admisión exige tipos de lectura conocidos y cantidad acotada, cero trabajos activos/próximos y estados de unidades exactos; no exige un número fijo que estos bucles de aplicación pueden incrementar.
El primer preflight de parada no emitió CAS ni shutdown: sólo web masked reportaba NeedDaemonReload=yes, las otras nueve no; PID/start y máscaras eran correctos. Se admite únicamente ese caso si symlinks y drop-ins siguen fijados, API870/start40616126 y web871/start40618913 no cambian, y se preservan las otras guardas. No se infiere la causa del flag ni se lo ignora globalmente. Parar el CT dedicado no requiere recargar la definición activa de su web; la máscara persistente bloquea el próximo arranque. La secuencia mantiene una sola petición de shutdown real, sin force ni retry automático.
Recibo privado bajo ~/.local/state/ |
SHA256 |
|---|---|
wp-prometheus-retirement-20261021-bpf4r2_b/applied.json |
<REDACTED> |
Mismo directorio, stale-readback.json |
<REDACTED> |
wp-retirement-e269-20261021-8srb4qx6/admission.json |
<REDACTED> |
Mismo directorio, caddy270-applied.json |
<REDACTED> |
Mismo directorio, caddy271-applied.json |
<REDACTED> |
Mismo directorio, mask-applied.json |
<REDACTED> |
Mismo directorio, masked-units-diagnostic.json |
<REDACTED> |
Cierre operativo de la parada¶
El parent v2 conservó todas las guardas y admitió únicamente el caso web masked NeedDaemonReload=yes con PID/start, symlinks y drop-ins exactos; 37 pruebas del predicado y peer independiente PASS. Primero se aplicó CAS onboot1→0, sin cambiar otro campo PVE. Hubo una única petición de shutdown, timeout120/forceStop0. El observador terminó uncertain en following_shutdown; no se lanzó otra parada.
La lectura independiente a las 17:30:43 del UPID
UPID:proxmox:0000E7EE:00020A07:6AD8DA66:vzshutdown:281:root@pam: confirmó
stopped/exitstatus OK. A las 17:31:31, API status/current y pct status dieron
stopped, onboot0, mismo epoch e269 y configuración restante idéntica al backup.
La comparación de los otros27 huéspedes, 14configuraciones HA y 14estados HA no
encontró cambios. La incertidumbre inicial queda preservada como tal; el cierre
se sustenta en lecturas nuevas, sin inferir la causa del fallo del observador.
Backup PVE: /root/wp-retirement-shutdown-_8rk1y9u. Backup de unidades en el CT:
/root/wp-pulse-retirement-<REDACTED>. Datos, secretos,
bases y copias se conservan; ocho comandos de lectura pendientes siguen en DB.
No se arrancó el CT para verificar. Antes de un arranque manual futuro hay que
revisar cola y timers Persistent; Docker/previews conservan sus políticas
históricas de reinicio y también requieren revisión.
La medición conjunta posterior,17:33:53–17:34:15, da +681,3711MiB después de reservar2GiB y manteniendo Jellyfin entre los diez migrables. Es presupuesto instantáneo, no un N-1 completo ni ahorro causal frente al boot anterior.
Recibo privado en wp-retirement-e269-20261021-8srb4qx6 |
SHA256 |
|---|---|
shutdown-result-v2.json, incertidumbre inicial |
<REDACTED> |
shutdown-task-readback.json |
<REDACTED> |
stopped-independent.json |
<REDACTED> |
post-comparison.json |
<REDACTED> |