gioser

sergio/takana

> Una distribución Linux construida con un laboratorio funcional y hermético en el > sótano, y una terminal mutable, clásica y anárquica en el piso de arriba — diseñada > desde el suelo para que una IA programadora entienda, modifique y co…

TOML 188Rust 55Shell 30Markdown 281211 Sutichaykuna

⎇ 8730aad34e868068796f9c85ca4e4c89cfb43fa6 scripts/farm/vps-setup.sh Shell · 152 siq'ikuna · 8.6 KB Ñawpa willay ch'uya

takana etapa 3b: las 49 invocaciones pasan a ./target/release/takana 8730aad34 · Sergio ·

1#!/usr/bin/env bash
2# vps-setup.sh — provisiona un WORKER de granja hammer en un VPS o LXC fresco.
3#
4# DOS FAMILIAS SOPORTADAS, detectadas por gestor de paquetes:
5#   · apt  → Debian/Ubuntu: los workers efímeros de Hetzner (`farm-up.sh`, imagen golden).
6#   · dnf  → Fedora: el LXC prestado `dev.gioser.net` (2026-08-28), que reemplaza al worker
7#            de pago mientras esté disponible. NO se retira el camino Debian: la capacidad de
8#            abrir workers en Hetzner se conserva intacta.
9#
10# Modelo hub-and-spoke: el LAPTOP es el hub (tiene la clave de firma + gitea). El worker
11# NO toca gitea ni firma nada — sólo construye la cola `recipes/incoming/` y sella al store
12# local (PROMOTE=0). El hub le rsync-ea código+cola y le baja el store con `farm-sync.sh`.
13#
14# El hub corre esto por SSH tras rsync-ear el repo a $HAMMER_DIR. Idempotente (re-correr
15# actualiza in-place). Necesita root (paquetes + systemd).
16#
17# ── LO QUE UN CONTENEDOR NO DEJA HACER, Y NO ES UN FALLO ─────────────────────────────────────
18# En LXC `sysctl -w` es read-only y `swapon` devuelve `Operation not permitted` (medido en
19# dev.gioser.net, 2026-08-28). Los dos pasos degradan a aviso en vez de abortar:
20#   · sysctl: en Fedora los userns no están capados de fábrica, así que no hace falta.
21#   · swap:   NO se puede crear desde dentro. Si la caja va justa de RAM, el swap lo tiene que
22#             dar el HOST (en Proxmox es el parámetro `swap:` del contenedor). Un `swapon` que
23#             falla se avisa RUIDOSO, porque sin swap un build Rust grande no se pone lento:
24#             lo mata el OOM killer.
25#
26# Uso (en el VPS, como root):   HAMMER_DIR=/opt/hammer ./scripts/farm/vps-setup.sh
27#
28# ── CAJA FRESCA (no golden): DOS PASOS DEL HUB ANTES DE ESTO ────────────────────────────────
29# La imagen golden de Hetzner ya trae rsync y el lab horneados. Una caja virgen, no:
30#
31#   1. `rsync` no está instalado ⇒ no se puede subir el repo con rsync. Huevo y gallina:
32#        ssh root@caja 'dnf install -y rsync git'   # o apt-get install -y rsync git
33#
34#   2. El lab (`.dev-fs/`) lo EMPUJA EL HUB, no lo baja el worker. `lab-image.sh --traer`
35#      tira del Storage Box y eso exige credenciales — que el worker NO debe tener (modelo
36#      hub-and-spoke: compute puro, sin secretos). El hub le manda el tarball ya verificado
37#      y el worker lo re-verifica por sha256 al extraerlo:
38#        rsync -a .dev-fs/lab-image.tar.zst  caja:/opt/hammer/.dev-fs/lab-<SHA>.tar.zst
39#        rsync -a .dev-fs/tools/             caja:/opt/hammer/.dev-fs/tools/
40#      Mandar `tools/` entero (zig 0.13.0 + 0.16.0 + go) y no dejar que se descarguen solos
41#      evita el zig-skew: el worker usa EXACTAMENTE las versiones del hub.
42set -euo pipefail
43
44HAMMER_DIR="${HAMMER_DIR:-/opt/hammer}"
45cd "$HAMMER_DIR"
46
47# ── detección de familia ────────────────────────────────────────────────────────────────────
48if command -v apt-get >/dev/null 2>&1; then PKG=apt
49elif command -v dnf5 >/dev/null 2>&1; then PKG=dnf; DNF=dnf5
50elif command -v dnf   >/dev/null 2>&1; then PKG=dnf; DNF=dnf
51else echo "!! ni apt ni dnf: familia no soportada" >&2; exit 1
52fi
53echo "==> 0. familia detectada: $PKG  ($(. /etc/os-release 2>/dev/null; echo "${PRETTY_NAME:-?}"))"
54
55echo "==> 1. deps del sistema ($PKG)"
56if [ "$PKG" = apt ]; then
57    export DEBIAN_FRONTEND=noninteractive
58    apt-get update -q
59    apt-get install -y -q git build-essential curl pkg-config libssl-dev \
60        bubblewrap rsync ca-certificates uidmap xz-utils
61else
62    # Equivalencias Fedora. `build-essential` no existe: gcc/g++/make sueltos.
63    # uidmap → shadow-utils (trae newuidmap/newgidmap); xz-utils → xz; libssl-dev → openssl-devel.
64    #
65    # ⚠ `patch` VA EXPLÍCITO Y NO ES OBVIO (2026-08-29). En Debian entra de regalo dentro de
66    # `build-essential`; al desglosar a gcc/g++/make sueltos se cayó sin que nadie lo notara. El
67    # campo `patches = [...]` de una receta lo aplica hammer DEL LADO DEL HOST, fuera del sandbox
68    # ⇒ no alcanza con que el rootfs lo traiga (lo trae: `/usr/bin/patch` está en el alpine). Sin
69    # él, `cairo-shared` muere con `spawn patch: No such file or directory` y detrás caen `pango`
70    # y `gtk4`, o sea el stack GUI entero de GNOME por un binario de 129 KB.
71    # Regla: toda herramienta que hammer invoque HOST-SIDE va en esta lista, no en `[deps]` de la
72    # receta — declararla en la receta re-hashearía cairo y todo GNOME debajo para nada.
73    $DNF install -y -q git gcc gcc-c++ make patch curl pkgconf-pkg-config openssl-devel \
74        bubblewrap rsync ca-certificates shadow-utils xz tar findutils
75fi
76
77echo "==> 2. user namespaces para bwrap"
78# Debian/Ubuntu los capan de fábrica; Fedora no. En un contenedor `sysctl -w` es read-only y
79# esto no importa, porque el userns lo concede el HOST. Todo el paso es best-effort.
80if [ "$PKG" = apt ]; then
81    sysctl -w kernel.unprivileged_userns_clone=1 2>/dev/null || true
82    sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 2>/dev/null || true
83    cat > /etc/sysctl.d/99-hammer-userns.conf <<'EOF'
84kernel.unprivileged_userns_clone=1
85kernel.apparmor_restrict_unprivileged_userns=0
86EOF
87    sysctl --system >/dev/null 2>&1 || true
88fi
89# El veredicto no es el sysctl: es que el userns REALMENTE se pueda crear. Se comprueba.
90if ! unshare -Umr true 2>/dev/null; then
91    echo "!! FATAL: no se pueden crear user namespaces sin privilegios." >&2
92    echo "   Sin esto el sandbox del lab (bwrap --unshare-all) no arranca y esta caja no" >&2
93    echo "   puede ser worker. En LXC lo habilita el host." >&2
94    exit 1
95fi
96echo "   userns: OK"
97
98echo "==> 2b. bubblewrap con soporte overlay (el de la distro suele ser viejo/sin overlayfs)"
99# El sandbox del lab usa `bwrap --overlay-src --tmp-overlay`; bubblewrap <0.10 (p.ej. el 0.9.0 de
100# Ubuntu 24.04) responde "Unknown option --overlay-src". Compilamos 0.11.2 a /usr/local/bin (gana
101# el PATH de systemd) si el instalado no lo soporta.
102if ! bwrap --help 2>&1 | grep -q overlay-src; then
103    if [ "$PKG" = apt ]; then apt-get install -y -q meson ninja-build libcap-dev
104    else $DNF install -y -q meson ninja-build libcap-devel; fi
105    BWV=0.11.2
106    curl -sL "https://github.com/containers/bubblewrap/releases/download/v${BWV}/bubblewrap-${BWV}.tar.xz" -o /tmp/bwrap.tar.xz
107    ( cd /tmp && rm -rf "bubblewrap-${BWV}" && tar xf bwrap.tar.xz && cd "bubblewrap-${BWV}" \
108      && meson setup _build --prefix=/usr/local -Dman=disabled -Dtests=false >/dev/null \
109      && ninja -C _build >/dev/null && ninja -C _build install >/dev/null )
110    hash -r
111    echo "   bwrap actualizado: $(/usr/local/bin/bwrap --version)"
112fi
113
114echo "==> 3. swap 16G (8GB de RAM es justo para builds Rust con JOBS=2)"
115# En un contenedor esto FALLA por diseño (swapon exige CAP_SYS_ADMIN del host). No se aborta,
116# pero se avisa fuerte: sin swap el límite de RAM es duro y el OOM killer no avisa antes.
117if ! swapon --show 2>/dev/null | grep -q /swapfile; then
118    if fallocate -l 16G /swapfile 2>/dev/null || dd if=/dev/zero of=/swapfile bs=1M count=16384 status=none; then
119        chmod 600 /swapfile; mkswap /swapfile >/dev/null 2>&1
120        if swapon /swapfile 2>/dev/null; then
121            grep -q '/swapfile' /etc/fstab || echo '/swapfile none swap sw 0 0' >> /etc/fstab
122            echo "   swap activo: $(swapon --show=NAME,SIZE --noheadings | tr '\n' ' ')"
123        else
124            rm -f /swapfile
125            echo "   ⚠ SIN SWAP: el kernel no deja activarlo (normal en LXC)."
126            echo "     RAM disponible: $(free -g | awk '/Mem/{print $2}') G — es un techo DURO."
127            echo "     Un build Rust grande que se pase NO se pone lento: lo mata el OOM killer."
128            echo "     Si hace falta más, lo da el host (en Proxmox: parámetro 'swap:' del contenedor)."
129        fi
130    fi
131fi
132
133echo "==> 4. rust host toolchain (para buildear el binario hammer)"
134if ! command -v cargo >/dev/null 2>&1; then
135    curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | \
136        sh -s -- -y --profile minimal --default-toolchain stable
137fi
138. "$HOME/.cargo/env"
139
140echo "==> 5. build hammer (target/release/takana)"
141cargo build --release --bin takana --bin hammer
142
143echo "==> 6. rootfs Alpine edge + zig (bootstrap reproducible; varios min la 1ra vez)"
144./scripts/bootstrap-devfs.sh
145
146echo "==> 7. systemd unit del loop"
147cp scripts/farm/hammer-farm.service /etc/systemd/system/hammer-farm.service
148systemctl daemon-reload
149
150echo "✓ worker provisionado."
151echo "  arrancar:  systemctl enable --now hammer-farm"
152echo "  logs:      journalctl -u hammer-farm -f"