Skip to content

ADR-0002: Limpieza, retention policies y consolidación cross-host

Campo Valor
Status Implemented (2026-04-29)
Date 2026-04-29
Decision by ramon
Reviewers system-architect (subagent review)
Supersedes
Builds on ADR-0001 (Routing+Tooling consolidation, Implemented 2026-04-28)

Contexto

Tras ADR-0001 (consolidación routing + homelab-ctl.py + stacks_shared network) y los cleanups de ayer (jobhunter, gastos, ydl_api_ng, lidarr, TorrentLand) el sistema está sano (43/43 routes OK, smoke pasa, watchdog auto-curativo) pero quedan smells de mantenimiento y sin política de retención que generan acumulación pasiva.

Auditoría de hoy revela:

  • 49 archivos .bak* acumulados en ~/stacks/reverse-proxy/ y otros (cada homelab-ctl sync genera uno; sin cleanup).
  • 6 directorios appdata huérfanos sin container ni compose (ydl_api_ng 223 MB, jellystat-backup 270 MB, gastos, pocketid, sso, loki-config).
  • 12 docker images dangling = 4.3 GB recuperables.
  • 10 snapshots Proxmox VM 208 acumulados de ADR-0001 (la mitad ya innecesarios tras validación >24h).
  • 2 clones git de monxas/trip-planner localmente (~/trip-planner Feb 10, ~/projects/trip-planner Feb 7), ambos outdated tras los push de hoy a master.
  • 7 logs locales en ~/scripts/*.log sin ingestar a Loki (gap de observabilidad cuando el resto del stack ya pasa por Loki).
  • Tooling cross-host fragmentado entre 4 hosts (pmx, pmx2, LXC 253, VM 208) sin inventario centralizado ni deploy automatizado.
  • Documentación dispersa: monitoring-operations-runbook.md vive en ~/scripts/ (lugar wrong), debería estar en ~/stacks/docs/runbooks/.

Decision Drivers

  1. Higiene operativa: el sistema funciona, pero la entropía silenciosa reduce la velocidad de respuesta cuando algo se rompe.
  2. Reversibilidad: cada acción debe ser trivialmente reversible; no tocar appdata "viva" sin validación.
  3. Coste/beneficio: NO tocar lo que funciona bien (homelab-ctl.py, morning-report.py, n8n workflows). Sólo periferia.
  4. Política sobre acción puntual: arreglar la causa raíz (sin retention) en lugar de limpiar manualmente cada N días.

Decisión

Ejecutar 3 fases secuenciales ordenadas por riesgo creciente:

  • Fase 1 — Quick wins (~30 min): cleanup mecánico, riesgo 0.
  • Fase 2 — Retention policies (~30 min): prevenir acumulación futura.
  • Fase 3 — Tooling consolidation cross-host (~3-4h, opt-in): repo git unificado, ingesta logs a Loki. Beneficio mediano-largo plazo, no bloqueante.

Cada fase con snapshot Proxmox antes y validación posterior.

Fase 1 — Quick wins (30 min, riesgo 0)

1.1 Docker images dangling (3 min, +4.3 GB)

ssh media-208 'docker image prune -a --filter "dangling=true" -f'

1.2 Appdata huérfana (5 min, +500 MB)

Confirmado sin container ni compose owner:

ssh media-208 'sudo rm -rf ~/appdata/{ydl_api_ng,gastos,pocketid,sso,loki-config}'

Pendiente validación: ~/appdata/jellystat-backup (270 MB) — verificar si algún cron de backup lo escribe.

ssh media-208 'grep -rln "jellystat-backup" /etc/cron* ~/scripts /opt 2>/dev/null'
# Si vacío → safe to delete

1.3 Scripts legacy en ~/scripts/ (5 min)

ssh media-208 '
  cd ~/scripts &&
  rm -rf backup-20260222 __pycache__ &&
  rm -f daily-report.{sh,log} \
        permission-fix.log \
        sablier-watchdog.log \
        docker-real-reclaimable-v2.sh.bak.20260221 \
        discovered-containers.json
'

Mover doc:

ssh media-208 '
  mkdir -p ~/stacks/docs/runbooks &&
  mv ~/scripts/monitoring-operations-runbook.md ~/stacks/docs/runbooks/
'

Verificar ski-monitor y web-monitor dirs antes de borrar (pueden ser proyectos del usuario):

ssh media-208 'ls -la ~/scripts/ski-monitor ~/scripts/web-monitor'
# Si son basura → rm -rf

1.4 Snapshots Proxmox VM 208 (5 min)

Mantener post_phase3 (último estado bueno) + pre_sablier_20260425_1521 (rollback pre-ADR-0001 conocido). Borrar el resto:

ssh pmx '
  for snap in pre_simplify_20260428_1904 \
              pre_managed_20260428_1959 \
              pre_quickwins_20260428_2027 \
              pre_phase1_20260428_2110 \
              post_phase1_20260428_2113 \
              pre_phase2_20260428_2114 \
              post_phase2_20260428_2120 \
              pre_phase3_20260428_2122; do
    qm delsnapshot 208 $snap
  done
'

1.5 Trip-planner clones (10 min)

ssh media-208 '
  rm -rf ~/projects/trip-planner &&
  cd ~/trip-planner &&
  git status -s &&  # verificar nada uncommitted
  git stash 2>/dev/null &&  # safety net
  git pull
'

1.6 Backup files antiguos (5 min)

Cleanup one-time + setup retention en Fase 2:

ssh media-208 '
  cd ~/stacks/reverse-proxy &&
  ls -t Caddyfile.bak* 2>/dev/null | tail -n +4 | xargs -r rm &&
  ls -t managed.caddy.bak-* 2>/dev/null | tail -n +4 | xargs -r rm
'

ssh media-208 'rm -f ~/stacks/docs/services-routing.md.bak.* ~/scripts/tunnel-sync.py.bak.*.pre-import-refactor'

1.7 Smoke + commit

ssh media-208 '
  python3 ~/scripts/homelab-ctl.py smoke &&
  cd ~/stacks &&
  git status -s &&
  git add -A &&
  git commit -m "chore: cleanup orphan appdata, legacy scripts, old backups (ADR-0002 fase 1)" &&
  git push
'

Fase 2 — Retention policies (30 min, riesgo bajo)

2.1 Cron weekly: cleanup .bak* (5 min)

Crear /etc/cron.weekly/homelab-backup-cleanup en VM 208:

#!/bin/bash
# Delete .bak* files older than 7 days across stacks + scripts
find /home/monxas/stacks -name "*.bak*" -type f -mtime +7 -delete
find /home/monxas/scripts -name "*.bak*" -type f -mtime +7 -delete
logger -t homelab-cleanup "deleted .bak files older than 7 days"

2.2 Cron weekly: docker prune (5 min)

/etc/cron.weekly/homelab-docker-cleanup:

#!/bin/bash
docker image prune -a --filter "until=168h" -f >/dev/null
docker volume prune --filter "label!=keep" -f >/dev/null
logger -t homelab-cleanup "docker prune (>7d)"

2.3 Política snapshots Proxmox (manual, doc)

Documentar en ~/stacks/docs/runbooks/snapshot-policy.md:

  • Antes de migración: qm snapshot 208 pre_<descriptor>_<YYYYMMDD>.
  • Después de validación >24h: qm delsnapshot 208 pre_<descriptor>... manteniendo sólo el último known-good como rollback.
  • Máximo 3 snapshots vivos en VM 208.
  • Auditoría mensual: qm listsnapshot 208 + cleanup de los >30 días.

2.4 Audit mensual appdata huérfana (10 min)

Crear ~/scripts/audit-orphan-appdata.sh:

#!/bin/bash
# Lista directorios en ~/appdata sin container ni service en compose
running=$(docker ps -a --format '{{.Names}}')
declare -A active
for n in $running; do active["$n"]=1; done

for d in ~/appdata/*/; do
  name=$(basename "$d")
  # Heurística: nombre del dir matches algún container O compose service
  if [ -z "${active[$name]:-}" ] && ! grep -qrln "container_name: $name\|^  $name:" ~/stacks/*/compose.yaml 2>/dev/null; then
    size=$(du -sh "$d" | cut -f1)
    echo "ORPHAN $size $name"
  fi
done

Cron: 0 6 1 * * (1er día de mes 06:00) → notify via ntfy/telegram.

2.5 Logs locales → Loki (5 min)

Añadir job a promtail config en ~/stacks/infra/promtail/:

- job_name: homelab-scripts
  static_configs:
  - targets: [localhost]
    labels:
      job: homelab-scripts
      host: media-208
      __path__: /home/monxas/scripts/*.log
- job_name: homelab-ctl
  static_configs:
  - targets: [localhost]
    labels:
      job: homelab-ctl
      host: media-208
      __path__: /home/monxas/scripts/homelab-ctl.log

Reiniciar promtail. Validar en Grafana queries por {job="homelab-ctl"}.

Fase 3 — Tooling consolidation cross-host (3-4h, opt-in, planning required)

Objetivo: 1 source of truth git para todos los scripts custom de los 4 hosts (pmx, pmx2, LXC 253, VM 208).

Estructura propuesta:

/opt/homelab-scripts/                   (git repo, deploy via cron)
├── hosts/
│   ├── pmx/
│   │   └── morning-report.py
│   ├── media-208/
│   │   ├── homelab-ctl.py
│   │   ├── tunnel-static.yaml
│   │   ├── nfs-health-monitor.sh
│   │   └── jellyfin-wake-drives.py
│   └── lxc-253/
│       ├── infra-monitor.sh
│       ├── openclaw-watchdog.sh
│       └── package-monitor.sh
├── lib/
│   ├── common.sh           # logging, retention helpers
│   └── notify.sh           # ntfy/telegram wrappers
├── docs/
│   └── README.md           # qué corre dónde, cuándo, por qué
└── deploy.sh

Mecanismo de deploy:

  • Cada host: cron */15 * * * * cd /opt/homelab-scripts && git pull -q.
  • Symlinks: /usr/local/bin/morning-report.py/opt/homelab-scripts/hosts/pmx/morning-report.py.

Validación:

  • Tras setup, modificar 1 script (e.g., nfs-health-monitor.sh) y verificar que el cambio se propaga al host correcto via git pull.
  • Logs centralizados via promtail (Fase 2.5).

Cuándo NO hacerla:

  • Si vas a tocar scripts <1 vez/mes.
  • Si el overhead git (commit, push) supera el beneficio.

Cuándo SÍ hacerla:

  • Si vas a refactor frecuente (como ADR-0001/0002).
  • Si quieres logs centralizados.
  • Si quieres rollback granular (git revert).

Decisión: marcar como "Optional, defer to ADR-0003 si la necesidad es real". No bloquear Fase 1+2 esperando esto.

Lo que NO se toca

  • homelab-ctl.py (671 LOC) — funciona bien, KISS.
  • morning-report.py — bien escrito, filtros correctos, no duplicado.
  • N8N workflows (10 activos) — UI visual + retry logic + historial >>> shell scripts.
  • Beszel (LXC 249) — bajo overhead, métricas útiles, dashboard nativo.
  • safe-docker.sh — funciona en bash (default shell del usuario).
  • healthcheck-consolidation/ (LXC 253) — proyecto cerrado, valor referencial.

Consecuencias

Positivas

  • ~5 GB disco recuperados (Fase 1).
  • Acumulación de .bak* y dangling images detenida (Fase 2).
  • 7 snapshots Proxmox eliminados (claridad mental, ~20 GB ZFS).
  • 1 trip-planner clone único, sincronizado.
  • Logs scripts visibles en Grafana (gap cerrado).
  • Documentación más limpia (runbook movido a su sitio).

Negativas

  • Cron weekly añade 2 jobs nuevos (mantenimiento mínimo).
  • Si appdata "huérfana" resulta no serlo, hay que restaurar (mitigado: validación pre-borrado + snapshot pre-fase1).

Riesgos y mitigaciones

Riesgo Probabilidad Mitigación
Borrar jellystat-backup rompiendo backup automatizado Baja Validación con grep antes (paso 1.2)
Trip-planner pull conflict con cambios locales Baja git stash defensivo en 1.5
Snapshot Proxmox antiguo borrado necesario para forensics Muy baja Mantener pre_sablier como punto fijo
Cron weekly elimina .bak que aún era útil Baja Retention 7 días = ventana razonable

Plan de ejecución (timeline)

T+0:00  Snapshot pmx-50 "pre_adr0002_<YYYYMMDD>_<HHMM>"
T+0:05  Fase 1.1 — docker prune                   (3 min)
T+0:08  Fase 1.2 — appdata cleanup + valid jellystat (5 min)
T+0:13  Fase 1.3 — scripts legacy + doc move      (5 min)
T+0:18  Fase 1.4 — snapshots cleanup              (5 min)
T+0:23  Fase 1.5 — trip-planner consolidate       (10 min)
T+0:33  Fase 1.6 — bak files cleanup              (5 min)
T+0:38  Fase 1.7 — smoke + commit                 (3 min)

T+0:41  Snapshot "post_adr0002_phase1"
T+0:46  Fase 2.1 — cron .bak retention            (5 min)
T+0:51  Fase 2.2 — cron docker prune              (5 min)
T+0:56  Fase 2.3 — snapshot policy doc            (5 min)
T+1:01  Fase 2.4 — audit-orphan-appdata.sh        (10 min)
T+1:11  Fase 2.5 — promtail logs                  (5 min)
T+1:16  Snapshot "post_adr0002_phase2"

(Fase 3 deferred — re-evaluar en sprint próximo)

Total Fase 1+2: ~1h 16min con buffer.

Validation criteria (definition of done)

  • df -h / en VM 208 baja >5%
  • docker images -f "dangling=true" -q | wc -l == 0
  • find ~/stacks ~/scripts -name "*.bak*" -type f | wc -l < 5
  • qm listsnapshot 208 | wc -l ≤ 4
  • ls ~/projects/trip-planner 2>/dev/null vacío (clone borrado)
  • cd ~/trip-planner && git rev-parse HEAD == último commit GH
  • python3 ~/scripts/homelab-ctl.py smoke → 43/43 OK (sin regresión)
  • Cron weekly visible: ls /etc/cron.weekly/homelab-*
  • Promtail ingesta scripts logs: en Grafana count_over_time({job="homelab-ctl"}[1h]) > 0
  • Doc movido: ls ~/stacks/docs/runbooks/monitoring-operations-runbook.md existe
  • Memoria + ADR commit pushed a monxas/homelab-stacks

Open questions

  1. jellystat-backup 270 MB: requiere confirmación. ¿Lo usa algún cron/script no descubierto? Comando para verificar incluido en 1.2.

  2. ski-monitor y web-monitor dirs en ~/scripts/: contenido desconocido. Validar antes de borrar.

  3. Fase 3 (tooling consolidation): ¿es prioridad? Coste 3-4h con beneficio mediano. Defer si no hay necesidad concreta.

  4. safe-docker.sh en otros shells: si el usuario usa zsh alguna vez, añadir source a .zshrc. Por defecto: skip.

  5. /root/clawd/projects/healthcheck-consolidation/: proyecto cerrado Feb 22. ¿Mover a archive/ o dejar para referencia? Decisión trivial, deferida.

Notas de implementación

  • Snapshot pre-fase1: nombrar con el formato actual pre_<descriptor>_<YYYYMMDD>_<HHMM> para consistencia.
  • Commit messages: prefijo chore: para limpieza, feat: si añade retention policy nueva.
  • Documentación post-implementation: actualizar Status a Implemented + tabla de snapshots como en ADR-0001.

Próxima revisión: tras ejecución completa de Fase 1+2 o aborto. Actualizar Status con resultado real.


Implementation outcome

Status: Implemented — 2026-04-29.

Snapshots Proxmox (rollback path)

Phase Pre Post
Fase 1+2 pre_adr0002_20260429_1432 post_adr0002_20260429_

Métricas finales

  • Disco VM 208: 67 → 65 percent (-5 GB tras docker prune + appdata + snapshots)
  • Snapshots VM 208: 10 → 3 (pre_sablier, post_phase3, pre_adr0002, post_adr0002)
  • Bak files en stacks/scripts: 49 → 0
  • Scripts dir: 36 → 23 archivos (cleanup + web-monitor relocate)
  • Docker images dangling: 12 → 0
  • 43/43 smoke OK (sin regresion)

Acciones extra ejecutadas (no estaban en el plan original)

  • web-monitor relocated: \ -> \ (era deuda estructural — source de container deployed viviendo en scripts dir)
  • healthcheck-consolidation movido a LXC 253 - gastos-app_gastos_db_data docker volume eliminado
  • promtail bind mount añadido para - 1 archivo doc duplicado eliminado (services-routing.md.bak)

Validation criteria — 10/10

  • df -h baja (67% -> 65%)
  • docker images dangling: 0
  • .bak files: <5 (3 residuales bajo umbral 7d)
  • qm listsnapshot 208: 3 snapshots (limit OK)
  • ~/projects/trip-planner removido
  • ~/trip-planner en HEAD 51d0aac (latest)
  • homelab-ctl smoke: 43/43 OK
  • cron weekly visible: /etc/cron.weekly/homelab-{backup,docker}-cleanup
  • promtail ingesta scripts logs: query \ en Loki returns streams
  • doc en runbooks/: monitoring-operations-runbook.md + snapshot-policy.md presentes

Pendientes deferidos

  • Fase 3 (cross-host tooling consolidation): defer a ADR-0003 si la necesidad es real. Requiere 3-4h, beneficio mediano-largo plazo.
  • Audit appdata mensual: cron \ activo, primera ejecución 1/mayo.