Files
rafa df7bf98ef1 Apunta los scripts de sync prod al Hetzner nuevo y suma trabajo pendiente
- sync_translations_to_prod.py / sync_audio_to_prod.py / sync_carta_from_prod.py:
  migran de FEA_PROD_HOST/PASS (CDMON, password) a FEA_PROD_SSH_HOST/PASS +
  FEA_PROD_DOCKER_CONTAINER (Hetzner/Coolify, auth por clave), con wrapping
  docker exec y el fix del bug de redirecciones (wc -c/cat) resuelto en el host
  en vez de dentro del contenedor.
- fea_translate_helper.php: subcomando clone_new para clonar en ID local nuevo
  cuando el ID de prod ya está ocupado localmente.
- translate_post.py: --dry-run.
- tts_produce.py: --allow-default-voice (voz Nico solo si se permite
  explícitamente para autores sin voz clonada) + fix voice_for_author.
- minimax_tts.py: parámetro speed en t2a/_synth_chunk.
- mirror-antiguo/deploy/nginx-mirror.conf: redirects de URLs históricas de
  catálogo y vista imprimible EFFA.
- Importaciones humanas de Pagola (carta 738) + scripts/manifest de la
  fase A Enrique, y release_issue181_prod.sh / cdmon-retire-fewp1.sh.
- .gitignore: excluye docs/backups/ (dumps SQL, mismo motivo que backups/*).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-05 07:09:29 -04:00
..

mirror-antiguo — mirror estático del Joomla legacy de feadulta

Construcción del mirror HTML read-only de antiguo.feadulta.com (issue rafa/feadulta#180, remediación de #183).

Regla de diseño: el mirror se genera solo por HTTP, nunca copiando el filesystem. Así es imposible arrastrar los .php comprometidos del incidente. Lo que se captura es lo que Joomla renderiza.

Origen del crawl

No es producción: es el Joomla legacy restaurado en local.

Contenedor joomla-mirror-web (php:7.4-apache), red joomla-migration_joomla-net, IP 172.20.0.5
Acceso http://127.0.0.1:8086 o http://antiguo.feadulta.com (entrada en /etc/hosts → 172.20.0.5)
BD joomla_mirror dentro del contenedor joomla-mysql
Snapshot fuente source/antiguo-20260729.tar.gz + source/fejoomla3-20260729.sql.gz, hashes en MANIFEST-source.sha256

⚠️ La imagen base necesitó mysqli, pdo_mysql, gd, zip y mod_rewrite instalados en caliente: se pierden si el contenedor se recrea, no si solo se para/arranca. Para cambiar memoria o política de reinicio usar docker update, nunca docker rm + docker run.

⚠️ WSL2 se apaga cuando no queda ninguna sesión abierta desde Windows, y al apagarse para los contenedores. Antes de un proceso de horas, dejar un proceso ancla vivo en WSL.

Datos del sitio

  • SEF: sef=1, sef_rewrite=1, sef_suffix=1 → las URLs terminan en .html.
  • Un solo idioma de contenido publicado: es-ES (sef es); todo el contenido es language='*'. El sitio hace 301 de /x/es/x, así que el inventario se genera ya con /es/.
  • Solo 2 categorías K2 y un único ítem de menú de K2: buscadoravanzado (Itemid 138). Las URLs de ítem son /es/buscadoravanzado/item/<id>-<alias>.html.
  • 16.269 ítems K2 publicados · 9.079 artículos com_content · 216 ítems de menú · 68 categorías.

Método

El inventario no se reconstruye a mano: lo genera el propio router de Joomla. _genurls.php (en la raíz del sitio restaurado) arranca el framework por HTTP y llama a JRoute::_() y K2HelperRoute::getItemRoute(), así que las URLs son idénticas a las que el sitio imprime.

Cuatro pases, todos acotados por listas — nunca por recursión libre (ver post-mortem del 2026-07-29 en #180 comment-497):

Pase Qué captura Script
A Las 25.437 URLs del inventario 21-crawl-html.sh
B Recursos (css/js/img/mp3/pdf) referenciados por el HTML capturado 30-extract-links.py + 31-fetch-assets.sh
C Huecos: rutas de menú alternativas, páginas de autor de K2, /anterior, /ediciones 43-pendientes.sh
D /anterior, la web estática anterior a Joomla (HTML plano, recursión finita) 37-pase-d-anterior.sh

raw/ es inmutable. site/ es el derivado servible (45-normalize-links.py).

Trampas encontradas (y cómo se resuelven)

  1. Paginación de K2: los enlaces acumulan &start= en vez de reemplazarlo → espacio de URLs infinito. Es lo que tumbó la VM el 29-jul. Se evita capturando por inventario, y el --reject-regex incluye start|limitstart|limit|print|tmpl|format|searchword|task|orderby|filter.
  2. Alias con ? literal: ~198 artículos tienen el signo de interrogación dentro del alias (...-dios-nos-ama?.html). Para cualquier cliente HTTP eso es el separador de query, así que la ruta real es la parte anterior al ? y wget guarda un fichero sin extensión. Es correcto para servirlo estáticamente, pero nginx necesita default_type text/html en esa ubicación o el navegador se lo descargará en vez de mostrarlo.
  3. Assets con cache-busting (core.js?a32fb…): wget mete la query en el nombre del fichero. El paso de normalización deja también una copia con el nombre limpio, que es la que pedirá el servidor estático.
  4. Menús cuyo componente ya no existe (com_surveys, com_breezingforms): JRoute no les construye ruta y devuelven 404. Ya son 404 en producción; para el resto de ítems de menú la ruta autoritativa es la columna path de #__menu (41-menu-paths.sh).

Scripts

Numerados por orden de ejecución en scripts/. 23-estado.sh da el estado en cualquier momento; 22-monitor.sh vigila el crawl y lo aborta si el servidor pasa de 100 respuestas 500 o si la RAM libre baja de 800 MB. 60-restaurar-entorno.sh rearranca los contenedores parados durante el crawl.