Arquitectura

Cómo se despliega Meratyn y cómo está organizado el código. Dos vistas, un mismo sistema.

Entrega

Meratyn se distribuye de tres formas desde un único monorepo de workspaces Bun. El visor es una aplicación React servida como archivos estáticos por nginx (Docker) o instalada como app de escritorio Tauri v2 para Windows, macOS y Linux. La consola de gestión y la API de administración se despliegan como contenedores, y un anywidget de Jupyter incrusta el visor en cuadernos. La distribución es closed-source: imágenes Docker e instaladores de escritorio firmados, sin tiendas de aplicaciones ni servidores de actualización.

Topología en ejecución

Meratyn runtime topology Deployment topology of Meratyn: browser, desktop and Jupyter clients reach the nginx-hosted viewer and its sidecar, the admin console reaches a control plane of API, object store and plugin registry, an AI relay fronts model providers, and a GeoNode stack serves hosted layers. CLIENTS VIEWER · NGINX CONTROL PLANE RELAY GEONODE STACK HTTPS:443 LOCAL HTTPS:443 SIGNED URLS HOSTED LAYERS /SIDECAR Browser · PWA any modern engine Desktop Tauri v2 Jupyter anywidget Admin console admin UI x1 Static React app nginx · dist/ x1 FastAPI sidecar /sidecar · Whitebox conversion · raster · SQL Local sidecar 127.0.0.1:8765 x1 Admin API Bun · SQLite share.db Clerk auth RustFS S3 object store Plugin registry CDN · registry.json AI relay edge proxy · rate limits Redis relay queue x1 GeoNode Django bridge API PostGIS layers DB GeoServer WMS · WFS · tiles Celery + Redis async jobs LEGEND LIGHT BLUE · CROSSES BOUNDARY SOLID · SERVICE CALL FOCAL · CONTROL-PLANE HUB

Topología en ejecución. Los enlaces azul claro cruzan un límite de confianza o de red; la API de gestión es el hub del plano de control.

Meratyn container fleet Container-level deployment of the Meratyn self-hosted profile: the mappview container running nginx and the uvicorn sidecar, the control-plane containers for admin console, object store and AI relay, a remote AI worker submitting jobs to the relay, the full GeoNode container stack, and the observability set, all on one Docker host. Browser viewers AI worker remote · MAPPVIEW_EDGE_URL DOCKER HOST · SELF-HOSTED PROFILE VIEWER & CONTENT CONTROL PLANE GEONODE STACK OBSERVABILITY AI JOBS HTTP:80 HTTP:8765 · LOOPBACK /AI S3 API HTTP OWS 5432 CACHE BROKER SCRAPE SCRAPE SCRAPE PROMQL REGISTRY CONTAINER mappview python:3.12-slim-bookworm nginx :80 · PID 1 uvicorn sidecar · 127.0.0.1:8765 static web app · /data docs docs site plugins plugin registry CDN admin admin console · 127.0.0.1:8787 rustfs S3 object store · rustfs-data edge AI relay · 127.0.0.1:8788 geonode-proxy geonode/nginx:1.31.2 geonode-django Django + bridge API geonode-celery async jobs geonode-memcached memcached:1.6-alpine geonode-redis redis:7-alpine geonode-db geonode/postgis:15-3.5 geoserver geonode/geoserver:2.28.4 status gatus healthchecks grafana dashboards prometheus metrics scrapes node-exporter host metrics cadvisor container metrics LEGEND RUNTIME CALL INGRESS / CROSS-STACK OPTIONAL / EXTERNAL FOCAL · VIEWER STACK

Flota de contenedores del perfil autoalojado: el contenedor del visor ejecuta nginx y el sidecar en una sola imagen; GeoNode se despliega como su propia pila de siete contenedores.

Límites de confianza

  • La app de escritorio habla con su sidecar local solo por 127.0.0.1; el procesamiento pesado en Python nunca sale de la máquina salvo que se configure un servicio remoto.
  • El contenedor del visor alcanza el sidecar en el mismo origen en /sidecar, sin superficie CORS. Con MAPPVIEW_DISABLE_SIDECAR=1 se sirve solo estático.
  • La API de gestión antepone el almacenamiento de objetos con URL de capa firmadas y temporales (LAYER_ACCESS_TTL_SECONDS) y cuotas por tenant.
  • El relé de IA autentica visores con token, restringe modelos y limita la tasa por cliente; las claves de proveedores nunca llegan al navegador.

Despliegue

La misma build React se distribuye de tres maneras: el shell de escritorio Tauri con un sidecar bajo demanda, un contenedor nginx que hace proxy inverso de su sidecar empaquetado same-origin, y la wheel Jupyter con la app incrustada en el kernel. El escritorio mantiene el procesamiento pesado local, el contenedor le hace proxy y el embed lo deja fuera.

Mapa de despliegue Aplicación de escritorio, nginx en contenedor, sidecar FastAPI local, PWA del navegador y servicios remotos de teselas, y cómo se conectan. USER MACHINE CONTAINER EXTERNAL NETWORK HTTPS:443 /SIDECAR HTTP HTTP:8765 NATIVE FETCH DESKTOP Tauri desktop bundle de la app web Rust core · guarded_http_client MANAGED FastAPI sidecar uv run --frozen · 127.0.0.1:8765 CONTAINER nginx static dist/ proxy inverso /sidecar EXTERNAL Teselas remotas & servicios OGC BROWSER Navegador / PWA CROSSES NETWORK BOUNDARY LOCAL LOOP NATIVE OUTBOUND OPTIONAL COMPONENT

Abra el mapa editorial completo de despliegue para puertos, chips y detalles por superficie.

Imagen de contenedor

El Dockerfile raíz empaqueta la versión de navegador de la app. Usa una etapa de compilación Node para correr la build del workspace de mappview-desktop, y luego copia apps/mappview/dist en una imagen runtime de nginx. La configuración de nginx sirve los assets estáticos y recurre a index.html para las URLs de entrada del navegador.

El workflow Container build de GitHub Actions verifica que la imagen compila en cada push a main. No inicia sesión en ningún registry ni publica imágenes. Compile y etiquete la imagen localmente para desarrollo o publíquela mediante infraestructura propiedad de Meratyn.

La imagen también empaqueta el sidecar de Python opcional (uvicorn) y le hace proxy inverso en /sidecar, así que el navegador lo alcanza same-origin sin CORS. MAPPVIEW_CAPABILITIES se publica tanto en la configuración de runtime como en el sidecar y controla autoridad, no solo la visibilidad de los menús; consulte capacidades de despliegue para el contrato exacto. Defina MAPPVIEW_DISABLE_SIDECAR=1 para ejecutar solo nginx. El contenedor no ejecuta el shell de escritorio Tauri, así que los flujos de trabajo que dependen del acceso al filesystem de escritorio todavía requieren la app de escritorio instalada. Ver Ejecutar con Docker para lo que el sidecar empaquetado respalda y no respalda.

Seguridad

  • La CSP de Tauri mantiene una lista blanca de hosts de teselas y estilos (OpenFreeMap, CARTO).
  • El acceso a archivos usa solo rutas seleccionadas mediante diálogo.

Almacén de confianza HTTP nativo y TLS mutuo

La app de escritorio realiza algunas fetch remotas (resolución de teselas/URLs, GetCapabilities de OGC y similares) desde el proceso Rust nativo en lugar de desde la WebView. Esa ruta (guarded_http_client en src-tauri/src/lib.rs) confía en el almacén de certificados del OS/sistema además de las raíces Mozilla incluidas, de modo que un servidor firmado por una CA empresarial instalada en la máquina se acepta sin configuración adicional.

Para endpoints detrás de TLS mutuo (mTLS), la ruta fetch de la WebView muestra un aviso interactivo de certificados del OS; el cliente nativo Rust no puede hacerlo, así que apúntelo a un certificado de cliente mediante variables de entorno:

VariablePropósito
MAPPVIEW_HTTP_CA_CERTRuta a un bundle PEM de certificados CA adicionales en los que confiar, encima del almacén del OS (para una CA privada no instalada a nivel sistema).
MAPPVIEW_HTTP_CLIENT_CERTRuta al certificado de cliente a presentar. Un archivo .pem (cadena de certificados más una clave privada PKCS#8 sin cifrar) o un bundle PKCS#12 (.p12/.pfx).
MAPPVIEW_HTTP_CLIENT_CERT_PASSWORDFrase de contraseña para un certificado de cliente PKCS#12. Su presencia también fuerza la ruta de código PKCS#12.

Una extensión .p12/.pfx (o una frase de contraseña suministrada) selecciona PKCS#12; cualquier otra ruta se lee como PEM. Las identidades PEM usan el backend rustls predeterminado; las identidades PKCS#12 usan el backend native-tls nativo de la plataforma (SChannel en Windows, Secure Transport en macOS, OpenSSL en Linux), que también lee el almacén de confianza del OS. Convierta un export PKCS#12 a PEM con openssl pkcs12 -in cert.p12 -out cert.pem -nodes si prefiere la ruta rustls. Un valor definido pero vacío para cualquiera de estas variables se trata como no definido.

El certificado de cliente configurado se custodia en el cliente HTTP nativo compartido y por tanto se presenta a cualquier servidor HTTPS que solicite uno durante una fetch nativa (hosts de teselas, estilos y OGC a los que apunte un proyecto), no solo al endpoint para el que fue emitido el certificado. TLS envía un certificado de cliente solo cuando el servidor lo pide, así que esto no es una divulgación incondicional, pero configure un certificado de cliente únicamente cuando los hosts con los que habla la app sean de confianza.

Rendimiento: renderizado del mapa en Linux (WebKitGTK)

La app de escritorio usa la WebView del sistema. En Linux es WebKitGTK, cuyo pipeline WebGL/JavaScript es materialmente más lento que el motor Chromium en el que corre la build de navegador. El efecto más visible es el desplazamiento del mapa a zoom bajo:

  • Un mapa en blanco (sin capa de teselas) se desplaza a un estable 60 FPS a cualquier zoom.
  • Con cualquier capa de teselas (vectorial o ráster XYZ), el FPS colapsa a dígitos simples mientras se cargan teselas, y luego regresa a 60 una vez que la carga termina, al mismo zoom. El zoom bajo solo lo vuelve constante porque desplazarse por todo el mundo carga teselas continuamente y la caché nunca se asienta.

El costo es que WebKitGTK procesa cada tesela recién cargada en el hilo principal: la subida a la GPU de su textura (ráster) o buffers de vértices (vectorial) mediante llamadas síncronas WebGL, más los frames subsiguientes de repintado por fundido. El análisis de teselas vectoriales y la construcción de buckets corren en los Web Workers de MapLibre, así que no son el cuello de botella aquí. Cada ciclo de renderizado de integración de teselas medía ~125 ms de reloj en WebKitGTK frente a unos pocos ms en Chromium. La brecha está en la implementación WebGL de WebKitGTK y en su pipeline de composición (flush del command-stream del driver, composición de superficie TextureMapper GL), no únicamente en su motor JavaScript JavaScriptCore. Es una limitación del motor de WebView, no un bug de Meratyn, y no afecta a la build de navegador ni (sin probar) a las WebView de macOS/Windows.

Descartado durante el diagnóstico (para que investigaciones futuras no lo repitan): render por software (la GPU está en uso; Intel i915 confirmado), saturación de GPU (el motor de render se mantiene ~20% ocioso), lectura de archivos por IPC de Tauri (~126 ms para un GeoJSON de 22 MB), JSON.parse (~36 ms), latencia del compositor KWin, renderWorldCopies, y la proyección globe vs. mercator. Preservar el drawing buffer de WebGL no era la causa de esos picos de integración de teselas, pero imponía un costo global de memoria/tasa GPU. Ya está deshabilitado; impresión y captura de video realizan un repintado síncrono justo antes de leer el canvas.

Para reproducir la medición: registre temporalmente los eventos render de MapLibre, los eventos dataloading (tesela) y un contador requestAnimationFrame una vez por segundo; el FPS sigue inversamente la cantidad de teselas cargadas. La comprobación más rápida es este medidor de frame-rate pegado en la consola devtools de la WebView, y luego desplazar mientras observa el FPS registrado:

let f = 0,
  t = performance.now();
(function loop(n) {
  f++;
  if (n - t >= 1000) {
    console.log("FPS", f);
    f = 0;
    t = n;
  }
  requestAnimationFrame(loop);
})(t);

Mitigaciones (reducir cuántas teselas cargan durante un desplazamiento, dado que el costo por tesela está fijado por el motor) todavía no están implementadas: un maxTileCacheSize mayor, teselas ráster de 512px en lugar de 256px, y fadeDuration: 0, idealmente condicionadas a WebKitGTK para que las builds basadas en Chromium conserven fidelidad total.