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.
153 lines
8.7 KiB
Bash
Executable File
153 lines
8.7 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# vps-setup.sh — provisiona un WORKER de granja takana en un VPS o LXC fresco.
|
|
#
|
|
# DOS FAMILIAS SOPORTADAS, detectadas por gestor de paquetes:
|
|
# · apt → Debian/Ubuntu: los workers efímeros de Hetzner (`farm-up.sh`, imagen golden).
|
|
# · dnf → Fedora: el LXC prestado `dev.gioser.net` (2026-08-28), que reemplaza al worker
|
|
# de pago mientras esté disponible. NO se retira el camino Debian: la capacidad de
|
|
# abrir workers en Hetzner se conserva intacta.
|
|
#
|
|
# Modelo hub-and-spoke: el LAPTOP es el hub (tiene la clave de firma + gitea). El worker
|
|
# NO toca gitea ni firma nada — sólo construye la cola `recipes/incoming/` y sella al store
|
|
# local (PROMOTE=0). El hub le rsync-ea código+cola y le baja el store con `farm-sync.sh`.
|
|
#
|
|
# El hub corre esto por SSH tras rsync-ear el repo a $HAMMER_DIR. Idempotente (re-correr
|
|
# actualiza in-place). Necesita root (paquetes + systemd).
|
|
#
|
|
# ── LO QUE UN CONTENEDOR NO DEJA HACER, Y NO ES UN FALLO ─────────────────────────────────────
|
|
# En LXC `sysctl -w` es read-only y `swapon` devuelve `Operation not permitted` (medido en
|
|
# dev.gioser.net, 2026-08-28). Los dos pasos degradan a aviso en vez de abortar:
|
|
# · sysctl: en Fedora los userns no están capados de fábrica, así que no hace falta.
|
|
# · swap: NO se puede crear desde dentro. Si la caja va justa de RAM, el swap lo tiene que
|
|
# dar el HOST (en Proxmox es el parámetro `swap:` del contenedor). Un `swapon` que
|
|
# falla se avisa RUIDOSO, porque sin swap un build Rust grande no se pone lento:
|
|
# lo mata el OOM killer.
|
|
#
|
|
# Uso (en el VPS, como root): HAMMER_DIR=/opt/hammer ./scripts/farm/vps-setup.sh
|
|
#
|
|
# ── CAJA FRESCA (no golden): DOS PASOS DEL HUB ANTES DE ESTO ────────────────────────────────
|
|
# La imagen golden de Hetzner ya trae rsync y el lab horneados. Una caja virgen, no:
|
|
#
|
|
# 1. `rsync` no está instalado ⇒ no se puede subir el repo con rsync. Huevo y gallina:
|
|
# ssh root@caja 'dnf install -y rsync git' # o apt-get install -y rsync git
|
|
#
|
|
# 2. El lab (`.dev-fs/`) lo EMPUJA EL HUB, no lo baja el worker. `lab-image.sh --traer`
|
|
# tira del Storage Box y eso exige credenciales — que el worker NO debe tener (modelo
|
|
# hub-and-spoke: compute puro, sin secretos). El hub le manda el tarball ya verificado
|
|
# y el worker lo re-verifica por sha256 al extraerlo:
|
|
# rsync -a .dev-fs/lab-image.tar.zst caja:/opt/hammer/.dev-fs/lab-<SHA>.tar.zst
|
|
# rsync -a .dev-fs/tools/ caja:/opt/hammer/.dev-fs/tools/
|
|
# Mandar `tools/` entero (zig 0.13.0 + 0.16.0 + go) y no dejar que se descarguen solos
|
|
# evita el zig-skew: el worker usa EXACTAMENTE las versiones del hub.
|
|
set -euo pipefail
|
|
|
|
HAMMER_DIR="${TAKANA_DIR:-${HAMMER_DIR:-/opt/hammer}}"
|
|
cd "$HAMMER_DIR"
|
|
|
|
# ── detección de familia ────────────────────────────────────────────────────────────────────
|
|
if command -v apt-get >/dev/null 2>&1; then PKG=apt
|
|
elif command -v dnf5 >/dev/null 2>&1; then PKG=dnf; DNF=dnf5
|
|
elif command -v dnf >/dev/null 2>&1; then PKG=dnf; DNF=dnf
|
|
else echo "!! ni apt ni dnf: familia no soportada" >&2; exit 1
|
|
fi
|
|
echo "==> 0. familia detectada: $PKG ($(. /etc/os-release 2>/dev/null; echo "${PRETTY_NAME:-?}"))"
|
|
|
|
echo "==> 1. deps del sistema ($PKG)"
|
|
if [ "$PKG" = apt ]; then
|
|
export DEBIAN_FRONTEND=noninteractive
|
|
apt-get update -q
|
|
apt-get install -y -q git build-essential curl pkg-config libssl-dev \
|
|
bubblewrap rsync ca-certificates uidmap xz-utils
|
|
else
|
|
# Equivalencias Fedora. `build-essential` no existe: gcc/g++/make sueltos.
|
|
# uidmap → shadow-utils (trae newuidmap/newgidmap); xz-utils → xz; libssl-dev → openssl-devel.
|
|
#
|
|
# ⚠ `patch` VA EXPLÍCITO Y NO ES OBVIO (2026-08-29). En Debian entra de regalo dentro de
|
|
# `build-essential`; al desglosar a gcc/g++/make sueltos se cayó sin que nadie lo notara. El
|
|
# campo `patches = [...]` de una receta lo aplica takana DEL LADO DEL HOST, fuera del sandbox
|
|
# ⇒ no alcanza con que el rootfs lo traiga (lo trae: `/usr/bin/patch` está en el alpine). Sin
|
|
# él, `cairo-shared` muere con `spawn patch: No such file or directory` y detrás caen `pango`
|
|
# y `gtk4`, o sea el stack GUI entero de GNOME por un binario de 129 KB.
|
|
# Regla: toda herramienta que takana invoque HOST-SIDE va en esta lista, no en `[deps]` de la
|
|
# receta — declararla en la receta re-hashearía cairo y todo GNOME debajo para nada.
|
|
$DNF install -y -q git gcc gcc-c++ make patch curl pkgconf-pkg-config openssl-devel \
|
|
bubblewrap rsync ca-certificates shadow-utils xz tar findutils
|
|
fi
|
|
|
|
echo "==> 2. user namespaces para bwrap"
|
|
# Debian/Ubuntu los capan de fábrica; Fedora no. En un contenedor `sysctl -w` es read-only y
|
|
# esto no importa, porque el userns lo concede el HOST. Todo el paso es best-effort.
|
|
if [ "$PKG" = apt ]; then
|
|
sysctl -w kernel.unprivileged_userns_clone=1 2>/dev/null || true
|
|
sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 2>/dev/null || true
|
|
cat > /etc/sysctl.d/99-hammer-userns.conf <<'EOF'
|
|
kernel.unprivileged_userns_clone=1
|
|
kernel.apparmor_restrict_unprivileged_userns=0
|
|
EOF
|
|
sysctl --system >/dev/null 2>&1 || true
|
|
fi
|
|
# El veredicto no es el sysctl: es que el userns REALMENTE se pueda crear. Se comprueba.
|
|
if ! unshare -Umr true 2>/dev/null; then
|
|
echo "!! FATAL: no se pueden crear user namespaces sin privilegios." >&2
|
|
echo " Sin esto el sandbox del lab (bwrap --unshare-all) no arranca y esta caja no" >&2
|
|
echo " puede ser worker. En LXC lo habilita el host." >&2
|
|
exit 1
|
|
fi
|
|
echo " userns: OK"
|
|
|
|
echo "==> 2b. bubblewrap con soporte overlay (el de la distro suele ser viejo/sin overlayfs)"
|
|
# El sandbox del lab usa `bwrap --overlay-src --tmp-overlay`; bubblewrap <0.10 (p.ej. el 0.9.0 de
|
|
# Ubuntu 24.04) responde "Unknown option --overlay-src". Compilamos 0.11.2 a /usr/local/bin (gana
|
|
# el PATH de systemd) si el instalado no lo soporta.
|
|
if ! bwrap --help 2>&1 | grep -q overlay-src; then
|
|
if [ "$PKG" = apt ]; then apt-get install -y -q meson ninja-build libcap-dev
|
|
else $DNF install -y -q meson ninja-build libcap-devel; fi
|
|
BWV=0.11.2
|
|
curl -sL "https://github.com/containers/bubblewrap/releases/download/v${BWV}/bubblewrap-${BWV}.tar.xz" -o /tmp/bwrap.tar.xz
|
|
( cd /tmp && rm -rf "bubblewrap-${BWV}" && tar xf bwrap.tar.xz && cd "bubblewrap-${BWV}" \
|
|
&& meson setup _build --prefix=/usr/local -Dman=disabled -Dtests=false >/dev/null \
|
|
&& ninja -C _build >/dev/null && ninja -C _build install >/dev/null )
|
|
hash -r
|
|
echo " bwrap actualizado: $(/usr/local/bin/bwrap --version)"
|
|
fi
|
|
|
|
echo "==> 3. swap 16G (8GB de RAM es justo para builds Rust con JOBS=2)"
|
|
# En un contenedor esto FALLA por diseño (swapon exige CAP_SYS_ADMIN del host). No se aborta,
|
|
# pero se avisa fuerte: sin swap el límite de RAM es duro y el OOM killer no avisa antes.
|
|
if ! swapon --show 2>/dev/null | grep -q /swapfile; then
|
|
if fallocate -l 16G /swapfile 2>/dev/null || dd if=/dev/zero of=/swapfile bs=1M count=16384 status=none; then
|
|
chmod 600 /swapfile; mkswap /swapfile >/dev/null 2>&1
|
|
if swapon /swapfile 2>/dev/null; then
|
|
grep -q '/swapfile' /etc/fstab || echo '/swapfile none swap sw 0 0' >> /etc/fstab
|
|
echo " swap activo: $(swapon --show=NAME,SIZE --noheadings | tr '\n' ' ')"
|
|
else
|
|
rm -f /swapfile
|
|
echo " ⚠ SIN SWAP: el kernel no deja activarlo (normal en LXC)."
|
|
echo " RAM disponible: $(free -g | awk '/Mem/{print $2}') G — es un techo DURO."
|
|
echo " Un build Rust grande que se pase NO se pone lento: lo mata el OOM killer."
|
|
echo " Si hace falta más, lo da el host (en Proxmox: parámetro 'swap:' del contenedor)."
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
echo "==> 4. rust host toolchain (para buildear el binario hammer)"
|
|
if ! command -v cargo >/dev/null 2>&1; then
|
|
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | \
|
|
sh -s -- -y --profile minimal --default-toolchain stable
|
|
fi
|
|
. "$HOME/.cargo/env"
|
|
|
|
echo "==> 5. build hammer (target/release/takana)"
|
|
cargo build --release --bin takana --bin hammer
|
|
|
|
echo "==> 6. rootfs Alpine edge + zig (bootstrap reproducible; varios min la 1ra vez)"
|
|
./scripts/bootstrap-devfs.sh
|
|
|
|
echo "==> 7. systemd unit del loop"
|
|
cp scripts/farm/hammer-farm.service /etc/systemd/system/hammer-farm.service
|
|
systemctl daemon-reload
|
|
|
|
echo "✓ worker provisionado."
|
|
echo " arrancar: systemctl enable --now hammer-farm"
|
|
echo " logs: journalctl -u hammer-farm -f"
|