# 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](https://gitea.feadulta.com/rafa/feadulta/issues/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/-.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.