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…

censar: 26 repositorios existen SÓLO en la máquina que se va a borrar — el paso 1 del plan

Buscando cómo escribir la receta de `tawasuyu` apareció que su ÚNICO remoto es el gitea de gioser
(`ssh://gitea@git.tawasuyu.net:2345/…`, y ese nombre resuelve a 204.168.193.248 = gioser). Al mirar
el resto: **28 repositorios con remoto en esta máquina, 26 SIN NINGUNA copia fuera**. Sólo `takana` y
`llimphi-standalone` tienen espejo externo.

Apagar el origen no borra unos servicios: borra EL CÓDIGO CON EL QUE SE VOLVERÍAN A CONSTRUIR, y los
clones de trabajo están en el mismo disco que también muere. Entre los 26 está `tawasuyu`, que
produce 10 de los 13 binarios que nadie más provee — la dependencia circular completa.

No se ve desde ninguna otra parte del censo: un repo no es un proceso, ni un puerto, ni un dominio.
Se descubre cuando ya no hay de dónde sacarlo.

· `censar.py` lo mira ahora (`[[repo]]`), y reporta NO «tiene remoto» sino si alguno de sus remotos
  NO es esta máquina: un remoto que apunta afuera es la prueba de que el código sobrevive. La lista
  de «esta máquina» sale del censo mismo (sus IPs + los dominios que sirve), no de nombres cableados.
· `planear.py` lo emite como PASO 1, por delante del rescate de binarios: aquello pierde un servicio,
  esto pierde la posibilidad de reconstruirlo. El arreglo ya está escrito en el repo —takana usa
  `pushurl` doble por `scripts/espejo-setup.sh`.

Y una corrección de algo que dije antes: la perilla por receta que vi en `recipe.rs` es `strip_debug`,
no una versión de rust. NO existe `rust_version`; sólo `zig_version`. Pinear rustc por receta para
honrar el `rust-toolchain.toml` de tawasuyu (1.96.0, contra 1.97.0 del lab) sería código nuevo en
takana-core, no una opción ya disponible.

De paso, medido el subárbol de los diez binarios por separado: cuatro (`willay-daemon`,
`sandokan-seguridad-core`, `pacha-secretos`, `tupu-cli`, entre 84 y 153 deps) NO arrastran
criptografía en C; cuatro traen `ring` y dos `aws-lc-sys`. O sea que hay un escalón por donde
empezar sin pelear con cmake.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RomoxEGZUhaT4pob1QSX5x

0b5dae781220a3d9f727d0988964f921d0add15c · por Sergio · (hace 1 mes) · padre 35afe8260 · ver el árbol en este commit

Delta semántico · 4

scripts/mudanza/censar.py

scripts/mudanza/planear.py

  • cambió por dentro fn pasos

3 archivos · +98 −1

diff --git a/docs/29-mudanza.md b/docs/29-mudanza.md
index 9b79cfa9..4ab40441 100644
--- a/docs/29-mudanza.md
+++ b/docs/29-mudanza.md
@@ -301,6 +301,27 @@ reconstruyen el servidor desde cero**:
 Ninguno de los tres es un log de lo que se hizo: los tres son entradas que se vuelven a ejecutar.
 Ésa es la diferencia entre mudar un servidor y poder mudarlo otra vez.
 
+### 4.0 quindecies · ⚠ 26 repositorios existen SÓLO en la máquina que se va a borrar
+
+Es el hallazgo más grave de este frente, y va por delante de todo lo demás en el plan. Buscando cómo
+escribir la receta de `tawasuyu` apareció que **su único remoto es el gitea de gioser**
+(`ssh://gitea@git.tawasuyu.net:2345/…`, y `git.tawasuyu.net` resuelve a `204.168.193.248`, que es
+gioser). Al mirar el resto: **28 repositorios con remoto en esta máquina, 26 sin ninguna copia
+fuera.** Sólo `takana` y `llimphi-standalone` tienen espejo externo.
+
+Apagar el origen no borra unos servicios: borra **el código con el que se volverían a construir**. Y
+los clones de trabajo están en el mismo disco, que también muere. Entre los 26 está `tawasuyu`, que
+produce **10 de los 13 binarios que nadie más provee** — la dependencia circular completa.
+
+No se ve desde ninguna otra parte del censo: un repo no es un proceso, ni un puerto, ni un dominio.
+Se descubre cuando ya no hay de dónde sacarlo.
+
+El censo lo mira ahora (`[[repo]]`), y lo que reporta **no es «tiene remoto» sino si alguno de sus
+remotos NO es esta máquina** — un remoto que apunta afuera es la prueba de que el código sobrevive.
+En el plan es el **paso 1**, por delante del rescate de binarios: aquello pierde un servicio, esto
+pierde la posibilidad de reconstruirlo. El propio takana ya resuelve esto con `pushurl` doble
+(`scripts/espejo-setup.sh`), así que el arreglo está escrito.
+
 ### 4.0 quaterdecies · Los 13 binarios sueltos son casi un solo árbol
 
 De los 13 servicios cuyo binario **nadie provee** —los que se pierden al apagar el origen— **diez
diff --git a/scripts/mudanza/censar.py b/scripts/mudanza/censar.py
index 22ff3a32..2aed29c8 100755
--- a/scripts/mudanza/censar.py
+++ b/scripts/mudanza/censar.py
@@ -322,6 +322,50 @@ _CONFS = [("/etc/caddy/Caddyfile", "caddy"),
           ("/etc/httpd/conf.d/*", "apache")]
 
 
+def repos_git(t, raices, ips, dominios):
+    """Repositorios git bajo los datos, y si su remoto vive EN ESTA MÁQUINA.
+
+    ── LA DEPENDENCIA CIRCULAR QUE MATA LA MUDANZA AL FINAL ─────────────────────────────────────
+    Medido en gioser: **25 repositorios cuyo único remoto es el gitea de la propia máquina** — entre
+    ellos `tawasuyu`, que produce 10 de los 13 binarios que nadie más provee. Apagar el origen no
+    borra sólo unos servicios: borra **el código con el que se volverían a construir**, y los clones
+    de trabajo están en el mismo disco que también muere.
+
+    Es un fallo que no se ve desde ninguna otra parte del censo —el repo no es un proceso, ni un
+    puerto, ni un dominio— y que sólo se descubre cuando ya no hay de dónde sacarlo.
+
+    Un remoto que apunta AFUERA es la prueba de que el código sobrevive. Por eso lo que se reporta no
+    es «tiene remoto» sino **si alguno de sus remotos NO es esta máquina**."""
+    if not raices:
+        return []
+    busca = " ".join(shlex.quote(r) for r in raices)
+    out, ok = t.run(f"find {busca} -maxdepth 4 -name .git -prune 2>/dev/null | head -300")
+    if not ok or not out.strip():
+        return []
+    mios = set(dominios) | set(ips) | {"localhost", "127.0.0.1"}
+    res = []
+    for g in out.split():
+        r = g[:-5] if g.endswith("/.git") else g
+        urls, ok2 = t.run(f"git -C {shlex.quote(r)} config --get-regexp "
+                          f"'remote\\..*\\.(url|pushurl)' 2>/dev/null")
+        if not ok2 or not urls.strip() or urls.startswith("(sonda falló"):
+            continue
+        dentro, fuera = [], []
+        for l in urls.splitlines():
+            p = l.split(None, 1)
+            if len(p) != 2:
+                continue
+            u = p[1]
+            # `ssh://gitea@host:2345/x.git`, `gitea@host:x.git`, `https://host/x.git`
+            h = u.split("://", 1)[-1].split("/", 1)[0].split("@")[-1].split(":")[0]
+            (dentro if h in mios else fuera).append(u)
+        if dentro:
+            res.append({"path": r, "dentro": " ".join(dentro),
+                        "fuera": " ".join(fuera),
+                        "solo_aqui": "si" if not fuera else "no"})
+    return res
+
+
 def sitios_web(t):
     """Los sitios declarados en la config, con su raíz y si esa raíz EXISTE.
 
@@ -694,6 +738,11 @@ def emitir_toml(c):
         L.append(f"paquete = {toml_escape(s.get('paquete',''))}")
         L.append('decision = ""   # muda | muere | (vacío = sin decidir)')
         L.append("")
+    for x in c.get("repos", []):
+        L.append("[[repo]]")
+        for k, v in x.items():
+            L.append(f"{k} = {toml_escape(v)}")
+        L.append("")
     for x in c.get("sitios", []):
         L.append("[[sitio]]")
         for k, v in x.items():
@@ -845,10 +894,13 @@ def main():
             doms.append(sondear_dominio(d, set(ident["ips"]), http_ok))
     raices = a.data_root or ["/var/www", "/var/lib", "/srv", "/opt", "/home", "/store"]
     dat = datos(t, raices)
+    # Después de `raices`, que es de dónde salen los repos a mirar. (Estaba antes y `raices` todavía
+    # no existía: habría reventado con NameError en la primera corrida con datos.)
+    repos = repos_git(t, raices + ["/mnt/vvv"], ident.get("ips", []), nombres)
     censo = {
         "fecha": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
         "identidad": ident, "servicios": svc, "huerfanos": huerfanos, "puertos": pts,
-        "dominios": doms, "datos": dat, "contenido": cont, "sitios": sitios, "montajes": montajes(t),
+        "dominios": doms, "datos": dat, "contenido": cont, "sitios": sitios, "repos": repos, "montajes": montajes(t),
         "fuera_de_git": fuera_de_git(t),
     }
     resumen(censo)
diff --git a/scripts/mudanza/planear.py b/scripts/mudanza/planear.py
index bab7b9cb..d9d60edf 100755
--- a/scripts/mudanza/planear.py
+++ b/scripts/mudanza/planear.py
@@ -638,6 +638,30 @@ def pasos(censo, target, key):
     muere = [(t, n, e) for t, n, e in entradas(censo) if e.get("decision") == "muere"]
     contenido = censo.get("contenido", [])
 
+    # ── 0. CÓDIGO QUE SÓLO EXISTE ACÁ ───────────────────────────────────────────────────────────
+    # El primer paso del plan entero, por delante incluso del rescate de binarios: aquello es un
+    # servicio que se pierde, esto es EL CÓDIGO CON EL QUE SE VOLVERÍA A CONSTRUIR. Y no se ve desde
+    # ninguna otra parte del censo —un repo no es un proceso, ni un puerto, ni un dominio—, así que
+    # se descubre cuando ya no hay de dónde sacarlo.
+    solos = [r for r in censo.get("repo", []) if r.get("solo_aqui") == "si"]
+    if solos:
+        det = ["# Estos repositorios NO tienen ningún remoto fuera de esta máquina. Apagarla borra el",
+               "# código, y los clones de trabajo están en el mismo disco que también muere.",
+               "#",
+               "# Por cada uno: agregarle un remoto que viva en otro lado y empujar. El propio takana",
+               "# lo hace así (`scripts/espejo-setup.sh`: pushurl doble a gitea Y a un espejo privado).",
+               "#"]
+        for r in solos:
+            det.append(f"#   {r['path']}")
+            det.append(f"#      remoto actual (en esta máquina): {r['dentro'][:88]}")
+        paso("codigo", f"{len(solos)} repositorio(s) que sólo existen en la máquina que se va a borrar",
+             "\n".join(det),
+             verifica="revisión humana: cada uno tiene que tener un remoto FUERA antes de apagar nada",
+             verifica_tipo="humano",
+             nota="⚠ Es la dependencia circular de esta mudanza: `tawasuyu` produce 10 de los 13 "
+                  "binarios que nadie más provee, y su único remoto es el gitea de esta misma "
+                  "máquina. Sin esto, apagar el origen deja servicios que no se pueden reconstruir.")
+
     # ── 0 ter. COHERENCIA ───────────────────────────────────────────────────────────────────────
     incoh = incoherencias(censo)
     if incoh: