worldmap-viewer

git clone https://git.godosa.eu/worldmap-viewer

master

..
.containerignore395
Containerfile1597
README.md4171
requirements.lock83

Public map server (container)

A read-only map for anyone, on a small shared machine. Worlds are built elsewhere (a build needs far more memory and time than serving) and copied over; the container holds only code.

1. Build the image

From the folder that holds worldgen/ and worldmap-viewer/:

podman build -f worldmap-viewer/deploy/Containerfile --ignorefile worldmap-viewer/deploy/.containerignore \
    -t worldmap-viewer .

requirements.lock pins the libraries the worlds were built and checked with (same numbers → the same tiles as on the build machine).

2. Copy a world

python deploy.py bundle WORLD_DIR server:/srv/data/map/WORLD_NAME [--res N]   # --dry-run lists it first

Copies config/, places/regions.json and out/r<N>/, leaving out:

  • pins, exports, lore notes and build inputs, which a public server never serves;
  • refined areas and tiles of earlier builds.

It uses rsync -a, which keeps modification times. The build fingerprint depends on them; without them, the server would rebuild every refined area. Nothing is deleted on the server: it prunes what it no longer uses at start, and keeps the tiles it rendered.

Then, on the server, let the eras' serve caches share their identical blocks again (a copy loses that; ≈ half their size on btrfs or reflink XFS):

podman exec worldmap-viewer python deploy.py dedupe /data/WORLD_NAME

Data disk: btrfs with compress=zstd:1, normal copy-on-write (no chattr +C/nodatacow: that turns off both compression and block sharing). Back up nothing under out/: it can all be copied again.

3. Run it

Settings come from WORLDMAP_<OPTION> variables (every serve.py option; a command-line flag wins). The image sets WORLDMAP_PUBLIC=1, WORLDMAP_BIND=0.0.0.0, WORLDMAP_PORT=8765.

Variable Meaning
WORLDMAP_WORLD world folder(s) in the container, comma separated (the first opens by default)
WORLDMAP_RES build resolution to serve
WORLDMAP_ALLOW_HOST the public name(s) the proxy passes on, e.g. maps.example.org
WORLDMAP_WORKERS render processes (≈ one core each while rendering)
WORLDMAP_MAX_RENDERS tiles rendering or waiting at once; more → 503 + Retry-After (default 4 × workers)
WORLDMAP_TILE_CACHE_GB cap on saved tiles; the least recently used go first
WORLDMAP_TILE_KEEP_Z relief + mesh tiles to this zoom are never deleted nor counted (a shipped prerender)

Example Quadlet (/etc/containers/systemd/worldmap-viewer.container). This sizing is for a 4-core, 16 GB machine shared with other services:

[Unit]
Description=Public world map

[Container]
Image=localhost/worldmap-viewer:latest
ContainerName=worldmap-viewer
Volume=/srv/data/map:/data:Z
Environment=WORLDMAP_WORLD=/data/world-a,/data/world-b,/data/world-c
Environment=WORLDMAP_ALLOW_HOST=maps.example.org
Environment=WORLDMAP_WORKERS=4
Environment=WORLDMAP_TILE_CACHE_GB=140
Environment=WORLDMAP_TILE_KEEP_Z=9
PublishPort=127.0.0.1:8765:8765
HealthCmd=python -c "import os, urllib.request as u; u.urlopen('http://127.0.0.1:' + os.environ['WORLDMAP_PORT'] + '/api/worlds', timeout=8)"
HealthStartPeriod=10m

[Service]
MemoryHigh=7G
MemoryMax=8G
CPUWeight=50
Restart=on-failure

[Install]
WantedBy=multi-user.target

Keep % out of Quadlet lines: systemd expands %s, %h and the like (write %% for a literal %). The HealthCmd above builds its URL without one.

Leave WORLDMAP_RES unset when the worlds differ: each then serves its final build (world-a r5, the others r4). A set WORLDMAP_RES applies to every world.

The image runs as uid 8765. /srv/data/map must be writable by that user as the container maps it: the server writes tiles and serve caches there. :U would do this, but it re-chowns every file at each start, which is slow with millions of tiles. Set the ownership once instead.

The first start after a new copy can take minutes, while the server loads or builds its serve caches. Ship them built (they are part of out/r<N>/) and it starts in about half a minute.

Single-disk machines: set a smaller WORLDMAP_TILE_CACHE_GB (e.g. 20).