Commit 8feb0cff by arquitecturati

Se aagrega readme y profiles en dockerfile

parent 56b79471
......@@ -21,3 +21,10 @@ KOHA_INTRA_SERVER_NAME=koha-intra.local
# Memcached (interno al contenedor — no cambiar)
MEMCACHED_SERVERS=127.0.0.1:11211
# Timezone para conexiones MySQL/MariaDB
# Si la BD NO tiene cargadas las tablas mysql.time_zone* (típico en MariaDB
# self-hosted), usar un offset UTC: -05:00 para Bogotá (UTC-5, sin DST).
# Si la BD tiene las tablas cargadas (mysql_tzinfo_to_sql), se puede usar
# America/Bogota directamente.
KOHA_TIMEZONE=-05:00
......@@ -27,6 +27,15 @@ RUN wget -qO- https://debian.koha-community.org/koha/gpg.asc \
RUN apt-get update && apt-get install -y koha-common \
&& rm -rf /var/lib/apt/lists/*
# Bug en scripts shell de Koha: usan "xmlstarlet sel -t -v" sin -T, lo que
# re-escapa entidades XML al output (& → &). Passwords con caracteres
# como & quedan mal cuando koha-mysql los pasa al cliente mysql. Parche:
# agregar -T para que xmlstarlet emita texto plano decodificado.
RUN for f in /usr/sbin/koha-* /usr/share/koha/bin/*.sh; do \
[ -f "$f" ] && grep -q "xmlstarlet sel -t" "$f" && \
sed -i 's|xmlstarlet sel -t |xmlstarlet sel -T -t |g' "$f"; \
done; true
# List::MoreUtils: la versión de Ubuntu 20.04 no exporta zip6 correctamente.
# cpanm instala una versión consistente PP+XS desde CPAN.
RUN apt-get update && apt-get install -y cpanminus build-essential && \
......@@ -54,7 +63,12 @@ RUN apt-get update && \
echo "MySQL client activo: $(mysql --version)"
# Fase 3.7 — Habilitar módulos Apache requeridos
RUN a2enmod rewrite cgi headers proxy_http
# remoteip: leer X-Forwarded-For del ALB y reemplazar REMOTE_ADDR
RUN a2enmod rewrite cgi headers proxy_http remoteip
# Fase 3.8 — Configuración para ALB / reverse proxy
COPY docker/alb-proxy.conf /etc/apache2/conf-available/alb-proxy.conf
RUN a2enconf alb-proxy
# --- Archivos de migración ---
# SQL dump de la base de datos (Fase 4.1)
......
# Koha Humboldt — Migración a Docker
Instancia dockerizada de Koha 25.11 (Ubuntu 20.04) para el catálogo del Instituto Humboldt. Incluye OPAC, intranet, Plack, Zebra, Memcached y RabbitMQ corriendo en un solo contenedor, con perfiles para alternar entre BD local (MariaDB) y BD en la nube (RDS).
## Arquitectura
```
┌────────────────────────────────────┐
│ Contenedor koha │
Browser ──→ │ Apache + Plack + Zebra │ ──→ MariaDB
(nginx/ALB) │ + Memcached + RabbitMQ │ (local | RDS)
│ - mod_remoteip lee X-Fwd-For │
│ - VirtualHost por dominio (Host:) │
└────────────────────────────────────┘
```
El contenedor está diseñado para correr detrás de un reverse proxy con terminación TLS. En local se simula con un servicio `nginx`; en producción se reemplaza por un ALB de AWS.
## Requisitos
- Docker Desktop con Docker Compose v2
- OpenSSL (para generar el certificado autofirmado del proxy local)
- Permisos de administrador para editar `C:\Windows\System32\drivers\etc\hosts`
- Permisos de administrador para importar el certificado raíz local
## Setup inicial (una sola vez)
### 1. Copiar el `.env`
```bash
cp .env.example .env
```
Editar `.env` y ajustar las credenciales según el entorno:
| Variable | Valor para BD local | Valor para RDS |
|----------|---------------------|----------------|
| `DB_HOST` | `db` | `<endpoint>.rds.amazonaws.com` |
| `DB_PORT` | `3306` | `3306` |
| `DB_NAME` | `koha_prod_humboldt` | `koha_prod_humboldt` |
| `DB_USER` | `db_koha_prod_humboldt` | el usuario que creaste en RDS |
| `DB_PASSWORD` | tu password local | el password del RDS |
| `DB_ADMIN_PASSWORD` | password root MariaDB local | (no se usa con RDS) |
| `KOHA_OPAC_SERVER_NAME` | `koha.local` | `catalogo.humboldt.org.co` |
| `KOHA_INTRA_SERVER_NAME` | `koha-intra.local` | `intranet.humboldt.org.co` |
### 2. Configurar `/etc/hosts`
Abrir `C:\Windows\System32\drivers\etc\hosts` como administrador y agregar:
```
127.0.0.1 koha.local koha-intra.local
```
### 3. Generar certificados TLS para el proxy local
```bash
bash docker/gen-local-certs.sh
```
Crea `docker/certs/local.crt` y `docker/certs/local.key`.
### 4. Confiar en el certificado raíz (Windows)
En PowerShell como administrador:
```powershell
Import-Certificate `
-FilePath "<ruta-al-repo>\docker\certs\local.crt" `
-CertStoreLocation Cert:\LocalMachine\Root
```
Cerrar y volver a abrir Chrome.
## Ejecución
### Modo local (MariaDB en Docker + proxy nginx con HTTPS)
```bash
docker compose --profile local up --build
```
Activa los servicios `db`, `koha` y `proxy`. El **primer arranque tarda 10-20 minutos**: importa el dump SQL, ejecuta `koha-upgrade-schema`, reindexar Zebra y aplica todos los parches. Los reinicios posteriores son rápidos gracias al `INIT_FLAG` persistido en el volumen.
### Modo RDS (BD en la nube, sin proxy local)
```bash
docker compose up --build
```
Sin `--profile local` solo arranca el contenedor `koha`. Útil para probar contra RDS antes de subir a ECS.
## Acceso a la aplicación
### Con perfil local (recomendado)
| URL | Descripción |
|-----|-------------|
| `https://koha.local` | OPAC (catálogo público) — pasa por nginx, simula el ALB |
| `https://koha-intra.local` | Intranet — pasa por nginx |
| `http://koha.local:8080` | OPAC directo a Apache (sin SSL) — útil para debug |
| `http://koha-intra.local:8080` | Intranet directo a Apache |
### Sin proxy local
| URL | Descripción |
|-----|-------------|
| `http://koha.local:8080` | OPAC directo a Apache |
| `http://koha-intra.local:8080` | Intranet directo a Apache |
> El acceso por `http://localhost:8080` muestra la página default de Apache porque el header `Host` no coincide con ningún VirtualHost. Usar siempre los dominios configurados.
## Comandos útiles
```bash
# Ver logs en vivo
docker compose --profile local logs -f koha
# Ver estado de los servicios
docker compose --profile local ps
# Entrar al contenedor
docker compose --profile local exec koha bash
# Verificar Plack
docker compose --profile local exec koha koha-plack --status prod_humboldt
# Forzar reindexación de Zebra
docker compose --profile local exec koha koha-rebuild-zebra -f -v prod_humboldt
# Parar sin borrar volúmenes (preserva BD local + INIT_FLAG)
docker compose --profile local down
# Borrar todo y forzar reimportación
docker compose --profile local down -v
```
## Health check
El contenedor expone dos endpoints de health check según la fase del ciclo de vida:
| Fase | Puerto | Quién responde | Cuánto dura |
|------|--------|----------------|-------------|
| FIRST_RUN (importación de BD, schema upgrade, reindex) | `8099` | Python `http.server` en background | 10–20 min |
| Operación normal | `80` | Apache → Plack → Koha | indefinido |
El servidor en `:8099` arranca como primera acción del entrypoint y se mata justo antes de que Apache tome el control. Esto evita que ECS marque el task como unhealthy y lo reinicie durante el FIRST_RUN, que es lo más caro de reproducir.
### Verificación local
```bash
# Mientras el contenedor inicializa (FIRST_RUN)
docker compose exec koha wget -qO- http://localhost:8099/
# → starting
# Después de que arrancan los servicios
docker compose exec koha wget -qO- -S http://localhost/ 2>&1 | head -1
# → HTTP/1.1 200 OK
```
### Configuración ECS (task definition)
```json
"healthCheck": {
"command": [
"CMD-SHELL",
"wget -qO /dev/null http://localhost:8099/ 2>/dev/null || wget -qO /dev/null http://localhost/ 2>/dev/null || exit 1"
],
"interval": 30,
"timeout": 10,
"retries": 3,
"startPeriod": 60
}
```
El `||` encadena las dos pruebas: durante el FIRST_RUN responde `:8099`; cuando Apache toma `:80` el primer comando falla y cae al fallback. `startPeriod: 60s` da margen al proceso Python para hacer `bind()`.
### Configuración ALB (target group)
| Parámetro | Valor |
|-----------|-------|
| Protocol | HTTP |
| Port | 80 |
| Path | `/` |
| Healthy threshold | 2 |
| Unhealthy threshold | 5 |
| Interval | 30s |
| Timeout | 10s |
| Success codes | `200-399` |
El ALB chequea `:80`, no `:8099`. Durante el FIRST_RUN el target aparece `unhealthy` en el ALB → simplemente no recibe tráfico (el `startPeriod` del task health check evita que ECS lo reinicie). Cuando Apache arranca, el ALB lo marca `healthy` y empieza a enrutar.
> Importante: el container health check (ECS) y el target group health check (ALB) son independientes. El primero controla el ciclo de vida del task; el segundo, el routing de tráfico.
## Decisiones de diseño relevantes
- **Health check temporal en puerto 8099**: durante el FIRST_RUN (10-20 min) un servidor HTTP en Python responde 200 en `:8099` para que ECS no reinicie el task por timeout. Apache toma `:80` cuando termina.
- **mpm_itk + LD_PRELOAD**: Docker no otorga `CAP_SETGID`, así que `mpm_itk` falla al llamar `initgroups()`. La librería `libfakegroups.so` compilada en el Dockerfile intercepta esa llamada y devuelve 0.
- **mysql-client-8.0**: Ubuntu 20.04 instala el cliente MariaDB por default, que no soporta `caching_sha2_password` de MySQL 8. Se reemplaza con `mysql-client-8.0` para compatibilidad con RDS MySQL 8.
- **Filtros en el dump**: el SQL dump original tiene FKs y GRANTs incompatibles. El entrypoint filtra `CONSTRAINT ... FOREIGN KEY`, `ADD CONSTRAINT`, `GRANT`, `CREATE USER`, etc. antes de pasarlo a `koha-mysql`.
- **Log files pre-creados**: `opac-error.log` e `intranet-error.log` se tocan con ownership `prod_humboldt-koha` antes de iniciar servicios, para que Apache (root) y Plack (instance-user) puedan escribir en los mismos archivos.
- **ALB headers**: `mod_remoteip` confía en `X-Forwarded-For` de RFC 1918; `X-Forwarded-Proto: https` se mapea a la variable `HTTPS=on` para que Koha emita cookies `Secure` y URLs absolutas con esquema correcto.
## Despliegue a producción (ECS)
Resumen — para detalles ver la conversación de migración.
1. **ECR**: build + push de la imagen
2. **RDS MariaDB/MySQL 8**: crear DB y usuario `koha_prod_humboldt` con `ALL PRIVILEGES` (sin `WITH GRANT OPTION`)
3. **EFS**: filesystem con 2 access points (`/koha_data`, `/koha_logs`) con UID/GID 1000
4. **Secrets Manager**: guardar `DB_PASSWORD`
5. **ECS Task Definition**: Fargate, 2 vCPU / 4 GB RAM, montar EFS. Health check según la sección [Health check](#health-check).
6. **ALB**: HTTPS:443 → target group puerto 80 (un solo TG sirve ambos dominios — Apache enruta por `Host` header). HTTP:80 → redirect 301 a HTTPS.
7. **ACM**: certificado con SANs `catalogo.humboldt.org.co` + `intranet.humboldt.org.co`
8. **DNS**: dos CNAME apuntando al DNS name del ALB
## Troubleshooting
### Las URLs se quedan cargando
Revisar los logs de Plack:
```bash
docker compose --profile local exec koha tail -20 /var/log/koha/prod_humboldt/plack-error.log
```
Si dice `Permission denied` en `opac-error.log`, los archivos de log quedaron con ownership root. Fix manual:
```bash
docker compose --profile local exec koha bash -c "
chown prod_humboldt-koha:prod_humboldt-koha \
/var/log/koha/prod_humboldt/opac-error.log \
/var/log/koha/prod_humboldt/intranet-error.log && \
koha-plack --restart prod_humboldt"
```
### Página default de Apache al entrar a `:8080`
El header `Host` no coincide con ningún VirtualHost. Usar `koha.local:8080` o `koha-intra.local:8080`, no `localhost:8080`.
### Chrome bloquea HTTPS con "Unsafe attempt to load URL"
Limpiar el caché HSTS en `chrome://net-internals/#hsts` para los dominios, o importar el cert raíz local (paso 4 del setup).
### `ERROR 1044: Access denied for user ... to database`
El dump contiene `GRANT` statements que el usuario de BD no puede ejecutar. El entrypoint las filtra, pero requiere rebuild:
```bash
docker compose down -v
docker compose up --build
```
### El container reinicia en loop durante el FIRST_RUN
Si tarda más de los 10-20 min esperados, verificar:
- Conectividad a la BD (`DB_HOST`, `DB_PORT`, security groups en RDS)
- Espacio en disco del volumen de Docker / EFS
- Logs: `docker compose logs koha`
services:
# -------------------------------------------------------
# BD local para pruebas — reemplaza al RDS en desarrollo.
# Para usar RDS real: comentar este bloque y ajustar .env
# BD local para pruebas — solo activa con: --profile local
# Para RDS: no pasar el perfil y ajustar DB_HOST en .env
# -------------------------------------------------------
db:
profiles: ["local"]
image: mariadb:10.6
environment:
MYSQL_ROOT_PASSWORD: ${DB_ADMIN_PASSWORD}
......@@ -24,19 +25,38 @@ services:
ports:
- "8080:80"
env_file: .env
environment:
# Apuntar al servicio db local en vez del RDS
DB_HOST: db
depends_on:
db:
condition: service_healthy
required: false # ignorado cuando db no está activa (perfil RDS)
volumes:
# Persistir estado de inicialización, uploads, plugins e índices Zebra.
# Si se elimina este volumen, el entrypoint ejecuta la restauración completa.
- koha_data:/var/lib/koha
- koha_logs:/var/log/koha
restart: unless-stopped
# -------------------------------------------------------
# Proxy nginx local — simula el ALB con terminación TLS.
# Solo activa con: --profile local
#
# Antes del primer uso ejecutar:
# bash docker/gen-local-certs.sh
#
# Flujo de prueba ALB:
# https://koha.local → nginx (443) → koha:80 (X-Forwarded-Proto: https)
# https://koha-intra.local → nginx (443) → koha:80 (X-Forwarded-Proto: https)
# -------------------------------------------------------
proxy:
profiles: ["local"]
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./docker/nginx-local.conf:/etc/nginx/nginx.conf:ro
- ./docker/certs:/etc/nginx/certs:ro
depends_on:
- koha
volumes:
db_data:
koha_data:
......
......@@ -8,6 +8,28 @@ INIT_FLAG="/var/lib/koha/${INSTANCE}/.docker_initialized"
echo "ServerName localhost" >> /etc/apache2/apache2.conf
# -------------------------------------------------------
# Health check temporal — responde HTTP 200 en puerto 80
# desde el primer segundo del arranque, incluso durante el
# FIRST_RUN (DB restore puede tardar 10-20 min en ECS).
# ECS ve siempre 200 → no reinicia el task por timeout.
# Se mata justo antes de que Apache tome el puerto.
# -------------------------------------------------------
python3 -c "
import http.server, socketserver
class H(http.server.SimpleHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.end_headers()
self.wfile.write(b'starting')
def log_message(self, *a): pass
socketserver.TCPServer.allow_reuse_address = True
with socketserver.TCPServer(('', 8099), H) as s:
s.serve_forever()
" &
HEALTHCHECK_PID=$!
echo "[koha] Health check temporal iniciado (pid ${HEALTHCHECK_PID})"
# -------------------------------------------------------
# Aplica variables de entorno sobre la plantilla koha-conf.xml.
# Se llama tanto en el primer arranque como en reinicios.
# -------------------------------------------------------
......@@ -15,16 +37,38 @@ apply_koha_config() {
local tpl="/etc/koha/koha-conf.xml.tpl"
local dst="/etc/koha/sites/${INSTANCE}/koha-conf.xml"
sed \
-e "s|<hostname>localhost</hostname>|<hostname>${DB_HOST}</hostname>|g" \
-e "s|<port>3306</port>|<port>${DB_PORT:-3306}</port>|g" \
-e "s|<database>koha_prod_humboldt</database>|<database>${DB_NAME:-koha_prod_humboldt}</database>|g" \
-e "s|<user>koha_prod_humboldt</user>|<user>${DB_USER:-koha_prod_humboldt}</user>|g" \
-e "s|<pass>KohaProd2026!Secure</pass>|<pass>${DB_PASSWORD}</pass>|g" \
-e "s|<memcached_servers>127\.0\.0\.1:11211</memcached_servers>|<memcached_servers>${MEMCACHED_SERVERS:-127.0.0.1:11211}</memcached_servers>|g" \
"${tpl}" > "${dst}"
echo "[koha] koha-conf.xml aplicado (DB_HOST=${DB_HOST})"
# Perl en vez de sed: el password puede tener caracteres que rompen sed
# (& en el reemplazo = "todo lo matcheado") y bash parameter expansion
# también trata & especial. Perl hace XML-encoding y string replace limpio.
DB_HOST="$DB_HOST" \
DB_PORT="${DB_PORT:-3306}" \
DB_NAME="${DB_NAME:-koha_prod_humboldt}" \
DB_USER="${DB_USER:-koha_prod_humboldt}" \
DB_PASSWORD="$DB_PASSWORD" \
MEMCACHED_SERVERS="${MEMCACHED_SERVERS:-127.0.0.1:11211}" \
KOHA_TIMEZONE="${KOHA_TIMEZONE:--05:00}" \
perl -p -e '
BEGIN {
for my $k (qw(DB_HOST DB_PORT DB_NAME DB_USER DB_PASSWORD MEMCACHED_SERVERS KOHA_TIMEZONE)) {
my $v = $ENV{$k};
$v =~ s/&/&amp;/g;
$v =~ s/</&lt;/g;
$v =~ s/>/&gt;/g;
$v =~ s/"/&quot;/g;
$v =~ s/\x27/&apos;/g;
$enc{$k} = $v;
}
}
s|<hostname>localhost</hostname>|<hostname>$enc{DB_HOST}</hostname>|g;
s|<port>3306</port>|<port>$enc{DB_PORT}</port>|g;
s|<database>koha_prod_humboldt</database>|<database>$enc{DB_NAME}</database>|g;
s|<user>koha_prod_humboldt</user>|<user>$enc{DB_USER}</user>|g;
s|<pass>KohaProd2026!Secure</pass>|<pass>$enc{DB_PASSWORD}</pass>|g;
s|<memcached_servers>127\.0\.0\.1:11211</memcached_servers>|<memcached_servers>$enc{MEMCACHED_SERVERS}</memcached_servers>|g;
s|<timezone>[^<]*</timezone>|<timezone>$enc{KOHA_TIMEZONE}</timezone>|g;
' "$tpl" > "$dst"
echo "[koha] koha-conf.xml aplicado (DB_HOST=$DB_HOST)"
}
# -------------------------------------------------------
......@@ -123,7 +167,8 @@ if [ ! -f "${INIT_FLAG}" ]; then
echo "SET sql_mode='';"
zcat /migration/prod_humboldt-2026-04-30.sql.gz \
| awk 'BEGIN{prev=""} /^\s*CONSTRAINT .* FOREIGN KEY/{sub(/,\s*$/,"",prev); if(prev!="") print prev; prev=""; next} {if(prev!="") print prev; prev=$0} END{if(prev!="") print prev}' \
| grep -v "ADD CONSTRAINT.*FOREIGN KEY"
| grep -v "ADD CONSTRAINT.*FOREIGN KEY" \
| grep -Eiv "^\s*(GRANT|REVOKE|CREATE USER|DROP USER|ALTER USER|SET PASSWORD)\b"
echo "SET FOREIGN_KEY_CHECKS=1;"
echo "SET UNIQUE_CHECKS=1;"
} | koha-mysql "${INSTANCE}"
......@@ -190,16 +235,36 @@ if [ ! -f "${INIT_FLAG}" ]; then
fi
# -------------------------------------------------------
# Sincronizar OPACBaseURL / staffClientBaseURL con las variables de entorno.
# Se ejecuta en cada arranque para que un cambio de dominio (ej. local → prod)
# se refleje sin entrar al panel de administración.
# Las URLs usan https:// porque el ALB siempre termina TLS; en local el nginx
# proxy también usa HTTPS, por lo que el esquema es siempre correcto.
# -------------------------------------------------------
koha-mysql "${INSTANCE}" -e "
UPDATE systempreferences
SET value='https://${KOHA_OPAC_SERVER_NAME}'
WHERE variable='OPACBaseURL';
UPDATE systempreferences
SET value='https://${KOHA_INTRA_SERVER_NAME}'
WHERE variable='staffClientBaseURL';" 2>/dev/null || true
# -------------------------------------------------------
# Limpiar PIDs stale de ejecuciones anteriores
# -------------------------------------------------------
rm -f /var/run/koha/"${INSTANCE}"/*.pid 2>/dev/null || true
# Garantizar permisos en logs para el usuario de la instancia (Plack escribe aquí)
chown -R "${INSTANCE}-koha:${INSTANCE}-koha" "/var/log/koha/${INSTANCE}/" 2>/dev/null || true
# -------------------------------------------------------
# Fase 5 — Arranque de servicios
# -------------------------------------------------------
# Pre-crear los archivos de log con ownership correcto ANTES de cualquier
# servicio. koha-plack --start dispara "apache2 restart" que crea opac-error.log
# e intranet-error.log como root; si ya existen con ownership prod_humboldt-koha
# Apache los abre en modo append sin cambiar el owner, y Plack puede escribir.
mkdir -p "/var/log/koha/${INSTANCE}"
touch "/var/log/koha/${INSTANCE}/opac-error.log" \
"/var/log/koha/${INSTANCE}/intranet-error.log"
chown -R "${INSTANCE}-koha:${INSTANCE}-koha" "/var/log/koha/${INSTANCE}/"
echo "[koha] Iniciando Memcached..."
service memcached start
......@@ -226,8 +291,10 @@ echo "[koha] OPAC: http://${KOHA_OPAC_SERVER_NAME:-koha.local}"
echo "[koha] Intranet: http://${KOHA_INTRA_SERVER_NAME:-koha-intra.local}"
echo ""
# koha-plack --start hace service apache2 restart en Koha 25.11.
# Detener y limpiar antes de tomar el control en foreground.
# Liberar puerto 80 antes de que Apache arranque en foreground
kill "${HEALTHCHECK_PID}" 2>/dev/null || true
wait "${HEALTHCHECK_PID}" 2>/dev/null || true
service apache2 stop 2>/dev/null || true
rm -f /var/run/apache2/apache2.pid 2>/dev/null || true
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment