250 líneas de comentario en 151 scripts. Control verificado: el diff no toca NI UNA línea que no empiece por #, y la sintaxis de los 151 pasa. El barrido saltea heredocs y cadenas triples, y el guardián DISPARÓ 3 veces: las tres eran el MOTD que el script escribe DENTRO de la imagen construida — texto del producto, no comentario del script. Se cambiaron aparte y a propósito, que es rebranding, no limpieza. Y el hallazgo caro: casaba contra , que es el TARGET de tracing — o sea el module_path!, o sea el nombre del crate. La etapa 4 lo movió a y el script quedó casando NADA. No fallaba: imprimía cero atribuciones, indistinguible de un log sin problemas. Comprobado con el binario (RUST_LOG=info sobre zlib), no deducido. Ahora acepta las dos, y tiene que seguir aceptándolas porque los logs viejos en disco dicen la vieja. Además 14 rutas de módulo en docs, que el barrido anterior no tocó porque no es frontera de palabra.
105 lines
6.1 KiB
Bash
Executable File
105 lines
6.1 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# poda-fuentes.sh — recolector de `work/sources/`. Hermano de `store-gc.sh`, pero el criterio no
|
|
# tiene nada que ver, y confundirlos sería caro.
|
|
#
|
|
# ── QUÉ SE ESTABA ACUMULANDO ────────────────────────────────────────────────────────────────────
|
|
# Medido el 2026-08-31 en gioser: `work/sources` pesaba **79 G** — 106 árboles, media de 745 MB,
|
|
# los peores `pixi` 3,5 G y siete apps cosmic por encima de 3 G cada una. El volumen del store
|
|
# (`/dev/sdb`, 246 G) estaba al **97%, 8,8 G libres**, con la granja escribiendo ahí.
|
|
#
|
|
# Y esos 3,5 G no son fuente: el árbol de `work/sources/<nombre>-<sha>` es a la vez el sitio donde
|
|
# se extrae Y donde se compila (takana parchea y vendorea in situ). Lo que engorda es el `target/`
|
|
# de cargo, los `.o`, el `vmlinux`. Es RESIDUO DE BUILD, no código.
|
|
#
|
|
# ── POR QUÉ BORRARLO NO CUESTA NADA (esto es lo que hace segura la poda) ────────────────────────
|
|
# No es una caché. `takana-build/src/fetch.rs` tiene exactamente dos caminos —`fetch_git` (línea
|
|
# 73) y `fetch_tarball` (línea 234)— y **los dos hacen `remove_dir_all` del árbol y lo re-extraen,
|
|
# incondicionalmente, en cada build**. No hay ni una rama que reutilice un árbol existente.
|
|
#
|
|
# ⇒ Borrar un árbol no cuesta ni la re-extracción: ésa ocurre igual la próxima vez que se construya
|
|
# esa receta. Lo ÚNICO que se pierde es poder mirar por dentro el último build de esa receta
|
|
# —el post-mortem de uno que falló—, y ese valor se muere en un día.
|
|
#
|
|
# ── POR QUÉ TOMA `work/.farm-build.lock` (y no es opcional) ─────────────────────────────────────
|
|
# El único modo de que esto haga daño es borrar el árbol que un `bwrap` está usando para compilar:
|
|
# el ADR 0012 en su forma más directa, y el mismo desastre medido en CLAUDE.md §1 (93 de 205
|
|
# recetas KDE muertas con `cc is not a full path`). El lock es el punto de serialización que ya
|
|
# usan `farm-worker-loop.sh` y `campana-deuda.sh`, así que tomarlo acá nos serializa con la granja.
|
|
#
|
|
# ⚠ Y hace falta DE VERDAD, no por prudencia: el filtro es por mtime del directorio raíz, que sólo
|
|
# cambia cuando se añaden o quitan entradas de primer nivel. Un build largo en vuelo puede tener
|
|
# un raíz con mtime de hace dos días mientras compila abajo. El mtime NO distingue "viejo" de
|
|
# "lento". El lock sí.
|
|
#
|
|
# ── POR QUÉ EL SUELO ES EN HORAS Y NO EN DÍAS ───────────────────────────────────────────────────
|
|
# La lección de `cache-ci-no-envejece`: aquel cron podaba a +10 días sobre datos que se reescriben
|
|
# enteros cada ráfaga, o sea que liberaba CERO y parecía que protegía. **Un guardián calibrado a
|
|
# una escala que el dato nunca alcanza no protege.** Acá los 106 árboles abarcaban 3 días (76 de
|
|
# uno solo) ⇒ un umbral en días grandes no habría tocado nada. 24 h deja el post-mortem del día y
|
|
# se lleva el resto.
|
|
#
|
|
# ── POR QUÉ NO SE CONDICIONA A ESPACIO LIBRE ────────────────────────────────────────────────────
|
|
# Porque borrar no cuesta nada. Un umbral por espacio ("podar sólo bajo 20 G") deja que la basura
|
|
# se acumule hasta rozar el borde y convierte una poda barata y plana en un pico cerca del límite,
|
|
# que es justo cuando un build en vuelo puede quedarse sin disco a mitad. Se mantiene plano.
|
|
#
|
|
# Uso: scripts/poda-fuentes.sh [--aplicar] [--horas N]
|
|
# (sin --aplicar es DRY-RUN: mide y no borra)
|
|
set -uo pipefail
|
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)"; cd "$ROOT"
|
|
FUENTES="${FUENTES:-work/sources}"
|
|
HORAS="${HORAS:-24}"; APLICAR=0; ESPERA="${ESPERA:-60}"
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
--aplicar) APLICAR=1 ;;
|
|
--horas) HORAS="$2"; shift ;;
|
|
*) echo "opción desconocida: $1" >&2; exit 2 ;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
[ -d "$FUENTES" ] || { echo "no existe $FUENTES — nada que podar"; exit 0; }
|
|
|
|
gigas() { echo "$1" | awk '{printf "%.1f", $1/1073741824}'; }
|
|
|
|
# `-mmin +N` en vez de `-mtime`: `-mtime +0` es ">24h" por redondeo hacia abajo y no deja expresar
|
|
# nada más fino. Con minutos el umbral se dice tal cual.
|
|
MINUTOS=$((HORAS * 60))
|
|
|
|
poda() {
|
|
local libre_antes libre_despues n
|
|
libre_antes=$(df -B1 --output=avail "$FUENTES" | tail -1)
|
|
mapfile -t viejos < <(find "$FUENTES" -mindepth 1 -maxdepth 1 -type d -mmin "+$MINUTOS" | sort)
|
|
n=${#viejos[@]}
|
|
local quedan
|
|
quedan=$(find "$FUENTES" -mindepth 1 -maxdepth 1 -type d -not -mmin "+$MINUTOS" | wc -l)
|
|
|
|
echo "── poda de $FUENTES · suelo ${HORAS}h · libres $(gigas "$libre_antes") G"
|
|
echo " $n árbol(es) por encima del suelo · $quedan se quedan (post-mortem del día)"
|
|
[ "$n" -eq 0 ] && { echo " nada que podar"; return 0; }
|
|
|
|
if [ "$APLICAR" -eq 0 ]; then
|
|
# El dry-run NO mide con `du`: recorrer 79 G de árboles cuesta minutos y este script corre
|
|
# dentro del lock. Se nombran los candidatos y se deja el tamaño para el --aplicar, que lo saca
|
|
# gratis del `df` de antes y después.
|
|
printf ' · %s\n' "${viejos[@]}" | head -20
|
|
[ "$n" -gt 20 ] && echo " … y $((n - 20)) más"
|
|
echo " DRY-RUN: no se borró nada (usar --aplicar)"
|
|
return 0
|
|
fi
|
|
|
|
printf '%s\0' "${viejos[@]}" | xargs -0 -r rm -rf
|
|
libre_despues=$(df -B1 --output=avail "$FUENTES" | tail -1)
|
|
echo " borrados $n · libres $(gigas "$libre_despues") G · LIBERADO $(gigas $((libre_despues - libre_antes))) G"
|
|
}
|
|
|
|
# `-w`: si hay un build en vuelo NO se espera indefinidamente. Este script lo llama el cron cada 30
|
|
# min; quedarse colgado del lock durante un kernel de 2 h dejaría al cron sin cosechar. Saltar un
|
|
# ciclo no cuesta nada porque la basura no se va a ningún lado.
|
|
exec 9>work/.farm-build.lock
|
|
if ! flock -w "$ESPERA" 9; then
|
|
echo "── poda de $FUENTES: build en vuelo (lock tomado tras ${ESPERA}s) ⇒ este ciclo NO toca"
|
|
exit 0
|
|
fi
|
|
poda
|