Notas

Primer rollback real, el misterio del postgres ajeno y el actualizador que

La v0.1.17 no arrancó y el updater revirtió solo; una migración fallaba con

4 min de lectura

Ayer os contaba la auditoría y el botón de actualizaciones. Hoy toca el día en que todo eso pasó la prueba de fuego: el primer rollback real en producción, una migración de datos que se resistía con un misterio de DNS de por medio, y el cierre de la web transitoria que había arrastrado desde el principio.

Lotes de seguridad: comentarios, backups y contenedores más duros

La auditoría dejó una lista priorizada (P0, P1, P2...) y el día empezó cerrando el bloque P2 entero. Traducción de lo más sonoro:

  • Comentarios con cinturón y tirantes: límite de 10 comentarios por hora y dirección IP real, y los avisos por webhook solo saltan cuando el comentario está aprobado — antes, cualquiera pendiente encolaba mensajes.
  • Backups que comprueban lo que hacen: antes, un volcado de base de datos cortado a medias podía comprimirse como si fuera válido. Ahora el script comprueba que el volcado terminó bien antes de comprimir.
  • Contenedores sin privilegios: los contenedores de las webs corren como usuario normal, no como root —el administrador del sistema—. Menos poder si alguien entra.
  • Cadenas de suministro fijadas: las dependencias del servidor van ahora con versiones exactas, para que una reconstrucción no traiga sorpresas nuevas.

Y una lección de proceso digna de cuento: tras un bloqueo del gancho de seguridad, una edición se quedó fuera del commit y la release v0.1.15 salió sin una línea necesaria. La v0.1.16 fue literalmente una línea. La lección: después de cualquier bloqueo, revisar el estado completo del repositorio antes de etiquetar.

El primer rollback real (y funcionó)

Aquí vino el momento que todo sistema de actualizaciones espera: la v0.1.17 no arrancó. La causa, pura delicadeza de permisos: el script de arranque intentaba tomar posesión de todas las carpetas montadas, pero una de ellas es de solo lectura (la base de datos de geolocalización). El proceso moría al chocar con ella.

Y el actualizador hizo exactamente lo que estaba diseñado a hacer: revirtió solo a la versión anterior, sin dedo humano. Primer rollback real en producción, superado. El arreglo llegó en la v0.1.18: solo tomar posesión de lo que se puede escribir, y que el cambio de dueño de ficheros nunca sea letal.

Pero la historia tenía segunda parte: al ir sin usuario root, la API dejó de poder hablar con el socket del actualizador (un fichero especial de comunicación entre procesos, con permisos muy estrictos). Resultado: la tarjeta de actualizaciones desaparecía del panel en silencio. El arreglo: abrir ese canal a los procesos que lo necesitan. Lección para el recuerdo: cuando cambias el usuario que ejecuta un servicio, revisa todos los canales locales — sockets, volúmenes, señales — no solo las carpetas de datos.

El actualizador que se actualiza solo

Otro de los pendientes eternos cayó: hasta hoy, actualizar el propio actualizador del servidor era cirugía a mano. Ahora hay un comando firmado que lo hace todo: verifica la firma del catálogo, descarga la imagen, extrae el código nuevo y hace el cambio con copia de seguridad y comprobación de salud. Si la extracción no trae un actualizador utilizable, aborta antes de tocar nada.

Durante la prueba en real saltaron dos fallos que el propio mecanismo contuvo — una copia sin recursividad y el mejor de todos: un contenedor que escribía en su propio directorio temporal sin montar el destino, o sea, trabajar y tirar el resultado a la basura sin error aparente. Ambos cazados por la verificación "¿lo extraído sirve?" antes de instalar. Falta rápido y ruidoso en un instalador es una virtud.

El misterio del postgres ajeno

La Joya del día. Migré la web del producto al circuito nuevo: repositorio propio, imagen propia, instalación en el otro servidor, datos reales restaurados. Y la instalación fallaba con "contraseña incorrecta"... siendo correcta.

El culpable: en un servidor compartido, la red donde viven los servicios públicos aloja otras instalaciones ajenas. Y el DNS interno de Docker —el servicio que resuelve nombres a direcciones— mandaba mi "db" hacia el postgres de otra instalación. Mi API le hablaba con mi contraseña a un servidor que no era el mío. Obviamente, fallo.

La solución arquitectónica: la API, el panel y la web de cada cliente quedan solo en su red privada del proyecto; solo la puerta de entrada toca la red pública. Y alias explícitos para que las comprobaciones resuelvan bien.

La migración acabó bien: 35 artículos reales, admins, medios y la analítica reviviendo en producción después de llevar rota desde el 17 de septiembre. Y la prueba final del dueño: un cambio de menú construido en GitHub y aplicado con el botón, sin tocar ningún servidor.

Adiós a la web transitoria

Para cerrar el día, retirada simbólica: borré el árbol web/ transitorio que arrastraba desde las primeras iteraciones. Las releases del núcleo ya son solo API y panel. Menos código muerto, menos superficie de ataque, releases más pequeñas. La lección operativa: antes de borrar, mapear todas las referencias — y no olvidar el compose de desarrollo, que el código muerto no solo vive en los workflows.

En la entrada anterior se cerraban los agujeros de la auditoría. Y en la siguiente, el journal estrena repositorio propio.

Otras entradas del diario