El dashboard «ChemaVX Homelab Overview» tenía 4 paneles Infinity apuntando a
api.polymarket-bot.svc.cluster.local:8000, decomisado el 2026-07-17, más dos
stat de servicios cuyos namespaces ya no existen (polymarket-bot, open-webui)
que se veían como un 0 sospechoso en vez de como ausencia. 24 -> 17 paneles, y
la rejilla de servicios reempaquetada (arrastraba huecos de borrados viejos).
La datasource Alertmanager apuntaba a un servicio inexistente (alertmanager
está desactivado a propósito) y devolvía 500. Dos sorpresas: no la apaga
alertmanager.enabled, sino grafana.sidecar.datasources.alertmanager.enabled; y
quitarla del provisioning NO la borra de grafana.db — hace falta una directiva
deleteDatasources de un solo uso. Igual para la Infinity, creada por UI.
Auditando el Deployment contra el render aparecieron dos campos puestos a mano
que el chart no genera y que sólo seguían vivos por el three-way merge de Helm,
el mismo agujero que 0501aab: GF_INSTALL_PLUGINS (ya sin uso, retirado junto al
plugin de 48 MB) y TELEGRAM_BOT_TOKEN/CHAT_ID, que sí son críticos — el contact
point usa ${TELEGRAM_BOT_TOKEN}, así que reconstruir el release desde cero
habría dejado las alertas mudas. Declarados ya en values.
Verificado: render == desplegado salvo defaults del API server; Grafana
reiniciada y arrancando sin errores, 1 datasource, 25 dashboards, 5 reglas y el
contact point de Telegram con token resuelto.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Los sidecars de dashboards y datasources autentican contra la API de Grafana con
las credenciales del secret del chart, y recibían 401 en cada recarga. El valor
en values era adminPassword: admin, que no es la contraseña de nadie.
Lo que apareció al investigarlo cambia el diagnóstico que traíamos anotado. La
cuenta admin local se configuró el 26-abr y NO se ha usado desde entonces
(last_seen_at y updated, ambos de esa fecha): no hubo ninguna rotación reciente
en la UI. El acceso real a Grafana es por Authentik — de ahí el usuario akadmin
—, así que la contraseña de esa cuenta no la sabía nadie y era irrecuperable,
por ser un hash bcrypt.
Conservar la contraseña existente era por tanto imposible: no había ninguna que
conservar. Se genera una aleatoria de 28 caracteres, se escribe en el Secret
grafana-admin y se resetea la de Grafana para que coincidan (leyendo del secret
por stdin: nunca pasa por argv ni se imprime). La cuenta queda como acceso de
emergencia; nadie tiene que memorizarla. Para leerla:
kubectl get secret grafana-admin -n monitoring \
-o jsonpath='{.data.admin-password}' | base64 -d; echo
El secret se crea FUERA del chart y se referencia con grafana.admin.existingSecret,
en vez de parchear el secret que genera Helm: con adminPassword en values, el
siguiente upgrade lo habría revertido a "admin". Es la misma trampa que el
montaje del alerting de hace un rato, y por eso adminPassword desaparece de git.
Verificado por el camino real y no por deducción: creando un ConfigMap de prueba
con la etiqueta grafana_dashboard, el sidecar escribe el fichero y la recarga
responde 200 OK "Dashboards config reloaded" — antes 401. ConfigMap de prueba
eliminado. Grafana sano tras el rollout (database ok, 12.4.2), las 5 reglas de
alerta provisionadas siguen activas, 25 dashboards y los 3 ficheros de alerting
en su sitio.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
El ConfigMap grafana-alerting (reglas provisionadas + contact points + política
de notificación → Telegram) se monta en el Deployment de Grafana, pero el chart
NO generaba ese volumen: se había añadido a mano en algún momento y sobrevivía
únicamente por el three-way merge de Helm, que preserva lo que no aparece ni en
el render viejo ni en el nuevo.
O sea que todo el alerting de Grafana dependía de un accidente afortunado. Un
`helm upgrade --force`, un `kubectl replace` desde la salida del chart, un
cambio del chart que reestructurase `volumes`, o reconstruir el release desde el
fichero de valores que acabo de añadir en el commit anterior — cualquiera de las
cuatro se lo habría llevado por delante. Y en silencio: los paneles seguirían
pintando igual, solo dejarían de llegar los avisos.
Se declara con grafana.extraConfigmapMounts. Comprobado antes de aplicar que el
spec renderizado es equivalente al vivo (subPath/readOnly a null y defaultMode
420 son los valores por defecto), así que el pod NO se ha reiniciado: mismo
nombre antes y después. Y verificado lo que de verdad importaba: renderizando
SOLO desde values-kube-prometheus-stack.yaml, el volumen aparece — el fichero ya
basta para reconstruir el release entero sin perder el alerting.
Encontrado haciendo el chequeo posterior al upgrade de las alertas de ruido, no
por el upgrade en sí: llevaba así desde que se montó.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Tres eran falsos positivos de k3s: KubeControllerManagerDown, KubeSchedulerDown
y KubeProxyDown llevaban 92 DÍAS disparadas en severidad critical. En k3s esos
tres componentes van embebidos en el proceso del servidor y no exponen las
métricas que espera el chart, así que sus ServiceMonitors nunca conectaban: la
alerta no medía la salud de nada, solo la ausencia de un target imposible.
La cuarta, PrometheusNotConnectedToAlertmanagers, llevaba disparada desde el
reinicio del máster del 16-jul. La causa es de diseño: alertmanager.enabled es
false y los avisos van por Grafana→Telegram y Kuma→Telegram. Ya se había
desactivado el grupo de reglas `alertmanager`, pero esta alerta vive en el
grupo `prometheus`, así que sobrevivió; se quita por nombre con
defaultRules.disabled.
Tres "critical" permanentes no son un aviso, son entrenamiento para ignorar el
panel: cuando algo se rompa de verdad, se perderá entre el ruido.
Hecho por Helm y no a mano (rev 1 → 2, mismo chart 83.2.0), que es lo único que
sobrevive a un futuro upgrade. Verificado ANTES de aplicar renderizando el chart
con y sin los valores nuevos: el diff quita exactamente esas 4 alertas (141 →
137) y ningún recurso nuevo aparece. Comprobado además que el render reproduce
el manifiesto ya desplegado, o sea que no había ediciones a mano que el upgrade
fuese a aplastar.
Después: 1 sola alerta disparada (Watchdog, el latido intencionado del chart,
severity none) y los 15 targets de scrape en up — antes 3 fallaban siempre.
Grafana sano (api/health ok, 12.4.2) y su contraseña rotada intacta, porque
GF_SECURITY_ADMIN_PASSWORD solo aplica en el primer arranque.
Se añade values-kube-prometheus-stack.yaml: este namespace no está bajo ArgoCD y
hasta hoy los valores del release solo vivían dentro de Helm, sin forma de saber
cómo estaba configurado sin interrogarlo. Se refresca el export del ConfigMap de
reglas y se borran 3 dashboards que el upgrade eliminó del cluster.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Cierra el cambio de orden del flujo editorial. Con 'on', ResearchOwl escribía
el SEO en el borrador nada más generarlo — pero el SEO es DERIVADO del
artículo, y la revisión editorial posterior cambia el texto: la meta acababa
describiendo algo que ya no existía y había que rehacerla a mano en cada post.
Caso real del 2026-07-20: la meta del artículo belga decía 'A declassified
case unsolved' cuando el artículo argumenta que los datos crudos siguen
clasificados.
Con 'dryrun' el borrador se crea sin SEO y autofill solo lo PROPONE por
Telegram. El SEO se deriva del texto FINAL en el remate (seo_finish.py), que
además escribe el feature_image_alt — imposible en generación porque la imagen
destacada aún no se ha elegido (de ahí el 'human adds later' de autofill.py,
un paso humano que no ocurría: 9 posts publicados sin alt entre el 29-jun y
el 16-jul).
Contrapartida asumida: si un post se publica SIN pasar por el remate, saldrá
sin SEO. Red de seguridad: seo_watch marca meta_description.missing como HIGH,
y seo-check sigue disponible como puerta pre-publicación.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Elimina manifests openclaw/ y argocd/application-openclaw.yaml
- backup-system: fuera del backup diario, crown-jewels, expected y retain;
fuera el mount hostPath del cronjob y la fila de RESTORE.md
- monitoring: eliminado panel openclaw del dashboard homelab-overview
- n8n: openclaw y polymarket fuera del health-check (espejo del workflow vivo)
- cluster-wide/namespaces.yaml: fuera openclaw + añadidos separadores '---'
que faltaban (el archivo era un único doc YAML donde ganaba el último ns)
Cluster ya limpio: app ArgoCD, namespace, PVC/PV, RBAC cluster-scoped y
monitor de Uptime Kuma. Backup final: ~/decommissioned/openclaw-final-2026-07-18.tar.gz
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- fix: card ze apunta a /ze/ (el redirect de nginx a http:// caía en 404 de Traefik)
- fuera Open WebUI (chat.chemavx.xyz no existe) y card duplicada de polymarket en Services
- nuevos servicios: Uptime Kuma, Infisical, Trilium, Files, Umami, Ollama
- Projects: blogs The Exclusion Zone (EN) y Zona de Exclusión (ES) + ResearchOwl
- badges de infra bajo el header y subtítulos en las cards; Status en el footer
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fase final de la decomisión: namespace y Application borrados del cluster,
manifests fuera de git. Dump definitivo verificado y offsite en
/data/backups/backups/polymarket-decommission/ (espejo en mega:k3s-backups/).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
El bloque [b2] ya vive en RCLONE_CONF (Infisical /backup-system); los 3
CronJobs vuelven a rclone-conf-infisical y se elimina el secret
out-of-band rclone-conf-b2.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- b2-crown-jewels.sh: sync diario 04:00 de las 2 copias mas recientes de
cada servicio irremplazable (todo menos uptime-kuma) a
b2:chemavx-k3s-backups/crown-jewels/ (~2GB, tier gratuito).
- reddit-intel.sh sube tambien a B2 (retencion 3d).
- verify.sh comprueba Mega (completo) + B2 (crown jewels).
- Config via Secret out-of-band rclone-conf-b2 (mega+b2, kubectl, NO git);
consolidar en Infisical RCLONE_CONF cuando se anada [b2] por UI (la
identity del operator es read-only por API).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
El k3s.yaml del host apunta a 127.0.0.1:6443 (inalcanzable desde el pod).
El script antiguo solo funcionaba porque su KUBECONFIG apuntaba a un
fichero inexistente y kubectl caia en fallback al SA. Se hace explicito:
unset KUBECONFIG + RBAC con apps/deployments,statefulsets get (para
kubectl exec deploy/...) y se retira el mount del kubeconfig.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- backup.sh: fix openclaw (tar-eaba un hostPath stub vacio, 104 bytes/dia
desde hace meses); ahora resuelve el PVC real via local-path.
- Nuevos servicios: ghost-en/zona-exclusion (VACUUM INTO consistente via
node del pod + tar del content), gitea (sqlite .backup + repos + conf,
sin packages 6.4G reconstruibles), researchowl (sqlite backup API via
python3), roswell-pg, umami-pg, infisical-pg, trilium, vaultwarden,
uptime-kuma (sqlite3 .backup), filebrowser-db, home-master (sin ~/.ssh
ni caches), k3s token + /etc/rancher/k3s (cadena de restauracion).
- Validacion por artefacto: gzip -t + tamano minimo; el job sale 1 si
falta algo -> alerta Grafana->Telegram existente.
- rclone-mega: de semanal a diario (03:30); --exclude reddit-intel/.
- Nuevo CronJob reddit-intel-backup en n97 (unico dato del worker fuera
de k3s; su cron local escribia en el mismo disco).
- verify.sh: lista completa (20 servicios + reddit-intel), diario,
ventana de frescura 3 dias (BusyBox-safe).
- backoffLimit 1 (retry recrearia artefactos y romperia la retencion).
- RESTORE.md: runbook con la cadena token->state.db->infisical-secrets.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
El pod fue OOMKilled el 2026-07-10 durante un research: 20 fuentes
concurrentes con PDFs grandes y pdfplumber superaron 1Gi. El scraper
se endurece en paralelo (cap PDF 15MB, contenido 300k chars), pero el
margen extra evita que un pico legítimo mate la tarea en memoria.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Para que el workflow 'Health Check General' lea el token del bot de notificaciones
via process.env en vez de hardcodearlo (rotación 2026-07-09).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- openclaw/golden/openclaw.json: botToken → placeholder (referencia Infisical)
- n8n/health-check-code-node.js: literal → process.env.TELEGRAM_BOT_TOKEN
Token del bot @chemavx_bot rotado 2026-07-09 (BotFather). El valor vivo se
gestiona en Infisical homelab/prod/telegram. NOTA: el token viejo sigue en el
HISTORIAL de git; la revocación en BotFather es lo que lo neutraliza.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Regla homelab-backup-job-failed (kube_job_status_failed{namespace="backup-system"} > 0)
en el grupo homelab-infra; cubre backup, rclone-mega-backup y backup-verify.
Aplicado con kubectl replace + rollout restart de Grafana (namespace manual).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
App corre en docker compose en n97 (semana de calibración). El app-of-apps
no es recursivo: nada de este directorio se sincroniza hasta aplicar a mano
argocd-app.yaml. Checklist de activación en PROMOTION.md (imagen construida,
secrets en Infisical /reddit-intel, copia de la BD al PVC).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Merge de feat/portfolio-polymarket-archived con las cifras finales de
cierre (12 trades, +$247.78 realized, 239k evaluaciones, 81 días).
El widget deja de consultar la API (apagada) y pasa a tarjeta estática
con enlace al case study.
Primer paso del apagado ordenado: se detiene el ciclo de evaluación con
postgres aún vivo, para que el dump final sea la foto exacta del cierre
sin ciclos a medias. Postgres y cronjobs se apagan en commits siguientes.
Se retiran las suscripciones on-sync-failed / on-health-degraded: durante
el apagado (F3) generarían ruido sin valor. No hay trigger 'informativo'
viable: escalar a 0 vía git deja la app Synced/Healthy. El resto de apps
conservan sus notificaciones.