Mirror del Joomla antiguo: versionar los scripts y reponer los assets que faltaban

Los scripts del mirror (00-90) vivian solo en el disco. Van al repo; los datos que
generan no (16 GB entre crawl, snapshot del origen y Joomla restaurado) -> .gitignore.

Nuevo 91-repone-assets404.sh: repone los ficheros que el crawl no capturo porque se
referencian SOLO desde CSS y el crawler seguia enlaces HTML (system.css, los fondos de
fe_adulta_1, ratingstars.gif de K2). Salian como 404 en los logs de nginx del Hetzner.

Descarga por HTTP desde el Joomla local aislado, nunca del filesystem -- mismo principio
que el crawl, para no arrastrar los .php comprometidos del #183 -- y escanea PHP embebido
antes de copiar a site/.

Resultado sobre las 286 rutas unicas con 404 del log: 196 repuestas y verificadas en
produccion (196/196 en 200 tras el rsync), 82 que dan 301->404 tambien en el origen (ya
estaban rotas en la web original) y 8 rutas basura /%22/... de HTML mal formado.

Refs #180

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-31 14:04:31 -04:00
parent 69e849d38e
commit 275aff1430
71 changed files with 2667 additions and 0 deletions
+76
View File
@@ -0,0 +1,76 @@
# 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/<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.