hammer-{core,build,bootstrap,overlay,journal,mirror,upgrade,agent,recover,cli}
→ takana-*, con sus deps de workspace, sus identificadores en el fuente y las
referencias -p de los scripts.
VERIFICADO que no mueve nada del corpus: `takana hash recipes/zlib.toml`
devuelve b3:dc363f26… , idéntico a antes del renombre. Los nombres de crate no
entran en hash_inputs, pero eso se comprueba, no se supone. 600 tests en verde.
DOS BINARIOS SE CONGELAN, y no por prolijidad:
- `hammerd` — paquete Y binario. Es componente de Stage 1 de la distro (musl,
busybox, hammerd, arje-zero), lo supervisa arje-zero en el sistema arrancado,
`PRESEED=hammerd` lo nombra en selfhost-verify y sus bytes anclan el baseline
of_tree. El nombre del crate va en los símbolos ⇒ renombrarlo mueve los bytes.
- `hammer-recover` — el PAQUETE se renombra a takana-recover, el BINARIO no.
hammer-live-install.sh lo copia a /usr/sbin/hammer-recover en sistemas ya
instalados y hornea un hook de arranque que lo invoca por ese nombre:
renombrarlo rompe máquinas instaladas, no el repo.
Consecuencia que hay que anotar igual: al renombrar hammer-core, los bytes de
hammerd cambian de todos modos porque linkea contra un crate con otro nombre.
El baseline of_tree del selfhost hay que rehacerlo — es efecto de la etapa 4,
no de un cambio de hammerd.
Las referencias en comentarios de recetas y docs (rutas hammer-core/src/…)
quedan para la etapa 5: son texto, no mueven hash.
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 (hammer 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
|