diff --git a/docs/state/fuentes-vigia.json b/docs/state/fuentes-vigia.json index fcea5860..e06467bc 100644 --- a/docs/state/fuentes-vigia.json +++ b/docs/state/fuentes-vigia.json @@ -1,48 +1,50 @@ { "schema": "hammer-fuentes-vigia/1", "total": 1179, - "vivas": 1174, - "muertas": 5, + "vivas": 1175, + "muertas": 4, "detalle_muertas": [ { "receta": "busybox", "fichero": "recipes/busybox.toml", "tipo": "tarball", "url": "https://busybox.net/downloads/busybox-1.36.1.tar.bz2", + "sha256": "b8cc24c9574d809e7279c3be349795c5d5ceb6fdf19ca709f80cde50e47de314", "codigo": "000", - "viva": false + "viva": false, + "respaldo": "cache-local" }, { "receta": "firefox-pgo-profile", "fichero": "recipes/firefox-pgo-profile.toml", "tipo": "tarball", "url": "https://no-hay-upstream.invalid/hammer/firefox-pgo-2026-09-08.tar.xz", + "sha256": "95472411ba252e14c4d57de7193d53e4ba35fbccf4819ba95ceff1ca843e4a7a", "codigo": "000", - "viva": false + "viva": false, + "respaldo": "cache-local" }, { "receta": "freetype", "fichero": "recipes/freetype.toml", "tipo": "tarball", "url": "https://download.savannah.gnu.org/releases/freetype/freetype-2.14.3.tar.xz", + "sha256": "36bc4f1cc413335368ee656c42afca65c5a3987e8768cc28cf11ba775e785a5f", "codigo": "502", - "viva": false + "viva": false, + "respaldo": "cache-local" }, { "receta": "freetype-shared", "fichero": "recipes/freetype-shared.toml", "tipo": "tarball", "url": "https://download.savannah.gnu.org/releases/freetype/freetype-2.14.3.tar.xz", - "codigo": "000", - "viva": false - }, - { - "receta": "libtool", - "fichero": "recipes/incoming-kde/libtool.toml", - "tipo": "tarball", - "url": "https://ftpmirror.gnu.org/libtool/libtool-2.5.4.tar.xz", + "sha256": "36bc4f1cc413335368ee656c42afca65c5a3987e8768cc28cf11ba775e785a5f", "codigo": "502", - "viva": false + "viva": false, + "respaldo": "cache-local" } - ] + ], + "en_riesgo": 0, + "mirror_consultado": true } diff --git a/recipes/incoming-kde/libtool.toml b/recipes/incoming-kde/libtool.toml index 41b53ab6..9cbcf49a 100644 --- a/recipes/incoming-kde/libtool.toml +++ b/recipes/incoming-kde/libtool.toml @@ -4,7 +4,7 @@ name = "libtool" version = "2.5.4" license = "GPL-2.0-or-later" [source] -tarball = "https://ftpmirror.gnu.org/libtool/libtool-2.5.4.tar.xz" +tarball = "https://ftp.gnu.org/gnu/libtool/libtool-2.5.4.tar.xz" sha256 = "f81f5860666b0bc7d84baddefa60d1cb9fa6fceb2398cc3baca6afaa60266675" [build] compiler = "zig-cc" diff --git a/scripts/fuentes/fuentes-vigia.py b/scripts/fuentes/fuentes-vigia.py index fd312d59..be11a21c 100755 --- a/scripts/fuentes/fuentes-vigia.py +++ b/scripts/fuentes/fuentes-vigia.py @@ -24,7 +24,9 @@ def fuentes(): nombre = (re.search(r'^\s*name\s*=\s*"([^"]+)"', s, re.M) or [None, os.path.basename(f)[:-5]])[1] t = re.search(r'^\s*tarball\s*=\s*"([^"]+)"', s, re.M) g = re.search(r'^\s*repo\s*=\s*"([^"]+)"', s, re.M) - if t: out.append({"receta": nombre, "fichero": f, "tipo": "tarball", "url": t.group(1)}) + sh = re.search(r'^\s*sha256\s*=\s*"([0-9a-f]{64})"', s, re.M) + if t: out.append({"receta": nombre, "fichero": f, "tipo": "tarball", "url": t.group(1), + "sha256": sh.group(1) if sh else None}) elif g: out.append({"receta": nombre, "fichero": f, "tipo": "git", "url": g.group(1)}) return out @@ -52,6 +54,51 @@ def revisa(e): except Exception as ex: return {**e, "codigo": f"error:{type(ex).__name__}", "viva": False} +def respaldo(muertas): + """¿El blob de cada fuente muerta está a salvo en algún sitio? + + POR QUÉ (2026-09-08). El vigía decía «5 muertas» y sólo UNA estaba en riesgo de verdad: las + otras cuatro tenían el tarball en la caché local y en el mirror, o eran `.invalid` a propósito. + Un upstream caído con el blob guardado es una NOTICIA; uno sin copia es una EMERGENCIA — la + receta deja de poder reconstruirse. Mezclarlos hace que el número suba, nadie lo mire, y el día + que uno importe esté enterrado entre cuatro que no. A un guardián lo mata el ruido. + + ⚠ Si el mirror NO se puede consultar (el worker no tiene la clave del Storage Box), esto dice + `indeterminado`, NUNCA `en_riesgo`. Un instrumento que no pudo correr y un hallazgo real se + parecen demasiado como para dejar que el informe los confunda. + """ + if not muertas: + return True, set() + en_mirror, consultado = set(), True + try: + r = subprocess.run( + ["ssh", "-4", "-p", os.environ.get("SB_PORT", "23"), + "-i", os.path.expanduser(os.environ.get("SB_KEY", "~/.ssh/github5")), + "-o", "BatchMode=yes", "-o", "StrictHostKeyChecking=accept-new", + "-o", "ConnectTimeout=15", + f"{os.environ.get('SB_USER','u647150')}@{os.environ.get('SB_HOST','u647150.your-storagebox.de')}", + "ls hammer/fuentes/"], capture_output=True, text=True, timeout=90) + if r.returncode == 0: + en_mirror = {l.strip() for l in r.stdout.splitlines() if l.strip().endswith(".tar")} + else: + consultado = False + except Exception: + consultado = False + for m in muertas: + sha = m.get("sha256") + blob = f"{sha}.tar" if sha else None + if blob and os.path.exists(os.path.join("work/tarballs", blob)): + m["respaldo"] = "cache-local" + elif blob and blob in en_mirror: + m["respaldo"] = "mirror" + elif not consultado: + m["respaldo"] = "indeterminado" # NO es un hallazgo: es que no pude mirar + elif not sha: + m["respaldo"] = "sin-sha" # git, o receta sin pin: no aplica el mirror + else: + m["respaldo"] = "EN RIESGO" + return consultado, en_mirror + def main(): fs = fuentes() if LIMITE: @@ -61,6 +108,8 @@ def main(): with ThreadPoolExecutor(max_workers=JOBS) as ex: res = list(ex.map(revisa, fs)) muertas = [r for r in res if not r["viva"]] + consultado, _ = respaldo(muertas) + en_riesgo = [m for m in muertas if m.get("respaldo") == "EN RIESGO"] # El informe se ordena por receta para que el diff de git sea legible entre corridas. inf = { "schema": "hammer-fuentes-vigia/1", @@ -68,14 +117,22 @@ def main(): "vivas": len(res) - len(muertas), "muertas": len(muertas), "detalle_muertas": sorted(muertas, key=lambda r: r["receta"]), + # La cifra que de verdad importa: muerto arriba Y sin copia en ningún lado. + "en_riesgo": len(en_riesgo), + "mirror_consultado": consultado, } os.makedirs("docs/state", exist_ok=True) with open("docs/state/fuentes-vigia.json", "w") as fh: json.dump(inf, fh, indent=1, ensure_ascii=False) fh.write("\n") - print(f"== vivas {inf['vivas']} / {inf['total']} MUERTAS {inf['muertas']}") + print(f"== vivas {inf['vivas']} / {inf['total']} muertas arriba {inf['muertas']} " + f"EN RIESGO {inf['en_riesgo']}" + ("" if consultado else " (mirror NO consultado)")) for m in inf["detalle_muertas"][:40]: - print(f" {m['codigo']:>12} {m['receta']:<24} {m['url'][:70]}") + print(f" {m['codigo']:>12} {m.get('respaldo','?'):<14} {m['receta']:<24} {m['url'][:60]}") + if inf["en_riesgo"]: + print(" ⚠ EN RIESGO = upstream caído Y sin blob en caché ni mirror ⇒ receta irreconstruible.") + print(" Bajar el tarball de otro espejo, verificar el sha256 pineado y " + "`scripts/fuentes/mirror-poblar.sh`.") # Exit 0 SIEMPRE: informar, no abortar el latido. Ver la docstring. return 0