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 1534Shell 199Rust 109Markdown 993713 Definiciones

mudanza: decidir SIN teclas unitarias, `/mnt/vvv` entra al censo, y 25 rutas de secretos más

Tres cosas que salieron de usar la herramienta de verdad.

1. `--decidir <fichero>`: decidir EN LOTE desde `<ruta-o-nombre> <decision>` por línea.

   El usuario: «no sé cómo activar o desactivar cosas aquí desde shuma llimphi remoto, que las
   opciones son con teclas unitarias». El modo interactivo lee una tecla por entrada, y hay
   terminales donde eso no se puede usar — una herramienta cuya única forma de decidir exige un
   tipo de terminal no es una herramienta, es una herramienta PARA ESA TERMINAL. El fichero anda en
   cualquiera, se revisa antes de aplicarlo, se versiona y se vuelve a correr.

   Una clave que no empareja con nada, o que empareja con varias, es ERROR RUIDOSO y no se escribe
   NADA: un lote a medias deja decidido lo que nadie revisó.

2. `/mnt/vvv` entra a las raíces del censo. No estaba, y ahí vive el trabajo: los repos de la
   persona, el monorepo, el store, work/sources. El agujero se vio preguntando por `humanoid`: el
   censo sólo conocía `/home/sergio/humanoid` —200 M de `build/` de junio— mientras el proyecto de
   verdad, con su `.git`, estaba en `/mnt/vvv/humanoid`, fuera de toda raíz censada. `repos_git` SÍ
   lo miraba: una parte del censo conocía el árbol y la otra no.

3. 25 rutas más en `rutas-fuera-de-git.txt`, del barrido que hizo el frente tawasuyu sobre su home:
   `~/.wawa/seeds` (sin él ninguna app nueva del génesis nace), `~/.tejido` (la identidad de esta
   máquina en la flota), `~/.local/share/agora`, `~/.config/{wawa,minga,thasnuna,shuma,mirada,hcloud}`,
   `~/keys` y `~/fdroid` (⚠ firma de apps Android), `~/.gnupg`, `~/.pgpass`, `/etc/wireguard`,
   `/etc/{shuma,sandokan,tawasuyu/agente,local.d}`. Son identidades y semillas: no se «vuelven a
   generar», porque generar otras significa ser OTRA máquina para el resto de la flota.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

1c237f9da2f42c7059fa27001da23d312f0cac06 · por Sergio · (hace 1 mes) · padre b057c057d · ver el árbol en este commit

Delta semántico · 3

scripts/mudanza/censar.py

  • cambió por dentro fn main

scripts/mudanza/planear.py

3 archivos · +102 −1

diff --git a/scripts/mudanza/censar.py b/scripts/mudanza/censar.py
index ddb5f84c..5d20522f 100755
--- a/scripts/mudanza/censar.py
+++ b/scripts/mudanza/censar.py
@@ -1226,7 +1226,13 @@ def main():
             if i and http_ok:
                 time.sleep(0.5)   # no parecer un escaneo ante el propio fail2ban del objetivo
             doms.append(sondear_dominio(d, set(ident["ips"]), http_ok))
-    raices = a.data_root or ["/var/www", "/var/lib", "/srv", "/opt", "/home", "/store"]
+    # ⚠ `/mnt/vvv` ENTRA (2026-09-16). No estaba, y ahí vive el trabajo: los repos de la persona, el
+    # monorepo, el store y `work/sources`. El censo listaba 169 cosas y ninguna era el árbol donde
+    # está todo — el agujero apareció cuando el usuario preguntó por `humanoid` y el censo sólo
+    # conocía `/home/sergio/humanoid`, que son 200 M de `build/` viejo: el proyecto de verdad, con
+    # su `.git`, estaba en `/mnt/vvv/humanoid`, fuera de toda raíz censada.
+    # (`repos_git` sí lo miraba: una parte del censo conocía el árbol y la otra no.)
+    raices = a.data_root or ["/var/www", "/var/lib", "/srv", "/opt", "/home", "/store", "/mnt/vvv"]
     # Dos vistas y no una: el TOTAL por raíz es lo que dimensiona la mudanza (y lo que mira el
     # preflight), y el DETALLE por cosa es lo que se puede decidir. Dar sólo el total obliga a
     # decidir 25 G de una; dar sólo el detalle pierde el número que dice si entra en el destino.
diff --git a/scripts/mudanza/planear.py b/scripts/mudanza/planear.py
index 85e410fc..6685e0ed 100755
--- a/scripts/mudanza/planear.py
+++ b/scripts/mudanza/planear.py
@@ -601,6 +601,62 @@ def revisar(censo, interactivo=True):
     return censo
 
 
+def decidir_desde_fichero(censo, ruta_dec):
+    """Decidir EN LOTE desde un fichero de texto: `<ruta-o-nombre> <decision>` por línea.
+
+    ── POR QUÉ EXISTE (pedido del usuario, 2026-09-16) ─────────────────────────────────────────
+    «no sé cómo activar o desactivar cosas aquí desde shuma llimphi remoto, que las opciones son
+    con teclas unitarias». El modo interactivo de `--decide` lee una tecla por entrada, y hay
+    terminales —la consola remota de shuma, entre otras— donde eso no se puede usar. Una
+    herramienta cuya única forma de decidir exige un tipo de terminal no es una herramienta: es una
+    herramienta *para esa terminal*. El fichero funciona en cualquiera, se revisa antes de
+    aplicarlo, se versiona y se vuelve a correr — que es lo mismo que ya se pide del plan.
+
+    Formato, deliberadamente pobre (`#` comenta, se ignoran líneas vacías):
+
+        /home/sergio/mapas        muda
+        terapeuta                 respalda      # basta con que el nombre sea único
+        /var/lib/swap             muere
+
+    ⚠ Una clave que no empareja con NADA es un ERROR RUIDOSO, no un aviso: significa que alguien
+    decidió algo que no se aplicó, y el silencio ahí es exactamente la clase de fallo que este
+    programa existe para no tener. Lo mismo si empareja con varias: se nombran todas y no se toca
+    ninguna, porque elegir una por su cuenta sería adivinar.
+    """
+    lineas = []
+    with open(ruta_dec, encoding="utf-8") as fh:
+        for i, l in enumerate(fh, 1):
+            l = l.split("#", 1)[0].strip()
+            if l:
+                lineas.append((i, l))
+    todos = entradas(censo)
+    aplicadas, problemas = 0, []
+    for i, l in lineas:
+        partes = l.rsplit(None, 1)
+        if len(partes) != 2 or partes[1] not in DECISIONES:
+            problemas.append(f"línea {i}: «{l}» — falta la decisión o no es una de "
+                             f"{'/'.join(DECISIONES)}")
+            continue
+        clave, dec = partes
+        cands = [(t, n, e) for t, n, e in todos
+                 if n == clave or os.path.basename(str(n)) == clave]
+        if not cands:
+            problemas.append(f"línea {i}: «{clave}» no empareja con NADA del censo")
+            continue
+        if len(cands) > 1:
+            problemas.append(f"línea {i}: «{clave}» empareja con {len(cands)}: "
+                             + ", ".join(n for _t, n, _e in cands[:5]))
+            continue
+        cands[0][2]["decision"] = dec
+        aplicadas += 1
+    if problemas:
+        print("\n!! decisiones NO aplicadas:", file=sys.stderr)
+        for x in problemas:
+            print(f"   {x}", file=sys.stderr)
+        sys.exit("\n   No se escribe nada: un lote a medias deja decidido lo que nadie revisó.")
+    return aplicadas
+
+
 def guardar_censo(censo, ruta, respaldar=True):
     """Vuelve a escribir el censo con las decisiones puestas, usando el MISMO emisor que lo creó.
 
@@ -1149,6 +1205,9 @@ def main():
     ap.add_argument("--revisar", action="store_true",
                     help="sólo mostrar la revisión con recomendaciones, sin preguntar")
     ap.add_argument("--out", help="fichero del plan (default: stdout)")
+    ap.add_argument("--decidir", metavar="FICHERO",
+                    help="decidir EN LOTE desde un fichero `<ruta-o-nombre> <decision>` por línea. "
+                         "Para terminales donde no se puede pulsar una tecla por entrada")
     ap.add_argument("--censo-out", help="dónde escribir el censo con las decisiones tomadas "
                                         "(default: sobre el mismo censo, con copia `.bak`)")
     ap.add_argument("--respaldo", help="corral donde va lo marcado `respalda` (host:ruta o ruta "
@@ -1161,6 +1220,11 @@ def main():
     if a.revisar:
         revisar(censo, interactivo=False)
         return
+    if a.decidir:
+        n = decidir_desde_fichero(censo, a.decidir)
+        destino_censo = a.censo_out or a.censo
+        guardar_censo(censo, destino_censo, respaldar=(destino_censo == a.censo))
+        print(f"   {n} decisión(es) aplicadas → {destino_censo}")
     if a.decide:
         censo = revisar(censo, interactivo=True)
         # ── LAS DECISIONES SE GUARDAN, O NO EXISTIERON ──────────────────────────────────────────
diff --git a/scripts/mudanza/rutas-fuera-de-git.txt b/scripts/mudanza/rutas-fuera-de-git.txt
index 666a1eb3..0e51de73 100644
--- a/scripts/mudanza/rutas-fuera-de-git.txt
+++ b/scripts/mudanza/rutas-fuera-de-git.txt
@@ -29,3 +29,34 @@
 /var/lib/gitea            # los repos servidos, la sqlite y las claves del gitea
 /etc/arje/cards.d         # las tarjetas de arje: qué arranca esta máquina
 /ente                     # la Semilla de arje-zero
+
+# ── AMPLIACIÓN 2026-09-16: el barrido del frente tawasuyu ───────────────────────────────────────
+# El agente de tawasuyu barrió SU home y encontró 572 KB de secretos que ninguna de las 15 rutas de
+# arriba miraba. Son identidades y semillas: no se «vuelven a generar», porque generar otras
+# significa ser OTRA máquina para el resto de la flota. Van acá para que el censo las vea sin que
+# nadie tenga que acordarse — que es el modo de fallo que esta lista existe para cerrar.
+~/.wawa/seeds             # ⚠ el anillo de la ceremonia de capacidades del génesis: sin él NINGUNA app nueva de wawa nace
+~/.config/wawa            # las tres llaves del operador de wawa (primaria, secundaria, recuperación)
+~/.tejido                 # la IDENTIDAD de esta máquina en tejido + el padrón de teléfonos dados de alta
+~/.local/share/agora      # el grafo de confianza de ágora: el mismo padrón que usa `minga listen --admission`
+~/.config/minga           # la frase del espejo (16 B): sin ella el post-commit se aparta EN SILENCIO
+~/.config/thasnuna        # el token de telegram y el roster: INSTALAR.md dice que esto no va al repo a propósito
+~/.config/shuma           # identidad x25519, token del gateway y pares conocidos de shuma
+~/.config/mirada          # el escritorio afinado a mano: `perfiles-escritorio.ron` (178 KB) no lo genera nada
+~/.config/hcloud          # credenciales de Hetzner Cloud
+~/.config/hammer          # ⚠ ruta VIEJA del renombre, pero acá viven los `storagebox*.env` del respaldo
+~/.rumi/seeds             # semillas de rumi
+~/keys                    # ⚠ claves de FIRMA DE APPS (`rizoma.p12`): perderlas impide ACTUALIZAR lo ya instalado
+~/fdroid                  # ⚠ `keystore.p12` firma el índice del repo F-Droid, y `config.yml` trae su contraseña
+~/.gnupg                  # el llavero GPG
+~/.pgpass                 # contraseñas de postgres
+~/.local/share/keyrings   # llavero del escritorio
+~/.local/share/pki        # certificados de cliente
+~/kkk                     # la credencial de deepseek (46 B): sin ella `scripts/agentes/deepseek` deja de existir
+~/.zshrc                  # el entorno de trabajo de la persona, afinado a mano
+~/.zshenv                 # ídem — y define rutas de las que dependen los guiones sueltos
+/etc/wireguard            # ⚠ `server.key` y `wg0.conf`: la VPN de la flota. Otra clave = otros pares
+/etc/shuma                # `gateway.env` del gateway de shuma
+/etc/sandokan             # `cerebro.json`
+/etc/tawasuyu/agente      # las autoridades del agente
+/etc/local.d              # los `*.start` de OpenRC escritos a mano (tejido-relay, arje-boot-guard)