Manual/Reference

Self-host Meratyn

Meratyn is a portable Node service for exchanging .meratyn.json projects. The monorepo includes an authenticated admin workspace, version admin, collections, personal API tokens, thumbnails, and the HTTP contract used by the Meratyn desktop Share dialog.

It no longer depends on Cloudflare:

  • Bun serves the TanStack Start SPA shell and API from one process.
  • SQLite stores users, metadata, collections, and SHA-256 API-token hashes.
  • A mounted filesystem stores immutable project versions and thumbnails.
  • Clerk remains optional for the admin console only. Viewer, desktop, and embed surfaces stay local-first and do not inherit admin authentication; Meratyn does not add a second hosted identity provider for those surfaces.
  • A lightweight nginx container serves the local Meratyn viewer used by embedded maps and “Open in Meratyn” links.

Run with Docker Compose

Docker Desktop or another Docker Engine with Compose v2 is required.

cp .env.example .env
docker compose -f docker/compose/admin.yaml up --build -d
docker compose -f docker/compose/admin.yaml ps

Open https://admin.mappview.mapptech.com.co. The Meratyn viewer runs at https://mappview.mapptech.com.co. The default Compose profile creates a local account named local-mapper for API and test access using the development-only ADMIN_LOCAL_TOKEN from the copied .env file. The browser remains signed out by default and shows only the authentication gate. To open the offline workspace without Clerk, set ADMIN_BROWSER_TOKEN to the same value as ADMIN_LOCAL_TOKEN and rebuild. If Clerk keys are present, the Clerk session takes precedence over that fallback and the workspace uses Clerk’s modal sign-in and user menu.

Do not reuse the example token outside loopback development. Replace it with a long random mappview_… value before exposing the service to another machine.

To stop the service without deleting data:

docker compose -f docker/compose/admin.yaml down

Project data persists through the logical volume management-data. Its default Docker volume name is mappview-management-data, and the database is stored at /data/admin.db. Set ADMIN_DATA_VOLUME or ADMIN_DB_PATH when intentionally choosing a different location. To explicitly delete local test data, add --volumes to the down command.

To publish and manage local GIS files, start the optional headless GeoNode and GeoServer profile:

docker compose -f docker/compose/admin.yaml --profile hosted-layers up --build -d

Replace the development passwords in .env.example first. See Deploy hosted layers for the service topology, configuration, and production requirements.

Load the sample vector and raster layers

After the hosted-layers profile is healthy, seed two small public examples:

bun run seed:online-layers

The command reads ADMIN_LOCAL_TOKEN from the shell or repository-root .env, uploads tests/fixtures/sample-colombia-cities.geojson and tests/fixtures/striped.tif, waits for publication, and prints their Share admin URLs. It is idempotent for samples it previously created. It also fails if publication changes the user-entered title or returns an internal publication detail in the public layer model.

Configuration

Set Compose substitutions in a local .env file or in the shell:

VariableDefaultPurpose
ADMIN_BIND_ADDRESS127.0.0.1Host interface used to publish Share
ADMIN_PORT8787Published host port
ADMIN_TRUST_PROXYfalseTrust CF-Connecting-IP or the first X-Forwarded-For address for view deduplication
VIEWER_BIND_ADDRESS127.0.0.1Host interface used to publish the viewer
VIEWER_PORT8080Published local Meratyn viewer port
APP_ORIGINhttps://admin.mappview.mapptech.com.coCanonical admin origin
ADMIN_LOCAL_TOKENlocal development valueBootstrapped mappview_… token
ADMIN_BROWSER_TOKENemptyOptional local token compiled into the browser for offline dashboard testing
ADMIN_BOOTSTRAP_USERNAMElocal-mapperLocal account username
MAX_PROJECT_BYTES26214400Maximum project size
DEFAULT_STORAGE_QUOTA_BYTES104857600Per-user storage quota
FIELD_PHOTO_MAX_BYTES26214400Maximum size of each field photo authorized for direct storage upload
VIEWER_URLhttps://mappview.mapptech.com.coViewer used for project links
CLERK_PUBLISHABLE_KEYemptyClerk key compiled into the TanStack Start frontend
CLERK_SECRET_KEYemptyClerk backend secret used to verify session tokens
CLERK_JWT_KEYemptyOptional PEM public key alternative to the Clerk secret
CLERK_AUTHORIZED_PARTIESemptyComma-separated allowed browser origins
GEONODE_CATALOGUE_SYNC_INTERVAL_MS300000Interval for importing and reconciling GeoNode catalogue records; minimum 60 seconds
STUDIO_PASSWORDemptyPassword for the demo studio at /studio; empty turns the studio off
MAPPVIEW_NO_EXTERNAL_CDNemptyBuild-time viewer policy (1 bundles controlled assets and fails on emitted CDN URLs)

CORS_ORIGINS includes the local viewer and Tauri WebView origins by default so the viewer and a desktop build configured for this self-host can fetch project JSON from Share. If either published port changes, update VIEWER_URL, APP_ORIGIN, and CORS_ORIGINS together before rebuilding.

STUDIO_PASSWORD enables the demo studio at /studio: one password, no accounts, and a 12-hour session cookie. The studio sets the scenario values behind the live showcases (Mocoa rainfall flooding, Bogotá air quality, and the Medellín smart city). Their layers are public at /api/studio/feeds/<feed>/layers/<layer>, combine the saved values with live Open-Meteo data, and are rendered once per 5-second tick, so a save reaches an open showcase within seconds. Each layer carries an ETag, so viewers skip layers that did not change since the previous tick. The showcase projects load these layers from STUDIO_FEED_ORIGIN in scripts/assets/showcases/studio.ts; change it and rebuild the showcases to serve them from another deployment.

The browser applications also compile links to the surrounding Meratyn services. Local values are listed in .env.defaults; override these when the services live on different domains. The localhost defaults only resolve on the machine running Compose — before exposing the deployment, set each VITE_MAPPVIEW_*_URL to its public HTTPS origin; otherwise generated project links, embeds, and help links will point at the user’s own machine:

VariableLocal defaultUsed for
VITE_MAPPVIEW_WEBSITE_URLhttps://mappview.mapptech.com.coMain-site and help links
VITE_MAPPVIEW_VIEWER_URLhttps://mappview.mapptech.com.co/Viewer, embeds, and HTML exports
VITE_MAPPVIEW_SHARE_URLhttps://admin.mappview.mapptech.com.coProject and hosted-layer APIs
VITE_MAPPVIEW_TILES_URLhttps://tiles.mappview.mapptech.com.coTile, OAM, Source Cooperative, and planetary proxies
VITE_MAPPVIEW_PLUGINS_URLviewer originPlugin marketplace origin
VITE_MAPPVIEW_PLUGIN_REGISTRY_URLhttps://plugins.mappview.mapptech.com.co/registry.jsonCurated plugin registry (served by the plugins container)
VITE_MAPPVIEW_TRUSTED_PLUGIN_REGISTRY_URLhttps://plugins.mappview.mapptech.com.co/registry.jsonDeployment-approved external plugins
VITE_MAPPVIEW_DATASETS_URLhttp://localhost:8790GeoLens sample catalogue

The desktop Vite build reads these from the repository-level .env as well as the app-local environment. They are build-time values, so rebuild browser images after changing them. The Python widget uses MAPPVIEW_VIEWER_URL (and accepts VITE_MAPPVIEW_VIEWER_URL as a fallback).

For a Docker runtime, MAPPVIEW_CAPABILITIES is an optional comma-separated authority policy shared by the viewer and sidecar. Use it with a kiosk or classroom UI profile when those actions must be denied, rather than relying on the visible menu alone. The sidecar requires both processing:run and processing:sidecar; see deployment capabilities.

The trusted registry document itself is fetched at runtime. After its URL has been compiled into the viewer, publishing new versioned entries to that same URL does not require another viewer deployment.

The default ports bind only to loopback because the default bootstrap token is a known local-development credential. Before setting either bind address to 0.0.0.0 or another externally reachable interface, replace ADMIN_LOCAL_TOKEN with a long random value or configure Clerk. Only set ADMIN_BROWSER_TOKEN on a trusted local machine: it embeds that credential in the browser bundle and therefore must never be used for an internet-facing deployment.

Clerk authentication

Clerk’s Hobby plan is sufficient for local testing and small deployments. It does not require a credit card and includes the prebuilt sign-in, sign-up, and user-profile interfaces. Copy .env.example to .env, create an application in the Clerk dashboard, and fill in:

CLERK_PUBLISHABLE_KEY=pk_test_...
CLERK_SECRET_KEY=sk_test_...
CLERK_AUTHORIZED_PARTIES=http://localhost:8787,http://127.0.0.1:8787,http://localhost:5174,http://127.0.0.1:5174

Then rebuild because TanStack Start and Vite compile the publishable key into the browser bundle:

docker compose -f docker/compose/admin.yaml up --build -d

The frontend uses @clerk/tanstack-react-start with ClerkProvider, a modal SignInButton, UserButton, and session getToken() for authenticated API requests. The Node service verifies those tokens with @clerk/backend. No Clerk key is committed to the repository.

The two 5174 origins authorize the split development server started by bun run dev:admin; the launcher also merges them into CLERK_AUTHORIZED_PARTIES automatically. Keep the 8787 origins for the same-origin Compose build. Add every other browser origin that can issue Clerk tokens, including both hostname variants when your team uses both, or the API will correctly reject those sessions with HTTP 401.

Production authentication

For a non-local deployment, explicitly set ADMIN_LOCAL_TOKEN and ADMIN_BROWSER_TOKEN to empty values, provide CLERK_PUBLISHABLE_KEY, and set CLERK_SECRET_KEY or CLERK_JWT_KEY. Set CLERK_AUTHORIZED_PARTIES, APP_ORIGIN, and CORS_ORIGINS to the actual browser, viewer, and Tauri WebView origins. Set ADMIN_BIND_ADDRESS for the reverse proxy’s network path and put TLS in front of port 8787. Set ADMIN_TRUST_PROXY=true only when Share is reachable exclusively through a proxy that replaces CF-Connecting-IP or X-Forwarded-For; this preserves per-client view counts without allowing direct clients to spoof their address.

The Compose file is deliberately optimized for local testing. Production operators should use secrets rather than plain environment values and should back up the /data volume.

Develop without Docker

Use Bun 1.4.2 or later:

bun install
bun run dev:admin

The command starts the admin API on port 8787 and the Vite frontend on https://admin.mappview.mapptech.com.co, with source watching for both processes. It loads the repository-root .env and maps CLERK_PUBLISHABLE_KEY to Vite; when Clerk is not configured, it supplies a matching local-development token instead. The launcher reuses a healthy admin API already listening on port 8787, such as the Docker Compose service. The service stores local data in services/admin/data/admin.db and services/admin/data/objects; the directory is ignored by Git. Set ADMIN_BOOTSTRAP_TOKEN or VITE_ADMIN_LOCAL_TOKEN before starting to override the default development token.

Run the portable integration suite with:

bun run test:admin

It verifies the built TanStack Start shell, health endpoint, publishing, raw downloads, versions, forks, thumbnails, collections, private access, and hashed personal tokens.

With the Compose stack running, verify route parity, the local viewer request, required mobile widths, and accessibility with:

bun run test:admin:e2e

Connect Meratyn desktop

Set the desktop/web build variable to your admin origin:

VITE_MAPPVIEW_SHARE_URL=http://localhost:8787

Packaged desktop builds use browser fetch for custom Share origins. The Share service must therefore include tauri://localhost and http://tauri.localhost in CORS_ORIGINS; the Compose defaults already do so.

Users create a personal token under Meratyn Settings, then paste that mappview_… token into Meratyn Settings. Project source versions are immutable; restoring a version creates a new one.

Backup and restore

Back up the entire /data volume as one unit. admin.db contains metadata, while objects/ contains project JSON and thumbnails. Restoring only one side will leave the project inventory and stored files inconsistent.

Engram cloud memory

Engram runs as a standalone stack in docker/compose/engram.yaml — it is NOT part of docker/compose/admin.yaml. The engram container serves cloud serve with the container listening on internal port 18080, published loopback-only on host port 127.0.0.1:18083 -> 18080. It is backed by the EXISTING VPS postgres — the geonode postgis container on the openship-mappview-admin network, engram database owned by the engram role — so there is no dedicated engram-db service and no engram-db-data volume here.

Public route: https://engram.mappview.mapptech.com.co via the host nginx TLS proxy in scripts/deploy/nginx/engram.mappview.mapptech.com.co.conf. The container stays loopback-only; nginx proxies all paths to http://127.0.0.1:18083 with standard forwarded headers. TLS requires a Let’s Encrypt certificate for this hostname (/etc/letsencrypt/live/engram.mappview.mapptech.com.co/); port 80 serves the ACME challenge and redirects to HTTPS.

Engram runs in authenticated mode. Dashboard login is /dashboard/login (full URL https://engram.mappview.mapptech.com.co/dashboard/login). Managed admin tokens can log into the dashboard and sync. Raw tokens are never committed or printed in docs: the local CLI reads ENGRAM_CLOUD_TOKEN at runtime from the environment or a secret manager.

Start it alongside the admin stack with an explicit file selection:

docker compose -f docker/compose/engram.yaml up --build -d

Configure it with server-side environment only — copy the ENGRAM_* names from .env.example into the server environment and set real values there. ENGRAM_DATABASE_URL points at the existing VPS postgres (postgres://engram@geonode-db:5432/engram, password in the URL, secret). The project allowlist is fixed to meratyn in Compose.

Operator outline (VPS-side, after this commit lands):

  1. Install the nginx config and issue the certificate for engram.mappview.mapptech.com.co, then reload nginx.
  2. Bootstrap one managed admin token server-side and distribute managed tokens to developers. Never commit token values.
  3. Developers follow “Shared Engram memory” in the root README.md (server config, enroll, sync, status).

Enroll a machine with:

engram cloud enroll mappview

Back up the memory rows together with geonode: they live in the engram database inside the existing VPS postgres, so they are covered by the geonode database backup, not by a separate volume. Never bring the stack down with volume deletion. For cloud deploys, see odd/tasks/engram-cloud-service.md (standalone -ServiceNames selection after one --full reconcile).