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 Definitions

⎇ 3627894045b5457caf16a772e05ee5acad5ecc0e scripts/drive-rebuild.py Python · 133 lines · 5.4 KB History Who touched what raw

drive-rebuild: APPEND env override para el cmdline del kernel 362789404 · sergio ·

1#!/usr/bin/env python3
2# drive-rebuild.py — Driver NO-interactivo del rebuild in-rootfs (auto-alojamiento, SDD 11 §7;
3# runbook docs/runbooks/stage1-vm-boot.md §8c).
4#
5# Bootea el builder en QEMU, espera la shell de arje-zero (PID 1), manda `rebuild-stage1` y captura
6# todo hasta el veredicto (✓ REPRODUCIBLE / ✗ DIVERGENTE), el RC o el timeout. Imprime un resumen y
7# sale con 0 si REPRODUCIBLE, 1 si DIVERGENTE/otro.
8#
9# Variables de entorno (todas opcionales):
10#   BUILDER_CPIO  initramfs del builder        (default work/builder.cpio.gz)
11#   KERNEL        bzImage/vmlinuz a bootear     (default /boot/vmlinuz-linux)
12#   MEM           RAM de la VM en MiB           (default 6144)
13#   CPU           modelo de CPU TCG             (default Broadwell; AVX2 sin KVM)
14#   KVM           1 ⇒ -enable-kvm -cpu host si hay /dev/kvm (mucho más rápido; recomendado)
15#   DEADLINE      techo en segundos             (default 14400 = 4h)
16#   LOG           fichero de captura            (default work/rebuild-run.log)
17#   NET           1 ⇒ NIC e1000 user-mode para el vendoring Rust (default 1)
18#
19# El rebuild Rust (hammerd/arje-zero) hace `cargo vendor` desde crates.io ⇒ necesita red: por eso la
20# NIC e1000 (qemu user-mode 10.0.2.0/24). Sin red, sólo sellan los componentes C.
21import os, pty, select, subprocess, sys, time, re
22
23ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
24
25
26def envpath(name, default):
27    v = os.environ.get(name, default)
28    return v if os.path.isabs(v) else os.path.join(ROOT, v)
29
30
31CPIO = envpath("BUILDER_CPIO", "work/builder.cpio.gz")
32KERNEL = os.environ.get("KERNEL", "/boot/vmlinuz-linux")
33LOG = envpath("LOG", "work/rebuild-run.log")
34MEM = os.environ.get("MEM", "6144")
35CPU = os.environ.get("CPU", "Broadwell")
36DEADLINE = int(os.environ.get("DEADLINE", "14400"))
37WANT_KVM = os.environ.get("KVM", "0") == "1"
38WANT_NET = os.environ.get("NET", "1") == "1"
39
40for p, what in [(CPIO, "initramfs del builder"), (KERNEL, "kernel")]:
41    if not os.path.exists(p):
42        sys.exit(f"no existe {what}: {p}")
43
44accel = ["-cpu", CPU]
45if WANT_KVM and os.access("/dev/kvm", os.W_OK):
46    accel = ["-enable-kvm", "-cpu", "host"]
47    print("==> KVM activo (-cpu host)")
48else:
49    print(f"==> TCG (sin KVM); -cpu {CPU}. Lento — el rebuild Rust puede tardar.")
50
51net = ["-netdev", "user,id=n0", "-device", "e1000,netdev=n0"] if WANT_NET else []
52
53# APPEND override: por defecto consola serie + initramfs. Útil para experimentar con params del kernel
54# (p.ej. el frente kernel-from-source: el bzImage hammer bootea pero bwrap falla en pivot_root porque /
55# es el rootfs absoluto del namespace; `rootfstype=tmpfs` NO lo arregla — la causa es que / no tiene
56# mount padre, no el tipo de fs). Override con APPEND="…".
57APPEND = os.environ.get("APPEND", "console=ttyS0 rdinit=/sbin/init")
58QEMU = (["qemu-system-x86_64", "-m", MEM, "-no-reboot", "-nographic"] + accel + net +
59        ["-kernel", KERNEL, "-initrd", CPIO, "-append", APPEND])
60
61print(f"==> kernel    : {KERNEL}")
62print(f"==> initramfs : {CPIO}")
63print(f"==> mem       : {MEM} MiB   deadline: {DEADLINE}s   net: {'sí' if WANT_NET else 'no'}")
64print(f"==> log       : {LOG}")
65
66ansi = re.compile(rb'\x1b\[[0-9;?]*[a-zA-Z]')
67strip = lambda b: ansi.sub(b'', b)
68
69master, slave = pty.openpty()
70p = subprocess.Popen(QEMU, stdin=slave, stdout=slave, stderr=slave, close_fds=True)
71os.close(slave)
72log = open(LOG, "wb")
73buf = b""
74sent = False
75start = time.time()
76send = lambda s: os.write(master, s.encode())
77
78while True:
79    if time.time() - start > DEADLINE:
80        log.write(b"\n[DRIVER] DEADLINE\n")
81        break
82    r, _, _ = select.select([master], [], [], 10)
83    if r:
84        try:
85            data = os.read(master, 8192)
86        except OSError:
87            break
88        if not data:
89            break
90        log.write(data)
91        log.flush()
92        buf = strip(buf + data)[-20000:]
93        # Boot listo: arje-zero levantó hammerd (fanotify) o apareció la shell.
94        if not sent and (b"watcher fanotify activo" in buf or b"\n~ #" in buf or b"\r~ #" in buf):
95            time.sleep(3)
96            send("echo DRIVER_BEGIN; rebuild-stage1; echo DRIVER_RC=$?\n")
97            sent = True
98            log.write(b"\n[DRIVER] enviado rebuild-stage1\n")
99            log.flush()
100        # OJO: el comando tecleado ECHOA "DRIVER_RC=$?"; el marcador real es la SALIDA
101        # "DRIVER_RC=<digito>". Matchear sólo eso (o el veredicto) evita salir antes de tiempo.
102        if sent and (re.search(rb'DRIVER_RC=[0-9]', buf) or b"REPRODUCIBLE" in buf or b"DIVERGENTE" in buf):
103            time.sleep(2)
104            try:
105                log.write(os.read(master, 8192))
106            except Exception:
107                pass
108            log.write(b"\n[DRIVER] FIN (marcador)\n")
109            break
110    else:
111        if p.poll() is not None:
112            log.write(b"\n[DRIVER] qemu salio\n")
113            break
114
115try:
116    p.terminate()
117    time.sleep(2)
118    p.kill()
119except Exception:
120    pass
121log.close()
122
123# Veredicto a partir de lo capturado.
124blob = strip(open(LOG, "rb").read())
125ok = (b"REPRODUCIBLE" in blob) and (b"DIVERGENTE" not in blob)
126print(f"\nDRIVER done sent={sent} elapsed={int(time.time() - start)}s")
127if ok:
128    print("RESULTADO: ✓ REPRODUCIBLE — auto-alojamiento bit a bit verificado")
129elif b"DIVERGENTE" in blob:
130    print("RESULTADO: ✗ DIVERGENTE — hay no-determinismo que cazar (SDD 09 §2)")
131else:
132    print("RESULTADO: ? incompleto (timeout/boot/OOM) — revisá el log")
133sys.exit(0 if ok else 1)