censar: cuatro binarios que YA NO EXISTEN en disco — y el paso de rescate, que caduca
`/proc/<pid>/exe` termina en « (deleted)» para cuatro servicios de gioser: el fichero ya no está en
disco, sólo vive el inodo que sostiene su proceso. Y en tres de los cuatro hay AHORA otro fichero en
la misma ruta, de distinto tamaño:
tejido corre 12750368 B · en su ruta hay 13815864 B
shuma-gateway corre 8431896 B · en su ruta hay 10363504 B
pacha-secretos corre 8634240 B · en su ruta hay 8647456 B
puerta-f6e393ff corre 197262440 B · en su ruta NO HAY NADA
Copiar la ruta NO FALLA: muda otra cosa, y el servicio nuevo no es el que estaba andando. Todo verde,
todo distinto — el modo de fallo más caro que hay en este frente.
Se recuperan leyendo `/proc/<pid>/exe`, y SÓLO mientras el proceso viva. En una mudanza que termina
BORRANDO el origen, un reinicio de gioser antes de este paso los pierde para siempre. Por eso:
· clase propia en `origen_binario` (`borrado`), no una coletilla dentro del texto de `suelto`: no es
«hay que llevarlo», es «se pierde en el próximo reinicio y el que está en su ruta no es el mismo».
El motivo trae el comando literal de rescate con su pid.
· paso `rescate` en el plan, ANTES del preflight, porque es el único paso que puede volverse
IMPOSIBLE mientras se piensa el resto.
· su verificación COMPARA TAMAÑOS contra el que corre. No es celo: un `cat` de un `/proc` que ya no
existe crea un fichero VACÍO y devuelve 0, así que sin comparar el rescate «pasa». Probado en los
dos sentidos — el paso real sale 0, y con un fichero vacío a propósito dice `FALTA tejido` y sale 1.
Los cuatro ya están rescatados en `work/mudanza/rescate/` (gitignored), byte a byte iguales a los que
corren, con su SHA256SUMS.
Y el empalme se hizo comprobando que el marcador fuera ÚNICO antes de cortar, que es la lección del
`def pasos` duplicado de ayer.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RomoxEGZUhaT4pob1QSX5x
This commit is contained in:
@@ -301,6 +301,35 @@ 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 undecies · Cuatro binarios que ya no existen — el paso que CADUCA
|
||||
|
||||
El más urgente de todo el censo, y lo encontró la lista de lo que el perfil no puede cubrir:
|
||||
`/proc/<pid>/exe` termina en `(deleted)` para **cuatro servicios de gioser**. El fichero ya no está
|
||||
en disco; sólo vive el inodo que sostiene su proceso.
|
||||
|
||||
Y en tres de los cuatro hay **ahora otro fichero en la misma ruta, de distinto tamaño**:
|
||||
|
||||
| servicio | corriendo | en su ruta hoy |
|
||||
|---|---|---|
|
||||
| `tejido` | 12 750 368 B | 13 815 864 B |
|
||||
| `shuma-gateway` | 8 431 896 B | 10 363 504 B |
|
||||
| `pacha-secretos` | 8 634 240 B | 8 647 456 B |
|
||||
| `puerta-f6e393ff…` | 197 262 440 B | *no hay nada* |
|
||||
|
||||
Copiar la ruta **no falla**: muda otra cosa, y el servicio nuevo no es el que estaba andando. Es el
|
||||
modo de fallo más caro que hay en este documento — todo verde, todo distinto.
|
||||
|
||||
Se recuperan leyendo `/proc/<pid>/exe`, **y sólo mientras el proceso viva**. En una mudanza que
|
||||
termina *borrando el origen*, un reinicio de gioser antes de este paso pierde esos binarios para
|
||||
siempre. Por eso el rescate va **antes del preflight**: es el único paso del plan que puede volverse
|
||||
imposible mientras se piensa el resto.
|
||||
|
||||
Su verificación **compara tamaños** contra el que corre, y no por celo: un `cat` de un `/proc` que ya
|
||||
no existe crea un fichero **vacío** y devuelve 0. Probado en los dos sentidos — el paso real pasa, y
|
||||
con un fichero vacío a propósito el guardián dice `FALTA tejido` y sale 1.
|
||||
|
||||
Los cuatro ya están rescatados en `work/mudanza/rescate/`, con su `SHA256SUMS`.
|
||||
|
||||
### 4.0 decies · Leer la config del servidor: un fósil se detecta SIN tocar la red
|
||||
|
||||
La sonda DNS dice si un dominio resuelve; no si el servidor tiene algo que servirle. Eso lo dice el
|
||||
|
||||
@@ -101,6 +101,24 @@ def invocaciones(t, pids):
|
||||
# quedaban en «no se pudo leer su binario» por esto, y no por ser realmente desconocidos.
|
||||
exe, _ = t.run(f"readlink /proc/{pid}/exe 2>/dev/null")
|
||||
cmd, cwd, exe = cmd.strip(), cwd.strip(), exe.strip()
|
||||
# ⚠⚠ `/proc/<pid>/exe` termina en « (deleted)» cuando el fichero YA NO ESTÁ en disco: el
|
||||
# proceso sostiene el inodo y nadie más lo puede abrir por su ruta. Medido en gioser: CUATRO
|
||||
# servicios están así, y en dos de ellos hay AHORA otro fichero en la misma ruta, de distinto
|
||||
# tamaño — `tejido` corre 12 750 368 bytes y en su ruta hay 13 815 864. Copiar la ruta muda
|
||||
# OTRA COSA, en silencio, y el servicio nuevo no es el que estaba andando.
|
||||
#
|
||||
# Se recupera leyendo `/proc/<pid>/exe`, y SÓLO mientras el proceso viva: al reiniciar gioser
|
||||
# —o al morirse ese pid— ese binario desaparece para siempre. En una mudanza que termina
|
||||
# BORRANDO el origen, esto es lo más urgente del censo.
|
||||
borrado = exe.endswith(" (deleted)")
|
||||
if borrado:
|
||||
exe = exe[:-len(" (deleted)")]
|
||||
vivo_b = disco_b = ""
|
||||
if borrado:
|
||||
v, _ = t.run(f"stat -L -c %s /proc/{pid}/exe 2>/dev/null")
|
||||
d, _ = t.run(f"stat -c %s {shlex.quote(exe)} 2>/dev/null")
|
||||
vivo_b = v.strip() if v.strip().isdigit() else ""
|
||||
disco_b = d.strip() if d.strip().isdigit() else ""
|
||||
for v in ("cmd", "cwd", "exe"):
|
||||
if locals()[v].startswith("(sonda falló"):
|
||||
cmd, cwd, exe = (("" if v == "cmd" else cmd), ("" if v == "cwd" else cwd),
|
||||
@@ -111,7 +129,9 @@ def invocaciones(t, pids):
|
||||
if cmd or cwd or exe:
|
||||
out[nombre] = {"cmdline": cmd, "cwd": cwd,
|
||||
"exe": exe if exe.startswith("/") else "",
|
||||
"usuario": (sup or {}).get("usuario", "")}
|
||||
"usuario": (sup or {}).get("usuario", ""),
|
||||
"exe_borrado": borrado, "pid": pid,
|
||||
"bytes_vivo": vivo_b, "bytes_disco": disco_b}
|
||||
return out
|
||||
|
||||
|
||||
@@ -642,6 +662,11 @@ def emitir_toml(c):
|
||||
L.append(f"exec = {toml_escape(s.get('exec',''))}")
|
||||
if s.get('usuario'):
|
||||
L.append(f"usuario = {toml_escape(s['usuario'])}")
|
||||
if s.get('exec_borrado'):
|
||||
L.append("exec_borrado = true")
|
||||
L.append(f"pid_vivo = {int(s.get('pid_vivo') or 0)}")
|
||||
L.append(f"bytes_vivo = {toml_escape(s.get('bytes_vivo',''))}")
|
||||
L.append(f"bytes_disco = {toml_escape(s.get('bytes_disco',''))}")
|
||||
if s.get('exec_motivo'):
|
||||
L.append(f"exec_motivo = {toml_escape(s['exec_motivo'])}")
|
||||
L.append(f"paquete = {toml_escape(s.get('paquete',''))}")
|
||||
@@ -750,6 +775,11 @@ def main():
|
||||
e['cmdline'] = i.get('cmdline', '')
|
||||
e['cwd'] = i.get('cwd', '')
|
||||
e['exe_real'] = i.get('exe', '')
|
||||
if i.get('exe_borrado'):
|
||||
e['exec_borrado'] = True
|
||||
e['pid_vivo'] = i.get('pid', 0)
|
||||
e['bytes_vivo'] = i.get('bytes_vivo', '')
|
||||
e['bytes_disco'] = i.get('bytes_disco', '')
|
||||
if i.get('usuario'):
|
||||
e['usuario'] = i['usuario']
|
||||
# `/proc/<pid>/exe` MANDA sobre `argv[0]`: es el binario de verdad y no lo puede reescribir el
|
||||
|
||||
@@ -297,6 +297,19 @@ def origen_binario(e, repo=None):
|
||||
return ("paquete-ajeno",
|
||||
f"lo provee el gestor del origen: paquete `{pkg}`. En el destino hace falta una "
|
||||
f"receta takana, o `qorpa` si se acepta una jaula con la distro ajena (ADR 0015)")
|
||||
if exe and e.get("exec_borrado"):
|
||||
# Su propia clase, y la más urgente del censo: el fichero YA NO EXISTE. No es «hay que
|
||||
# llevarlo», es «se pierde en el próximo reinicio y el que está en su ruta NO es el mismo».
|
||||
pid = e.get("pid_vivo") or (e.get("pids") or [0])[0]
|
||||
bv, bd = e.get("bytes_vivo", ""), e.get("bytes_disco", "")
|
||||
dif = (f" El que hay AHORA en esa ruta pesa {bd} bytes y el que corre {bv}: NO SON EL MISMO."
|
||||
if bv and bd and bv != bd else
|
||||
(" En esa ruta ya no hay nada." if not bd else ""))
|
||||
return ("borrado",
|
||||
f"⚠ el binario que CORRE fue borrado o reemplazado: `{exe}` ya no existe en disco."
|
||||
f"{dif} Sólo vive mientras viva el pid {pid}; al reiniciar el origen se pierde PARA "
|
||||
f"SIEMPRE. Rescatalo ya: sudo cat /proc/{pid}/exe > {os.path.basename(exe)} "
|
||||
f"&& chmod +x {os.path.basename(exe)}")
|
||||
if exe:
|
||||
return ("suelto",
|
||||
f"NADIE lo provee: binario puesto a mano en {exe}. Hay que LLEVARLO (con lo que "
|
||||
@@ -544,6 +557,33 @@ 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 bis. RESCATE, antes que nada ──────────────────────────────────────────────────────────
|
||||
# Va PRIMERO, antes del preflight, porque es el único paso del plan que puede volverse
|
||||
# IMPOSIBLE mientras se piensa el resto: estos binarios ya no existen en disco y sólo viven en el
|
||||
# inodo que sostiene su proceso. Un reinicio del origen —o que se caiga ese pid— y se pierden
|
||||
# para siempre. Y en dos de los cuatro hay AHORA otro fichero en la misma ruta, de distinto
|
||||
# tamaño: copiar la ruta no falla, muda otra cosa.
|
||||
borrados = [s for s in censo.get("servicio", [])
|
||||
if s.get("exec_borrado") and s.get("decision") == "muda"]
|
||||
if borrados:
|
||||
lineas, ver = [], []
|
||||
for s in borrados:
|
||||
pid = s.get("pid_vivo") or (s.get("pids") or [0])[0]
|
||||
nom = os.path.basename(s.get("exec") or s["name"])
|
||||
lineas.append(f"sudo cat /proc/{pid}/exe > rescate/{shlex.quote(nom)} && "
|
||||
f"chmod +x rescate/{shlex.quote(nom)}")
|
||||
if s.get("bytes_vivo"):
|
||||
# La verificación COMPARA contra el tamaño del que CORRE. Un `cat` de un /proc que ya
|
||||
# no existe crea un fichero VACÍO y devuelve 0: sin comparar, el rescate «pasa».
|
||||
ver.append(f"[ \"$(stat -c %s rescate/{shlex.quote(nom)} 2>/dev/null)\" = "
|
||||
f"\"{s['bytes_vivo']}\" ] || {{ echo 'FALTA {nom}'; exit 1; }}")
|
||||
paso("rescate", f"rescatar {len(borrados)} binario(s) BORRADOS del disco (sólo mientras corran)",
|
||||
"mkdir -p rescate\n" + "\n".join(lineas),
|
||||
verifica="\n".join(ver) + "\necho ' ✓ los binarios rescatados coinciden con los que corren'",
|
||||
nota="⚠ ESTE PASO CADUCA. `/proc/<pid>/exe` deja de existir cuando muere el proceso, y "
|
||||
"estos ficheros ya no están en su ruta. Si el origen se reinicia antes de correr "
|
||||
"esto, el binario que estaba andando NO se puede recuperar de ninguna manera.")
|
||||
|
||||
# ── 0. preflight ────────────────────────────────────────────────────────────────────────────
|
||||
# ⚠ El sitio se mide POR SISTEMA DE FICHEROS, no contra `/`. Medido en la caja takana: `/` tiene
|
||||
# 3,5 G libres, `/store` 19 G y `/work` 66 G — un preflight que sume 33,6 G y los compare contra
|
||||
|
||||
Reference in New Issue
Block a user