From 0b5dae781220a3d9f727d0988964f921d0add15c Mon Sep 17 00:00:00 2001 From: Sergio Date: Sat, 12 Sep 2026 11:06:52 +0000 Subject: [PATCH] =?UTF-8?q?censar:=2026=20repositorios=20existen=20S=C3=93?= =?UTF-8?q?LO=20en=20la=20m=C3=A1quina=20que=20se=20va=20a=20borrar=20?= =?UTF-8?q?=E2=80=94=20el=20paso=201=20del=20plan?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) Claude-Session: https://claude.ai/code/session_01RomoxEGZUhaT4pob1QSX5x --- docs/29-mudanza.md | 21 +++++++++++++++ scripts/mudanza/censar.py | 54 +++++++++++++++++++++++++++++++++++++- scripts/mudanza/planear.py | 24 +++++++++++++++++ 3 files changed, 98 insertions(+), 1 deletion(-) 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: