Back to zerosuite
zerosuite

El servidor que no dimos de baja: vaciar una máquina de producción en una noche, y cada número que comprobamos

Una noche, seis aplicaciones migradas fila por fila, tres copias de seguridad vacías, una sin destino y un servidor cuyo futuro cambió tres veces.

Juste Thales Gnimavo & Claude | October 2, 2026 19 min zerosuite
EN/ FR/ ES
caspclaude-codeclaude-opus-5.5infrastructuremigrationeasypanelpostgresbackupsdisaster-recoveryssh-hardeningchatwootproven-not-fixedceo-decisionssession-lifecycle

Por Juste Thales Gnimavo (CEO, ZeroSuite) y Claude Opus 5.5 — instancia de Claude Code

Nota sobre clientes: cuatro de las aplicaciones trasladadas en esta sesión pertenecen a clientes de ZeroSuite. Se mencionan como aplicaciones cliente A a D. Se omiten las direcciones de los servidores, los nombres de host de los sitios de clientes y todas las credenciales.

El prompt de la sesión tenía un título claro: vaciar un antiguo servidor dedicado, mover todo lo que seguía vivo al servidor de producción y después dar de baja el antiguo. Al terminar la sesión, nueve horas más tarde, todo se había movido. La baja nunca llegó, y ese servidor es hoy la máquina más importante de la próxima fase de la empresa.

Este artículo es el registro de aquella noche y de la mañana siguiente: seis aplicaciones de producción migradas con recuentos de filas idénticos, una plataforma de pagos conmutada con cuarenta segundos de corte, tres archivos de copia de seguridad que resultaron estar vacíos, una copia de seguridad que «estaba ok» pero no tenía adónde ir, y un servidor de Chatwoot respaldado y restaurado de prueba poco antes de que el propio CEO lo borrara.

El hilo que lo atraviesa todo es una regla de nuestra doctrina de operaciones, escrita hace meses para otro proyecto: «corregido» no es un estado al que se llega. Algo está hecho cuando una observación sobre un objetivo real lo dice, con el comando y su salida. No antes.


Parte 1 — Una ventana de 01:55 a 06:30

El servidor antiguo, una máquina dedicada de Hetzner que llamamos thales-deblo, había acumulado tres años de proyectos: sitios de clientes, un ERP para un cliente de logística, la base de datos y el Redis de nuestra plataforma de pagos 0fee, y un cementerio de proyectos muertos que nadie había mirado en un año.

La sesión había empezado la noche anterior con los dos pasos que no cuestan nada si se hacen bien: inventario y luego copia de seguridad. Treinta y cuatro archivos, 620 MB, SHA256SUMS verificados 34/34 en tres lugares (el servidor antiguo, el nuevo y el Mac del CEO).

A las 01:55 el CEO escribió: *« Il est 01 h 55, on a jusqu'à 6 h 30 pour tout migrer : les sites n'ont pas de visiteurs maintenant. »* Cuatro horas y media sin tráfico para seis aplicaciones. Él se encargó del DNS en tres proveedores; el agente, de todo lo demás.

Lo que hizo realista esa ventana no fue la velocidad. Fue que el método quedó fijado antes de que se moviera el primer byte:

  1. Crear en el destino, vacío y detenido. Cada servicio se creó mediante la API REST de
  2. Easypanel en el servidor nuevo, con cero réplicas y sin dominio.
  3. Tirar, no empujar. El servidor nuevo descargaba los datos del antiguo con una clave SSH
  4. dedicada, restringida a la IP del servidor nuevo con from=, sin reenvío de agente, sin
  5. reenvío de puertos y sin pty. Antes se copiaron los sshd_config y authorized_keys
  6. originales a archivos .bak.
  7. Volcado final con el origen detenido, restauración y, después, un script que cuenta cada
  8. fila de cada tabla en ambos lados y compara las dos listas. Solo imprime una de dos cosas:
  9. COMPTAGE IDENTIQUE o ECART.
  10. El dominio, solo después del DNS. El dominio se asocia al destino únicamente cuando el
  11. cambio de DNS del CEO es visible desde fuera.

El paso 3 es todo el artículo en miniatura. Una restauración que termina con código 0 demuestra que pg_restore se ejecutó. No demuestra que los datos llegaran. Una comparación de recuentos tabla por tabla, sí:

AplicaciónTablasFilasArchivos
Aplicación cliente A707.6184 archivos
Aplicación cliente B118990 archivos, 122,6 MB
Aplicación cliente C543.31067 archivos, 45 MB
Aplicación cliente D (ERP logístico)862.705sin volumen
Un quinto sitiobase de datos vacía

Cada línea: COMPTAGE IDENTIQUE. Dieciséis nombres de host en línea por HTTPS a las 02:52.


Parte 2 — Lo que falló, y por qué salió barato

Nada de lo que falló esa noche fue espectacular. Todo era del tipo de problema que convierte una ventana de cuatro horas en una de siete si lo descubres en el momento equivocado.

El certificado autofirmado. En el primer sitio, el dominio se asoció al servidor nuevo antes del cambio de DNS. Traefik intentó obtener un certificado de Let's Encrypt, falló el desafío y siguió sirviendo el certificado autofirmado CN=Easypanel de Easypanel después del cambio de DNS. La solución fue borrar el dominio y volver a crearlo. La lección entró en el método como paso 4 y no volvió a ocurrir. También importaba por una razón menos evidente: Let's Encrypt permite cinco validaciones fallidas por nombre de host y por hora. Reintentar por reflejo nos habría bloqueado durante el resto de la ventana.

El anycast no es instantáneo. Dos de los proveedores de DNS comparten las mismas direcciones anycast. Una región veía el nuevo registro minutos antes que otra, y Let's Encrypt valida desde varios puntos de observación. Un sitio falló su primer certificado y lo obtuvo en el reintento, a las 02:32. Para un dominio raíz que iba con retraso en un proveedor, el agente esperó a que los dos servidores autoritativos coincidieran antes de asociar el dominio, y dejó escrito por qué: unos minutos de corte para ese sitio hacia las 02:30, en lugar de reiniciar la aplicación antigua y dejar que escribiera en una base de datos a punto de ser abandonada.

El volumen que parecía vacío. Los volúmenes de Easypanel son bind mounts. Leídos desde la ruta convencional _data de Docker con el contenedor detenido, parecen vacíos. La primera copia de una carpeta de archivos subidos de un cliente volvió sin nada dentro. Leer desde la ruta real del bind mount lo resolvió.

Un error de comillas en un recuento. Un $$ dentro de un comando de shell remoto se lo comió el shell equivocado, y un recuento falló. El agente no parcheó el recuento. Volvió a ejecutar toda la migración de esa aplicación desde el origen detenido, porque ambos volcados tenían recuentos de origen coincidentes y una repetición completa era el único resultado que podía defender.

La regla del corte de sesión. A las 03:00 el CEO dijo: migra el ERP, luego cierra esta sesión y sigue en una nueva. La sesión escribió su log, su runbook y su estado de CASP, hizo push y envió el resumen por WhatsApp. Después la conversación siguió, porque el CEO no había terminado. Volvemos a ello en la parte 8.


Parte 3 — Una plataforma de pagos, a plena luz del día, en cuarenta segundos

La aplicación de 0fee ya corría en el servidor nuevo. Su base de datos y su Redis, no: el backend y el worker llegaban a ellos en el servidor antiguo a través de internet público, con sslmode=disable y un Redis sin cifrar. El prompt había marcado esta tanda como decisión del CEO, a plena luz del día, por tratarse de infraestructura de pagos.

A las 07:00 el CEO escribió: « Tu t'occupes de 0fee. »

El agente midió antes de tocar nada:

  • Base de datos de 10 MB, 39 tablas, 1.616 filas; última sesión de pago el 19 de septiembre.
  • Solo dos clientes de esa base de datos y de ese Redis, confirmados buscando en el entorno de
  • cada servicio del servidor antiguo.
  • Redis: doce claves. Tres sesiones del panel, dos contadores de limitación de peticiones,
  • enlaces de Celery que se reconstruyen al arrancar y **ninguna tarea pendiente en ninguna
  • cola**.

Después, un ensayo con la producción todavía en marcha: volcado completo, restauración en el nuevo Postgres, comparación de recuentos. COMPTAGE IDENTIQUE en 2,3 segundos.

Y luego, el paso real:

text07:10:55  backend and worker scaled to 0
          final dump → COMPTAGE IDENTIQUE, 0 restore errors
          7 variables rewritten in Easypanel (DATABASE_URL, REDIS_*, CELERY_*)
          docker service update --env-add … --replicas 1   (same image, no rebuild)
07:11:37  both services back at 1/1

Cuarenta y dos segundos. La prueba no fue el endpoint de salud, que responde sin tocar la base de datos. Fueron GET /v1/countries y GET /v1/payin-methods a través de la API pública, devolviendo 200 países y 115 métodos de pago (el origen tenía 115), con ambas peticiones visibles en los logs del nuevo backend, mientras la base antigua mostraba exactamente una conexión: la consulta de control del propio agente.

Se tomaron dos decisiones sin el CEO, registradas como tales, cada una con una forma de deshacerla en una línea:

  • Las claves de Redis no se copiaron. El coste: tres personas conectadas al panel de 0fee
  • tendrían que volver a iniciar sesión. Una copia habría obligado a sincronizar un archivo RDB
  • con una configuración AOF activa, para tres sesiones.
  • **Las variables se aplicaron con docker service update, no con un redespliegue de
  • Easypanel.** Un redespliegue reconstruye la imagen desde la rama principal. En plena
  • conmutación de pagos, eso habría puesto en producción, como efecto secundario, lo que hubiera
  • en main. Easypanel guarda los mismos valores, así que el próximo despliegue normal sigue
  • siendo coherente.

Se generaron contraseñas nuevas para ambos servicios. No se reutilizó ninguna credencial antigua, y ninguna apareció en la terminal, en el log ni en este artículo.


Parte 4 — Tres copias de seguridad vacías

Entonces el CEO cambió el plan. *« On ne supprime pas ce serveur : son objectif est de l'utiliser pour 0seat.dev. On le réinstalle après migration de tous les services. »*

Una reinstalación borra el disco. Así que la pregunta pasó a ser: para cada proyecto muerto de ese servidor, ¿es la copia de seguridad lo bastante buena para recuperarlo el día en que nadie recuerde nada de él?

El agente volvió a la copia de 620 MB de la noche anterior, la de las 34/34 sumas de verificación comprobadas, y listó cada archivo con su tamaño:

text110 ./volumes/deblo-ai_fne_fne-data.tgz
109 ./volumes/deblo-ai_santecloud_santecloud-sqlite-db.tgz
110 ./volumes/client-d_filebrowser_database.tgz

Tres archivos de unos cien bytes. Un tar vacío comprimido ocupa más o menos eso. Las carpetas reales contenían invoices.json, users.json y una base de datos SQLite. Las sumas de verificación eran perfectas, porque una suma de verificación demuestra que un archivo no ha cambiado. No dice nada de si contiene lo que crees que contiene. El error de ruta de volúmenes de la parte 2 se había corregido para la migración, pero estos tres archivos se habían creado el día anterior a la corrección.

Así que el agente reconstruyó el archivo, y de paso cambió el método:

  • Volcados lógicos, no carpetas de datos. Para cada Postgres, la carpeta de datos se
  • copió, la copia se montó en un contenedor postgres:17 iniciado con --network none y se
  • volcó con pg_dump -Fc. Postgres justificó la elección de inmediato con una advertencia: los
  • datos se habían creado con glibc 2.36 y la imagen actual usa la 2.41, un desajuste de versión
  • de collation. Una carpeta de datos en bruto restaurada sobre una imagen más reciente hereda
  • ese problema. Un volcado lógico, no.
  • Una prueba de restauración, no una suma de verificación. Cada volcado se restauró en una
  • base de datos vacía y se contó contra los recuentos tomados en el origen. Nueve bases de
  • datos, nueve IDENTIQUE. La mayor era la antigua plataforma de pagos de Déblo: 113 tablas,
  • 1.380.661 filas. Una restauración mostró un error: un permiso concedido a un rol llamado
  • admin, que existe en el globals.sql del archivo. El runbook ahora indica restaurar ese
  • archivo primero.

El archivo final cubre los siete proyectos del servidor, muertos o migrados: 64 archivos, 719 MB, verificados por suma de verificación en el servidor de producción y en el Mac.

La siguiente pregunta del CEO fue si alquilar un pequeño servidor Easypanel de 11 $ al mes para mantener vivos y listos los proyectos muertos. La primera respuesta del agente fue que no: un proyecto muerto necesita sus datos, no un servidor en marcha, y una máquina olvidada con código de pagos y de transferencias de dinero es un pasivo más que un seguro. Una hora de trabajo de restauración, frente a una licencia que llevará meses, no justificaba 132 $ al año.

Entonces el CEO señaló que el proyecto de transferencias de dinero ya tenía su propio servidor, usado solo para su Chatwoot. El agente cambió de postura y dijo por qué. El coste ya no era un servidor nuevo, sino solo mantener uno existente. Y una restauración sobre un Easypanel real demuestra algo que ningún archivo demuestra: que cada proyecto arranca de verdad. Ese segundo argumento es el que cuenta. Un archivo que has restaurado una vez es un hecho. Un archivo al que solo le has comprobado la suma de verificación es una esperanza.


Parte 5 — «Backup ok»

El CEO activó las copias de seguridad programadas de bases de datos de Easypanel para las bases migradas y escribió: « backup 0fee ok sur le nouveau serveur. »

El agente listó las configuraciones de copia de seguridad a través de la API. Seis bases de datos, todas activadas. Cinco apuntaban al bucket S3 de Hetzner. La sexta, la de 0fee, tenía un ID de proveedor de almacenamiento vacío. Se habría ejecutado cada hora, para siempre, sin enviar nada a ninguna parte. El panel la mostraba activada, y eso es justo lo que hacía razonable decir «ok».

El agente la asoció al mismo bucket (reversible con una llamada a la API, registrado como decisión tomada sin el CEO), la lanzó a mano y luego dio el paso que convierte «configurado» en «hecho»: leyó las credenciales de S3 desde Easypanel a memoria, sin imprimirlas nunca, y listó el bucket.

textzerosuite/0fee_postgres      1 file  ('2026-10-02 07:55', 73670)
client-c/postgres            0 files
...

Las otras cinco también estaban vacías, por una razón menos preocupante: su primera ejecución programada aún no se había producido. Una ejecución manual para cada una, cuarenta segundos de espera, y el listado mostraba seis archivos. Solo entonces el runbook dijo probado.

La doctrina tiene una fórmula para lo que el log de la sesión llevaba hasta ese momento: Preuve due: <observation> — sur <cible> — bloquée par <ce qui manque>. Prueba pendiente. No es un estado de fallo; es el estado honesto de algo configurado y aún no observado. El error es saltárselo.


Parte 6 — Respaldado justo antes de ser borrado

El servidor del proyecto de transferencias de dinero resultó tener una instalación nativa de Chatwoot, sin Docker: Rails, nginx, Postgres 16 y Redis en la propia máquina, versión 4.4.0. El CEO quería respaldarlo y después reconstruir el servidor con Easypanel para alojar los proyectos muertos.

El primer mensaje del agente sobre el tema fue una restricción, no un plan: **primero respaldar Chatwoot, después instalar Easypanel, sin discusión.** Easypanel ocupa los puertos 80 y 443 para su propio proxy y pasa Docker a modo Swarm. Instalado encima de un Chatwoot en funcionamiento, rompe el sitio de soporte.

La inspección, en solo lectura, encontró la materia prima del plan y dos cosas que nadie había preguntado:

  • 1.115 conversaciones, 4.311 mensajes, 199 adjuntos (78 MB) almacenados en un bucket S3 en
  • lugar de en el disco. Quince mensajes en los últimos treinta días.
  • El puerto 3000, el del servidor Rails, accesible desde internet sin pasar por nginx, en
  • una máquina sin cortafuegos activado.
  • Cuatro cuentas creadas por desconocidos, con el registro desactivado: test dos veces en
  • mayo, vulncheck en julio, poc-zjr5 al día siguiente, desde direcciones en scanner.test y
  • test.local. Sus usuarios nunca confirmaron un correo ni iniciaron sesión. Los logs de nginx
  • de esas fechas ya se habían eliminado por rotación, así que ya no se puede establecer cómo
  • entraron.

El agente lo dejó por escrito como hallazgo, no borró nada y recomendó no restaurar esas cuatro cuentas. Después respaldó la base de datos, los globals y la configuración, y **restauró el volcado de prueba** en un postgres:16 vacío: 5 cuentas, 1.115 conversaciones, 4.311 mensajes, 199 blobs, cero errores.

Poco después, el CEO contó que había reinstalado el servidor desde la consola de Hetzner, con Ubuntu 24 y Easypanel. El Chatwoot original ya no existía. Quedaba la copia de seguridad, restaurada una vez y contada. La regla de respaldar primero no era prudencia por sí misma. Fue lo que decidió si esta historia tiene un hueco.


Parte 7 — Lo que una reinstalación restablece

Esa misma mañana, más temprano, el CEO había pedido cerrar el acceso SSH por contraseña en ese servidor. El agente vio que authorized_keys tenía exactamente una clave, la suya, lo que significaba que el CEO había estado entrando con contraseña. Lo dijo antes de cortar, aplicó PasswordAuthentication no y PermitRootLogin prohibit-password en un archivo drop-in y comprobó ambos sentidos: una nueva sesión con clave funciona, y un intento con contraseña recibe Permission denied (publickey).

Tras la reinstalación, la primera inspección del sistema nuevo mostraba otra vez passwordauthentication yes. Una reinstalación restablece los valores por defecto del proveedor, incluido el que cerraste una hora antes. El endurecimiento se volvió a aplicar y a comprobar de la misma manera.

El token de API del nuevo Easypanel llegó por el portapapeles del CEO. El agente lo leyó con pbpaste en un archivo con modo 600, imprimió solo su longitud, lo probó con una llamada listProjects que devolvió 200 [] y vació el portapapeles. Cuando el CEO propuso añadir el servidor con una clave privada existente, el agente generó en su lugar una clave dedicada, para que un acceso pueda revocarse sin tocar ningún otro.


Parte 8 — Un servidor cuyo destino cambió tres veces

A lo largo de la sesión, el plan para thales-deblo cambió tres veces:

  1. Darlo de baja tras una semana de observación (el prompt de la sesión).
  2. Conservarlo y reinstalarlo para 0seat.dev una vez migrado todo (el CEO, a las 07:00).
  3. Sustituye al servidor nuevo que íbamos a comprar (el CEO, a las 07:40). Eso eliminó por
  4. completo una compra de la próxima fase. El agente midió la máquina antes de aceptar que
  5. servía: Ryzen 7 7700, 64 GB, dos NVMe de 1 TB en RAID y un tercer NVMe de 2 TB **que ni
  6. siquiera está montado**, en Helsinki. Es una máquina mejor que la del plan.

Ninguno de esos cambios le correspondía al agente. Lo que hizo cada vez fue actualizar todos los lugares que contenían la decisión anterior: la tabla de decisiones del CEO en el CLAUDE.md del proyecto, el prompt de la sesión (con un aviso fechado de «revisado» en lugar de una reescritura silenciosa), el runbook, el siguiente prompt de la cola y el estado de CASP. El periodo de observación pasó de siete días a dos, por decisión del CEO, y el agente estuvo de acuerdo por una razón explícita: un archivo completamente verificado cumple ahora la función de red de seguridad que antes cumplían los datos del servidor antiguo. También señaló el límite: 0fee no había tenido ningún pago desde el 19 de septiembre, así que dos días de observación no pondrán a prueba un pago real sobre la nueva base de datos.

Para entonces la sesión ya se había compactado una vez. Nuestra doctrina dice: tras una compactación, termina el trabajo en curso y no abras uno nuevo. Cuando el CEO pidió restaurar los proyectos muertos en el servidor instanly, el agente hizo la inspección, la copia de seguridad y el endurecimiento (unos minutos cada uno, cada uno cerrando algo ya empezado) y dejó la restauración en sí escrita en un nuevo prompt de sesión, con una sección DO NOT encabezada por la frase que más importa en proyectos que mueven dinero: **no arrancar ningún worker, cron ni cola de estos proyectos sin haber leído antes su código y sus variables.** Después cerró la fase en CASP y le dijo al CEO que podía cerrar.


Lo que nos llevamos de esta sesión

  1. Una restauración que termina con código 0 demuestra que el comando se ejecutó. Una
  2. comparación de recuentos de filas tabla por tabla demuestra que los datos llegaron.
  3. Automatízala para que solo pueda imprimir una de dos palabras.
  4. Una suma de verificación demuestra que un archivo no ha cambiado, no que contenga algo.
  5. Tres de nuestros 34 archivos verificados eran tars vacíos.
  6. Respalda las bases de datos como volcados lógicos a partir de una copia, en un contenedor
  7. sin red, y restaura cada una al menos una vez. Las carpetas de datos en bruto arrastran su
  8. glibc.
  9. «Activado» no es «respaldado». Una copia programada está probada cuando aparece un
  10. archivo en el listado del destino.
  11. Ensaya la conmutación con la producción en marcha. 2,3 segundos medidos valen más que
  12. 40 segundos prometidos.
  13. Respalda antes de instalar, siempre, incluso cuando el servidor «solo tiene un
  14. Chatwoot».
  15. Una reinstalación deshace tu endurecimiento. Vuelve a comprobarlo en el primer acceso.
  16. Cuando el CEO cambia el plan, actualiza cada documento que contenía el anterior, con un
  17. aviso fechado donde un lector pueda tener la versión antigua.
  18. Cambia de postura en voz alta. Di qué argumento nuevo te hizo cambiar. «Tienes razón» no
  19. es un argumento.

CASP — el Coding-Agent State Protocol. Tu agente de IA lleva toda la hoja de ruta y no puede perder el hilo. Nativo de Git, solo local, MIT, cero telemetría. Creado por Juste Thales Gnimavo, de ZeroSuite, un CEO en solitario cuyos productos funcionan en producción con Claude como único ingeniero. Instalación: npm i -g @justethales/casp · https://casp.sh · https://github.com/ThalesGnimavo/casp
Share this article:

Responses

Write a response
0/2000
Loading responses...

Related Articles

Thales & Claude zerosuite

El orquestador no programa: trece sesiones en un día, un navegador que verifica y saber cuándo parar

Un día, una plataforma privada de un cliente, trece sesiones headless de Claude encadenadas por un orquestador que nunca escribe código. Verificar en el navegador del CEO, agrupar correcciones sin soltar la gate y escribir cuándo cerrar una sesión.

19 min Sep 30, 2026
caspclaude-codeclaude-opus-5.5multi-session +12
Thales & Claude zerosuite

Una nueva incorporación que solo escribe notas privadas: conectar una IA a un soporte al cliente en producción sin dejarla hablar con un solo cliente

Un día, un soporte al cliente, 12 166 conversaciones pasadas: cómo conectamos una IA a un soporte Chatwoot en producción, solo en modo borrador. Escribe notas privadas, cita sus fuentes o pasa el caso, lee capturas de pantalla pero nunca PDF, y cada llamada al modelo tiene precio. Cuatro auditorías, errores incluidos.

28 min Sep 28, 2026
chatwootcustomer-supportragpgvector +9
Thales & Claude zerosuite

Cero código entregado: llevar una búsqueda de empleo como un proyecto de software, con una IA que lo prepara todo y no envía nada

Un día, una sesión con una IA, ninguna línea de código de producto: una búsqueda de empleo llevada con las herramientas que ZeroSuite usa para entregar software, como método válido para cualquier oficio. Un repositorio privado, un único archivo de seguimiento donde «enviado» significa «fechado», reglas que rechazan la frase indemostrable, una hoja de ruta CASP que termina en un contrato firmado y una automatización que llena los borradores sin pulsar nunca Enviar. Con una guía paso a paso para descargar.

17 min Sep 24, 2026
job-searchcareercaspclaude-code +7