PortLAB
PortLAB es la plataforma con la que, en el CNR-IFAC (Instituto de Física Aplicada «Nello Carrara»), se desarrollan y se gestionan portales científicos: un único motor, escrito en PHP con base de datos PostgreSQL, del que nacen sitios distintos — multilingües, adaptativos y ricos en contenidos interactivos — compartiendo estructura, estilo y herramientas.
Qué es
PortLAB es un framework ligero para la construcción de portales. Cada página se define mediante una pequeña configuración declarativa (en formato JSON) que establece su estructura, sus idiomas, sus bibliotecas y sus niveles de acceso, mientras que los contenidos viven en ficheros separados por idioma. Esto permite añadir páginas y sitios enteros con rapidez, manteniendo la coherencia gráfica y funcional y sin necesidad de tocar el motor.
Proyectos realizados con PortLAB
- WebNIR (webnir.eu) — portal del CNR-IFAC sobre las radiaciones no ionizantes: campos electromagnéticos (CEM), radiaciones ópticas artificiales (ROA) y dispositivos médicos implantables activos (DMIA), con herramientas para la evaluación de la exposición y del apantallamiento.
- Purple (purplecodex.eu) — portal sobre los métodos analíticos no invasivos aplicados a los bienes culturales, para el estudio y la catalogación de los códices purpúreos, manuscritos en pergamino teñido de púrpura.
- UISH — Urban Intelligence Science Hub (uish.ifac.cnr.it) — proyecto del CNR para el gemelo digital de la ciudad: la actividad del CNR-IFAC sobre la calidad del aire en Catania y la validación del modelo AirPortal.
- Eclipsis (eclipsis.ifac.cnr.it) — portal dedicado al eclipse de Sol del 12 de agosto de 2026 observado desde La Coruña: la cadena de reducción de las fotografías RAW, de los metadatos de las tomas a las efemérides, ejecutada en el servidor.
- Satellite (satellite.ifac.cnr.it) — el espacio de trabajo reservado de una propuesta de proyecto con varias entidades: entidades participantes, paquetes de trabajo, personas y horas-persona, costes, con el diagrama de Gantt construido a partir de los datos.
- Consola de administración — la interfaz de gestión de la propia plataforma, que alberga también una sección didáctica con ejemplos interactivos.
Arquitectura por proyectos y personalización
PortLAB se organiza en torno a un sitio principal que hace de base compartida para todos los proyectos secundarios alojados en la plataforma. Cada proyecto dispone de sus propios contenidos y de sus propias personalizaciones locales, pero hereda automáticamente del sitio principal tanto el código común como las bibliotecas de terceros, compartidas una sola vez y mantenidas de forma centralizada: sin duplicación y con actualizaciones inmediatas para todos. Al mismo tiempo, cada proyecto puede personalizar cualquier elemento en cualquier punto, sustituyendo de forma selectiva e independiente la versión compartida por una versión local propia. El resultado es una arquitectura por capas — elementos locales, comunes y bibliotecas compartidas, libremente combinables — que permite poner en marcha nuevos portales con rapidez manteniendo para cada uno una identidad y un aspecto a medida.
Además del código común y de las bibliotecas, también los servicios y las herramientas que ofrece PortLAB (gestión y visualización de la base de datos, diagrama del esquema, gestión de usuarios, consulta de los registros y demás) pueden heredarse en los proyectos secundarios mediante enlaces simbólicos al proyecto principal: cada nuevo portal dispone así de todo el instrumental sin duplicación, pudiendo en cualquier caso sustituir o ampliar localmente cualquiera de sus partes.
Interfaz gráfica (GUI)
La interfaz se compone de elementos estándar basados en Bootstrap — cabeceras, barras de navegación, menús, migas de pan, pies de página y ventanas modales: piezas independientes y reutilizables que se combinan hasta obtener la estructura de página deseada, con coherencia visual y plena adaptabilidad.
Plantillas
La plantilla es el modelo de página: establece qué elementos de interfaz utilizar y cómo maquetarlos, incluido el tipo de menú (horizontal arriba o lateral) y el estilo general. Cada plantilla es una combinación predefinida y reutilizable de componentes, invocada por una página con una simple referencia; al cambiar de plantilla cambian el aspecto y la disposición de toda la página sin tocar sus contenidos.
Descriptor de página
Cada página se declara mediante un pequeño fichero JSON, y la declaración no es un trámite: sin ese fichero la página no existe para la plataforma y su dirección responde «no encontrada». Es el primer eslabón del control de acceso. El descriptor indica qué plantilla usar, quién puede abrir la página, qué cargar y en qué orden, y cómo se presenta la página en el menú.
{
"template": 18,
"profiles": [1],
"flags": { "menu_order": 3 },
"libs": {
"css": ["/css/common/editor_svg.css", "/css/common/diagram.css"],
"js": ["/js/libs/ace-builds-master/src-min-noconflict/ace.js"],
"js_end": ["/js/common/editor_svg.js", "/js/common/diagramma.js", "/js/common/diagram_ui.js"]
}
}
- template — el modelo de página descrito más arriba.
- profiles — los perfiles autorizados. La lista vacía significa página pública, no página inaccesible: es la única apertura, y hay que escribirla a propósito.
- libs — las hojas de estilo y los scripts propios de la página, en el orden exacto en que deben cargarse, con js antes del cuerpo de la página y js_end después. La base común a todas las páginas (jQuery, Bootstrap, select2, el menú, la búsqueda, el acceso, el idioma, las ventanas) no se enumera: la escribe la plataforma antes de estas, y así se actualiza en un solo lugar.
- flags.hide_in_menu — la página sigue siendo accesible por dirección pero no aparece en el menú.
- flags.menu_order — el lugar de la página entre los ficheros de su carpeta: número creciente y, a igualdad o en ausencia de número, orden por nombre. El nombre deshace siempre el empate, de modo que un número equivocado desplaza una entrada y no reordena la sección. Sirve para mantener el orden fuera de los nombres de los ficheros, que son también direcciones. Las carpetas, que no tienen json, toman el número del mapa menu_order de protect/struct.json del proyecto; con un número se mezclan con los ficheros, y así una página puede ir también después de una carpeta.
Todo lo demás no es configuración sino ficheros reconocidos por el nombre: el cuerpo de la página, uno por idioma; un diccionario para las cadenas de la interfaz Javascript; y, si hace falta, un fichero de lógica en el servidor. Añadir una página significa por tanto crear ficheros, no modificar un registro central — y es la razón por la que el menú, las migas de pan y el motor de búsqueda la encuentran solos.
Usuarios, accesos y navegación
Gestión de usuarios multiperfil
A cada usuario se le pueden asociar varios perfiles de autorización. Un recurso es accesible cuando al menos uno de los perfiles del usuario coincide con uno de los perfiles exigidos por el recurso; las páginas sin perfiles siguen siendo públicas. El mismo criterio gobierna de manera uniforme el acceso a páginas y secciones, las entradas de menú, los ficheros descargables e incluso la visibilidad de porciones concretas de una página: así, una misma página puede mostrar contenidos distintos a usuarios con perfiles distintos.
Autenticación y contraseñas
Cada sitio pertenece a un clúster de autenticación, que indica dónde se guardan las credenciales de los usuarios, con qué proveedores de identidad externos se puede entrar y qué reglas valen para las contraseñas. Los sitios de un mismo clúster comparten las cuentas; un clúster libre no tiene autenticación, y sus sitios no tienen ni acceso ni registro.
Las reglas de las contraseñas se eligen por clúster: la longitud mínima, las clases de caracteres exigidas (contadas en Unicode, así que valen también las letras con acento y otros alfabetos), la prohibición de los datos personales y de las contraseñas más comunes, la caducidad, el historial de las últimas contraseñas y cuánto debe diferir la nueva de la anterior. El hash se calcula con Argon2id donde PHP lo ofrece, y una contraseña no se trunca nunca. Quien entra con una contraseña caducada, o que ya no cumple las reglas, debe cambiarla antes de recibir la sesión.
Acceso único y proveedores externos
Un sitio puede hacer de centro del acceso único: los sitios de su clúster que se adhieren reconocen a quien ha entrado allí y reciben la sesión mediante un billete de un solo uso válido durante sesenta segundos. Cada sitio mantiene su propia cookie de sesión, y la salida vale para todos. En el centro se puede entrar también con un proveedor de identidad externo (OpenID Connect; están previstos ORCID y Google). El proveedor no crea cuentas: una identidad externa abre la sesión solo si el usuario, tras entrar con la contraseña, la ha vinculado a su propia cuenta.
Área reservada
Las páginas públicas están netamente separadas del código de la aplicación y de los contenidos reservados, custodiados en un área situada fuera de la raíz web y, por tanto, inalcanzable directamente desde el navegador. Los documentos reservados nunca se exponen como URL estáticas: se entregan exclusivamente mediante procedimientos del servidor que, antes del envío, comprueban la identidad y los perfiles del usuario.
Seguridad y cifrado
Las defensas son transversales: sesiones protegidas con testigo anti-CSRF, regeneración del identificador de sesión y cookies con atributos de seguridad (HttpOnly, Secure, SameSite); contraseñas custodiadas solo como hash; parámetros de las consultas validados y acotados. Los contenidos reservados y los enlaces «de capacidad» están protegidos con cifrado estándar (AES-256-GCM: cada testigo lleva su finalidad y su caducidad, y no vale para otro uso) y claves mantenidas fuera del código fuente. El control de acceso es fail-closed: una página es accesible solo si está configurada explícitamente, y también los puntos de cálculo en el servidor están sujetos a los perfiles del usuario. Los errores tienen páginas propias (400, 403, 404, 413, 500 y 503), que no dependen ni de la sesión ni de la base de datos; la de página no encontrada devuelve al inicio.
Contra quien prueba contraseñas está el bloqueo de los intentos: cinco errores en quince minutos bloquean la cuenta durante quince minutos, pero solo desde los navegadores que el sitio no conoce. El navegador desde el que el usuario ya ha entrado se reconoce por una cookie cifrada y tiene su propio contador, de modo que quien prueba contraseñas en una cuenta no bloquea a su titular, que entra desde su navegador habitual. Ninguna respuesta dice si una dirección está registrada.
Cada respuesta lleva las cabeceras de seguridad: HSTS y nosniff desde Apache, X-Frame-Options, Referrer-Policy, Permissions-Policy y la Content Security Policy desde PHP. La política admite scripts, estilos, imágenes, fuentes y conexiones solo del propio sitio y de los orígenes que declara; empieza en modo solo informe, con las violaciones recogidas en un registro que el Diagnóstico resume, y se aplica cuando el registro queda limpio. Junto a ella se prueba otra más severa, que exige en cada script de la página un nonce nuevo en cada petición.
Generación dinámica de los menús de navegación
El menú no se escribe a mano: se construye automáticamente explorando el árbol de carpetas y páginas. Cada página nueva aparece sola, organizada en varios niveles según el anidamiento; las entradas se filtran según el perfil del usuario y una marca de exclusión específica, y los títulos se toman de la página en el idioma elegido. Sin números van primero los ficheros y después las subcarpetas, cada grupo por orden de nombre. Una página toma un número de su descriptor, una carpeta del mapa del proyecto: las entradas numeradas, ficheros o carpetas, van primero, por número y, a igualdad, por nombre. Una sección se reordena así cambiando un número, sin renombrar ficheros ni mover direcciones.
Contenidos, búsqueda y privacidad
Soporte multilingüe
Los idiomas disponibles se declaran página por página. El cuerpo de cada página reside en un fichero separado para cada idioma, mientras que las cadenas de la interfaz se gestionan mediante un sistema de traducción por diccionarios que funciona de manera idéntica en PHP y en Javascript: la misma función y los mismos diccionarios están disponibles tanto en el servidor como en el navegador. Así es posible traducir al vuelo cualquier cadena, procedente de cualquier sección de la página — incluidas las producidas por programas en Python o C++ ejecutados a través de la pasarela (gw2py) y devueltas a la página.
Se dispone además de una extensión para las cadenas paramétricas:
el texto traducido contiene marcadores numerados (p. ej. {0}, {1}) sustituidos en tiempo de ejecución por los valores reales.
Es un detalle esencial, porque en idiomas distintos el parámetro puede ocupar una posición distinta dentro de la frase:
el marcador se coloca donde hace falta en cada traducción, de modo que el texto sigue siendo natural
(por ejemplo «{0} elementi trovati» en italiano pasa a ser «found {0} items» en inglés, con el valor en lugares distintos).
Toda la cadena de tratamiento está en UTF-8, por lo que admite cualquier alfabeto;
los idiomas en uso hoy son el italiano, el inglés y el español, y cada portal tiene uno, dos o tres,
con un selector de idioma con bandera y sigla.
Búsqueda textual interna
Al teclear al menos tres caracteres, el sistema examina las páginas del sitio en el idioma seleccionado (o en todos, con la opción correspondiente) y muestra los resultados en una ventana dedicada. De cada página se presenta un extracto con título, palabras clave, descripción y texto en el que aparece el término buscado, resaltado en su contexto, con enlace directo a la página. Además del texto, la búsqueda mira los datos que la página incorpora y, con la opción «Incluir SVG», los textos de los dibujos SVG que la página muestra. La búsqueda trata correctamente los alfabetos y los caracteres internacionales (UTF-8) y puede dirigirse, página por página, solo a los párrafos relevantes.
Gestión del RGPD
PortLAB incluye una gestión del RGPD lista para usar: una política de privacidad y cookies específica y un aviso de consentimiento reutilizable por todos los sitios. La plataforma está preparada para el uso de solo cookies técnicas, sin perfilado ni terceros; el aviso es puramente informativo, no bloquea la navegación y recuerda que se ha leído durante un periodo preestablecido.
Herramientas para la base de datos (PostgreSQL)
Gestión de las tablas
Una única interfaz reutilizable permite crear, modificar, eliminar y consultar las filas de las tablas de la base de datos, con herramientas de filtrado multicriterio, ordenación personalizable, paginación e importación/exportación de los criterios en formato JSON. Las consolas que lo prevén exportan a CSV las filas filtradas, para los perfiles que el sitio autoriza.
Visualización y exploración de los datos
Un visualizador completo de la base de datos reúne cuatro herramientas: un navegador que explora bases de datos, esquemas, tablas, vistas, vistas materializadas y funciones mediante un árbol interactivo y filtros en cascada (mostrando estructura, tipos, claves, tamaños y definiciones); un ejecutor de consultas SQL con editor avanzado, ejecución directa y exportación de los resultados en CSV; una biblioteca de consultas guardadas invocables con un clic; y una búsqueda por palabras clave en todos los campos, con filtros por esquema, tabla, campo y tipo de dato.
Diagrama gráfico del esquema
Un visualizador gráfico genera un diagrama interactivo del esquema: cada tabla o vista es un nodo con la lista de los campos y la clave primaria resaltada. Los nodos se arrastran libremente por el plano de trabajo y los enlaces entre los campos relacionados se trazan como líneas; disposición, colores y enlaces se guardan y se restauran en cada acceso.
Administración y diagnóstico
Consulta de los registros del servidor
Una consola de administración permite consultar los registros de Apache — accesos y errores — filtrando las líneas por inclusión o exclusión de una cadena, con la posibilidad de ocultar los mensajes informativos o de vaciar el registro; una sección específica muestra la configuración PHP del servidor. El acceso está reservado a los administradores.
Mensajes de error normalizados
Cada punto crítico del código — consultas SQL, operaciones PHP, llamadas a la pasarela Python, scripts en el cliente — está marcado con un código/índice: cuando algo va mal aparece un mensaje uniforme que indica ese código, lo que permite remontarse al punto exacto en que se ha producido el problema. Los mensajes están diferenciados por perfil: el usuario ve un aviso esencial con el código, mientras que al administrador se le muestran además los detalles técnicos (tipo, fichero, línea, texto de la consulta) útiles para la depuración. El mecanismo es uniforme para las páginas y para las llamadas AJAX.
Tecnologías
El núcleo del funcionamiento en el servidor es una pasarela que permite a las páginas invocar, de manera uniforme, programas y procesos escritos en lenguajes distintos — hasta ahora Python y C++ — devolviendo sus resultados (por lo general en formato JSON) directamente en la página. La pasarela se usa de forma generalizada junto con la interfaz AJAX de jQuery: esto ha normalizado de hecho las llamadas en todo el portal, con un único esquema petición/respuesta para cualquier operación dinámica.
- Motor en PHP sobre Apache/Linux, base de datos PostgreSQL; front-end sobre Bootstrap 5 y jQuery.
- Páginas configuradas de forma declarativa (JSON): plantilla, metadatos, bibliotecas y perfiles de acceso, sin modificar el motor.
- Componentes reutilizables y cálculo en el servidor mediante una pasarela Python: las páginas pueden lanzar simulaciones y procesos y mostrar sus resultados.
- Contenidos científicos interactivos: gráficos (ECharts), fórmulas en LaTeX (KaTeX), visualizaciones 3D (three.js), código con resaltado de sintaxis, tablas de resultados que se descargan en PNG o XLS, compuestas en el navegador sin pasar por el servidor.
- Asistente para poner en marcha nuevos portales; diseño adaptativo para escritorio y móvil.
PortLAB se instala en un servidor propio con un script. Aquí preparas el zip: la copia de PortLAB y los parámetros de tu instalación, en un solo archivo.
En tres pasos
- Rellena los parámetros aquí abajo y pulsa «Preparar el zip».
- Copia el zip al servidor y descomprímelo.
- Lanza el script como root. Instala los paquetes, crea la base de datos, configura Apache y el certificado, y al final prueba la página de inicio.
Instrucciones
Qué hace falta
- Un servidor con Ubuntu 26.04 LTS y acceso como root. Ubuntu 26.04 tiene en su archivo PHP 8.5 y PostgreSQL 18, así que no hacen falta repositorios externos.
- Un nombre de dominio que apunte al servidor solo hace falta para el certificado de Let's Encrypt, que comprueba el dominio en el puerto 80: ese puerto tiene que ser accesible desde Internet. Sin él, el script pone un certificado autofirmado. El sitio responde en el 443, que tiene que estar abierto para quien lo usa.
- Salida a Internet durante la instalación: el script descarga los paquetes de Ubuntu (apt), las bibliotecas PHP (Composer) y las de Python (pip).
- Un servidor de correo para los correos del sitio, con host, puerto y cifrado y, si los pide, usuario y contraseña. Sin él, el sitio funciona, pero el registro y la recuperación de la contraseña no envían correos.
Los comandos
Desde tu ordenador, copia el zip al servidor y entra:
scp portlab-<nombre>.zip usuario@servidor:
ssh usuario@servidor
En el servidor, descomprime y lanza el script como root:
python3 -m zipfile -e portlab-<nombre>.zip .
sudo bash portlab/webadmin/install/install.sh
rm portlab-<nombre>.zip
Para descomprimir basta Python, que Ubuntu ya tiene. La última línea borra el zip, que contiene las contraseñas; el archivo de los parámetros lo borra el script cuando termina.
Qué hace el script
- Comprueba los parámetros. Si alguno no vale, se detiene y los enumera, antes de tocar nada. La contraseña del administrador sigue las reglas iniciales del sitio: al menos 8 caracteres, con una minúscula, una mayúscula, una cifra y un carácter que no sea letra ni cifra, y como máximo 72 bytes si el PHP del servidor no tiene Argon2id.
- Instala los paquetes: Apache con PHP y la pasarela Python, PostgreSQL, Composer, WeasyPrint, las fuentes para los PDF, pdftotext y certbot.
- Crea el usuario y copia los archivos en la carpeta elegida, que pasa a ser la carpeta personal del usuario, con las bibliotecas PHP y Python.
- Crea la base de datos: los cuatro roles (el propietario sin login, el usuario personal, los roles con los que las páginas escriben y leen), el esquema, los datos de referencia (idiomas, convenciones locales, expresiones regulares, perfiles), el clúster de autenticación con las reglas iniciales de las contraseñas, el sitio con sus idiomas y sus perfiles, la conexión a la base de datos y el administrador.
- Escribe la configuración: la clave de los tokens, las credenciales de la base de datos para el servidor web, el nombre del sitio, el correo, robots.txt y security.txt.
- Configura Apache con el vhost que espera el Diagnóstico, le abre la carpeta de
PortLAB para escribir con una regla de systemd (hace falta si la carpeta está bajo
/home, que Ubuntu 26.04 deja de solo lectura para Apache; en otro sitio no cambia nada) y pide el certificado. - Ajusta los permisos de las carpetas en las que escribe el servidor web y programa los trabajos nocturnos: las limpiezas y el índice de la búsqueda.
- Prueba la página de inicio e imprime la dirección del sitio. Si la contraseña del administrador la generó el script, la imprime aquí, una sola vez.
Después de la instalación
Abre el sitio, entra con la dirección y la contraseña del administrador y abre Gestión > Proyectos > Diagnóstico. Dice lo que queda por arreglar, y para muchas comprobaciones da el comando que lo arregla.
Si algo no va
- Se puede volver a lanzar. El script repite los pasos sin borrar nada: la clave de los tokens sigue siendo la misma, y el esquema de la base de datos no se vuelve a cargar si ya está. Los parámetros, eso sí, hay que volver a ponerlos, porque el archivo se ha borrado.
- El certificado no llega cuando el dominio todavía no apunta al servidor o el puerto 80 está cerrado. Se queda el autofirmado; arreglado el DNS, basta con volver a lanzar el script.
- La página de inicio no responde: el motivo está en
/var/log/apache2/error.log. - El registro dice «Read-only file system»: la carpeta de PortLAB está bajo
/home,/usro/etcy a Apache le falta la regla de systemd que se la abre (véase Configurar a mano).
Los paquetes
El script los instala por sí solo. Quien prepare el servidor a mano, o quiera saber qué se instala, encuentra aquí la lista completa.
Paquetes del sistema (Ubuntu 26.04)
| Paquete | Para qué sirve |
|---|---|
apache2 | el servidor web |
libapache2-mod-php8.5 | PHP dentro de Apache, para las páginas |
libapache2-mod-wsgi-py3 | la pasarela Python gw2py dentro de Apache |
php8.5-cli | PHP en línea de comandos: el script, el índice de la búsqueda, los trabajos nocturnos |
php8.5-pgsql | PHP con PostgreSQL |
php8.5-gd | las imágenes: miniaturas y gráficos |
php8.5-mbstring | los textos en UTF-8 |
php8.5-curl | las peticiones del servidor a otros sitios: Diagnóstico, acceso con proveedores externos |
php8.5-xml | XML, para las hojas de cálculo y las importaciones |
php8.5-zip | los archivos zip de las hojas de cálculo |
php8.5-bcmath | la aritmética de precisión arbitraria |
php8.5-intl | fechas y números escritos según el idioma |
postgresql | la base de datos, con las extensiones pg_trgm, dblink y postgres_fdw |
python3-venv, python3-pip | el entorno Python de la pasarela y sus paquetes |
composer, unzip | las bibliotecas PHP: PHPMailer para el correo, PhpSpreadsheet para las hojas de cálculo |
rsync | la copia de los archivos en la carpeta elegida |
ssl-cert | el certificado autofirmado, si Let's Encrypt no responde |
certbot | el certificado de Let's Encrypt y su renovación |
librsvg2-bin | rsvg-convert, que hace los PDF de los editores de diagramas y de Gantt |
fonts-ibm-plex, fonts-roboto, fonts-jetbrains-mono, fonts-nunito | las fuentes de esos PDF |
weasyprint | los PDF hechos a partir de páginas HTML, para las exportaciones de los proyectos |
poppler-utils | pdftotext, que lee el texto de los PDF del sitio para la búsqueda |
g++, make | los programas C++ que la pasarela puede ejecutar |
curl | la prueba de la página de inicio al final de la instalación |
ca-certificates | las autoridades que firman los certificados: correo, Let's Encrypt, CDN |
A mano, todos juntos:
sudo apt install apache2 libapache2-mod-php8.5 libapache2-mod-wsgi-py3 \
php8.5-cli php8.5-pgsql php8.5-gd php8.5-mbstring php8.5-curl php8.5-xml php8.5-zip \
php8.5-bcmath php8.5-intl postgresql python3-venv python3-pip composer unzip rsync ssl-cert \
certbot librsvg2-bin fonts-ibm-plex fonts-roboto fonts-jetbrains-mono fonts-nunito poppler-utils \
weasyprint g++ make curl ca-certificates
Paquetes Python de la pasarela gw2py
La pasarela funciona en un entorno Python propio, <carpeta>/venvs/webadmin, con
el mismo Python que mod_wsgi. Los paquetes están en wsgi-scripts/requirements.txt:
| Paquete | Versión | Para qué sirve |
|---|---|---|
multipart | 2.0.0 | lee los formularios y archivos que las páginas envían a la pasarela: es el único paquete que necesita gw2py |
psycopg2-binary | 2.9.12 | PostgreSQL desde Python, para el generador de las consolas (gen_table_page.py) |
waitress | 3.0.2 | solo para el desarrollo: la pasarela sin Apache (pyapp_server.py) |
El resto de la pasarela (la autorización, los trabajos largos, el conversor de PDF, el control de las imágenes) usa solo la biblioteca estándar de Python. A mano:
python3 -m venv /srv/portlab/venvs/webadmin
/srv/portlab/venvs/webadmin/bin/pip install -r /srv/portlab/wsgi-scripts/requirements.txt
Configurar a mano Apache, PHP y PostgreSQL
El script lo hace todo por sí solo. Para quien prefiera hacerlo a mano, o quiera ver qué escribe,
el zip tiene en la carpeta portlab/conf los archivos ya rellenados con los
parámetros del formulario. Los comandos de abajo usan los valores predeterminados (la carpeta
/srv/portlab, la base de datos boot): con otros valores hay que
cambiarlos.
Apache
conf/apache/<nombre>.conf: el vhost http, que manda todo a https salvo las pruebas con las que Let's Encrypt comprueba el dominio;conf/apache/<nombre>-ssl.conf: el vhost https, con la pasarela Python, las cabeceras de seguridad y las carpetas protegidas. Es el que espera el Diagnóstico.
Primero el vhost http y el certificado, luego el https, que ya nombra los archivos del certificado:
sudo a2dismod mpm_event
sudo a2enmod mpm_prefork ssl headers wsgi php8.5
sudo cp portlab/conf/apache/*.conf /etc/apache2/sites-available/
sudo a2dissite 000-default
sudo a2ensite <nombre> && sudo systemctl reload apache2
sudo certbot certonly --webroot -w /srv/portlab/website -d <dominio> -m <correo> --agree-tos
sudo a2ensite <nombre>-ssl && sudo apachectl configtest && sudo systemctl reload apache2
Con el certificado autofirmado el paso de certbot no hace falta: el vhost https usa el del
paquete ssl-cert.
En Ubuntu 26.04 systemd encierra a Apache en una jaula: entre otras cosas /home,
/usr y /etc son de solo lectura, y el sitio no puede escribir en sus
carpetas aunque los permisos sean los correctos (en el registro: «Read-only file system»). La
regla conf/systemd/portlab-<nombre>.conf le vuelve a abrir la carpeta de
PortLAB; dónde se escribe lo siguen decidiendo los permisos de los archivos. Con la carpeta por
defecto, /srv/portlab, la regla no hace falta y no cambia nada: el script la
escribe de todos modos.
sudo mkdir -p /etc/systemd/system/apache2.service.d
sudo cp portlab/conf/systemd/portlab-<nombre>.conf /etc/systemd/system/apache2.service.d/
sudo systemctl daemon-reload && sudo systemctl restart apache2
PHP
La configuración está en dos archivos, uno para el sitio y uno para la línea de comandos:
sudo cp portlab/conf/php/apache2/99-portlab.ini /etc/php/8.5/apache2/conf.d/
sudo cp portlab/conf/php/cli/99-portlab.ini /etc/php/8.5/cli/conf.d/
sudo systemctl reload apache2
El archivo del sitio tiene también session.upload_progress.enabled = Off: el avance de
las subidas escrito en la sesión es la manera clásica de hacer que PHP escriba bytes elegidos por
el atacante en un archivo de sesión (lo señaló también el CSIRT italiano en octubre de 2026); no es
un defecto que una actualización corrija, es el funcionamiento previsto, y PortLAB no lo usa: el
porcentaje lo calcula el navegador. Hay que apagarlo donde quiera que corra PHP. Con PHP-FPM basta
un archivo cualquiera en /etc/php/<versión>/fpm/conf.d/ con esa línea, y luego
systemctl reload php<versión>-fpm; para cerrarlo también ante un
.user.ini se escribe en el pool php_admin_flag[session.upload_progress.enabled]
= off. Con mod_php y AllowOverride None un .htaccess no puede volver
a encenderlo. Se comprueba con php-fpm<versión> -i | grep upload_progress, o en
la página phpinfo del sitio.
| Ajuste | Valor | Por qué |
|---|---|---|
post_max_size, upload_max_filesize | 200M | los archivos de medida y las imágenes que se suben |
max_file_uploads | 200 | muchos archivos en una sola subida |
memory_limit | 512M | el límite de una petición; las páginas que leen archivos grandes lo suben por sí solas |
session.gc_maxlifetime | 43200 | la sesión dura 12 horas sin actividad, como quiere PortLAB |
expose_php, display_errors | Off | ni versión en las respuestas ni errores en las páginas: van al registro de Apache |
date.timezone | la zona horaria del formulario | las fechas de los correos y de los registros |
pcre.jit | 0 | solo para el sitio: la jaula de systemd de Apache no da memoria ejecutable, y la compilación de las expresiones regulares llenaría el registro de avisos |
Las extensiones necesarias son pgsql, gd, mbstring, curl, xml, zip, bcmath e intl (los paquetes
php8.5-*), más sodium y openssl, que PHP ya tiene. El Diagnóstico las comprueba una
por una.
PostgreSQL
PortLAB quiere PostgreSQL 18 en la misma máquina, accesible por TCP en localhost con una contraseña. En Ubuntu los valores predeterminados ya sirven:
listen_addresses = 'localhost'enpostgresql.conf: la base de datos no se abre a la red;host all all 127.0.0.1/32 scram-sha-256enpg_hba.conf: las páginas entran con las contraseñas del.pgpass.
La base de datos la crean los archivos de webadmin/install/sql, en este orden. Cada
uno dice al principio qué variables de psql necesita (nombres y contraseñas de los roles, el
sitio, el administrador): install/database.sh las pasa por sí solo.
01_database.sql, como postgres: los cuatro roles, la base de datos (del propietario) y las extensiones pg_trgm, dblink y postgres_fdw. El propietario no tiene login ni contraseña; el usuario personal es miembro suyo sin heredar sus privilegios, y el rol fijado para la base de datos lo hace trabajar como propietario en cuanto entra (pgAdmin, QGIS, psql); los roles de las páginas no pueden convertirse en él;02_schema.sql, con el rol del propietario, que es dueño de todo;03_permessi.sql, como postgres: filas, no tablas, para el rol que escribe (SELECT, INSERT, UPDATE, DELETE, TRUNCATE y MAINTAIN, nada de CREATE, ALTER, DROP), solo lectura para el rol que lee, también para los objetos que el propietario creará;04_dati.sql, como postgres: idiomas, banderas, convenciones locales, zonas horarias, las magnitudes físicas con sus unidades y los prefijos del SI (esquemametrology), expresiones regulares y perfiles;05_sito.sql, con el rol que escribe: el clúster de autenticación 1 «PortLAB», con los usuarios en el esquemausersdeboot, las reglas iniciales de las contraseñas y ningún proveedor externo; el sitio, sus idiomas y sus perfiles; la conexiónboot; el administrador.
El servidor web encuentra las credenciales en /var/www/.pgpass, de www-data con
permisos 0600. Cada línea lleva encima una cabecera: #conn_p para el rol que lee,
#conn_a para el que escribe.
#conn_p
localhost:5432:boot:portlab_ro:<contraseña>
#conn_a
localhost:5432:boot:portlab_rw:<contraseña>
En una máquina dedicada se le puede dar a shared_buffers un cuarto de la memoria;
para lo demás bastan los valores de Ubuntu. Las copias de la base de datos se hacen con pg_dump,
por ejemplo cada noche: sudo -u postgres pg_dump -Fc boot > /var/backups/boot.dump.
Licencias
El código de PortLAB se distribuye con la licencia EUPL-1.2, los contenidos con
CC BY 4.0; las bibliotecas de terceros tienen las suyas, enumeradas en la carpeta
LICENSES de la copia. Las bibliotecas JavaScript llegan de jsDelivr, con la huella
que comprueba el navegador.
Algunos mecanismos de PortLAB se pueden probar aquí, en esta misma página: lo que ocurre al pulsar los botones es el comportamiento real del sitio, no una simulación.
La búsqueda por palabras clave y $id_search
Cada página declara qué parte de sí misma entra en la búsqueda:
el título, las palabras clave, la descripción y solo los trozos del cuerpo enumerados en la variable $id_search.
Así los textos se buscan, mientras los controles de la interfaz, las fórmulas y los bloques de código quedan fuera y no contaminan los resultados.
Vale para la búsqueda en vivo y para el índice que el sitio construye cada noche.
Los dos recuadros de abajo contienen cada uno una palabra inventada, que no aparece en ninguna otra página:
el primero está en un trozo del cuerpo enumerado en $id_search, el segundo no.
Pulsa los dos botones: la búsqueda del sitio, la de la barra superior, encuentra la primera palabra en esta página y de la segunda no sabe nada.
Trozo enumerado en $id_search
Este recuadro está en $body[2], que la página enumera entre los trozos que se buscan.
Su palabra marcador es zampillorio.
Trozo fuera de $id_search
Este recuadro está en $body[3], que la página no enumera.
Su palabra marcador es brugnafolo.
Alfabetos y caracteres multibyte
La búsqueda cuenta caracteres y no bytes, y todo el texto del sitio es UTF-8 en forma NFC: funciona con cualquier alfabeto. Las palabras marcador de este recuadro están en griego, ζαμπιλλόριο, en cirílico, зампиллорио, en árabe, زامبيلوريو, y en japonés, ざんぴろりお.
La gestión de los errores
Un error llega a quien usa el sitio en una ventana, con un código que lo identifica en el código fuente (COD. …) y un texto;
el detalle técnico, es decir la consulta, el mensaje de PHP, el archivo y la línea, o la excepción del gateway, lo ve solo el administrador (perfil 1), los demás solo el código.
Ningún camino devuelve la página HTML de error de Apache dentro de una respuesta: también el gateway Python responde en JSON, con err y txt, y el texto se traduce con el diccionario del sitio.
Los botones provocan un error en cada uno de los caminos que puede tomar una petición.
-
La página pide al servidor una lista de tres valores y recibe una cadena:
gest_resultcomprueba la forma de cada respuesta antes de usarla, y se detiene. -
Una rama del archivo
top-ejecuta una consulta sobre una tabla que no existe:error_alertresponde en JSON con el códigoCOD. HF-01, y el detalle solo al perfil 1. - La misma petición, enviada sin el token de la sesión: el servidor la rechaza con un 403 antes de que llegue a la página, como haría con una petición iniciada desde otro sitio.
-
El módulo Python que calcula la hipotenusa recibe un cateto negativo y responde
{"error": 1, "message": …}: el error previsto es un dato como los demás, y la página lo muestra. -
El gateway responde 403 con
{"err": "FORBIDDEN", "txt": …}, antes de importar nada; la misma forma tiene el 500 de un módulo que lanza una excepción, con el traceback en el registro del servidor. -
Las páginas de error HTTP (400, 403, 404, 413, 500 y 503) son páginas autónomas del sitio, en su idioma:
la 404 vuelve a la portada a los seis segundos, y los descriptores
.jsonde las páginas el servidor se los niega a todos.
Keywords: PortLAB, CMS, framework PHP, portales científicos, multilingüe, PostgreSQL, CNR-IFAC