niroku — a new implementation of UNVT Portable with JICA, for 2026 (Raspberry Pi OS trixie)
niroku is a new implementation of UNVT Portable. It is co‑developed with JICA Quick Mapping Project and targeted for 2026 use. niroku sets up an offline local web map server on Raspberry Pi OS (trixie). It uses Caddy (reverse proxy) and Martin (PMTiles tile server). It is designed for field operations where power and connectivity can be unstable.
The simplest way to install niroku:
curl -fsSL https://unvt.github.io/niroku/install.sh | sudo -E bash -
Or using wget:
wget -qO- https://unvt.github.io/niroku/install.sh | sudo -E bash -
If you prefer to review the script before running:
# Download the script
curl -fsSL https://unvt.github.io/niroku/install.sh -o install.sh
# Review the script
less install.sh
# Make it executable
chmod +x install.sh
# Run the installer
sudo ./install.sh
niroku follows the proven x-24b architecture:
Web Browser ←→ Caddy (Reverse Proxy) ←→ Martin (PMTiles Server)
niroku is the next iteration of UNVT Portable. Compared to earlier versions, it:
What you get:
The niroku installer will:
aria2, btop, tree, ncdu, gdal-bin, libgdal-dev, git, jq, ruby, tmux, vimcurl, wget, hostapd, dnsmasq, qrencode, build tools, and related packagesvite@latest, maplibre-gl@latest, pmtiles@latest)/opt/niroku with data subdirectorymartin.yml (PMTiles paths, web UI, base_path: /martin) and Caddyfile (reverse proxy, CORS; forwards X-Forwarded-Proto, X-Forwarded-Host, X-Forwarded-Port, and Host)/tmp/niroku_install.log for troubleshootingPM11=1 is set, downloads pm11.pmtiles (~1.4 GB, 11-country subset) and creates an interactive web viewer accessible at http://localhost/pm11/PM11=1 is set, the installer mirrors Protomaps basemaps assets (fonts and sprites) by default for offline usePM11 is a lightweight planet.pmtiles subset covering 11 countries, created by the hfu/pm11 project. It’s useful for testing and demonstrations without downloading the full planet dataset.
To install PM11 along with niroku:
# Install niroku with PM11
sudo PM11=1 ./install.sh
# Or using one-line install
curl -fsSL https://unvt.github.io/niroku/install.sh | sudo -E PM11=1 bash -
By default, when you enable PM11 (PM11=1), niroku mirrors basemaps assets so the viewer can run offline. You can also control this explicitly:
# Install niroku with PM11 (assets mirroring is ON by default)
sudo PM11=1 ./install.sh
# Disable mirroring explicitly
sudo NIROKU_MIRROR_ASSETS=0 PM11=1 ./install.sh
# Force enable mirroring explicitly
sudo NIROKU_MIRROR_ASSETS=1 PM11=1 ./install.sh
# One-line (with explicit enable)
curl -fsSL https://unvt.github.io/niroku/install.sh | sudo -E NIROKU_MIRROR_ASSETS=1 PM11=1 bash -
What this does:
/opt/niroku/data/fonts/opt/niroku/data/spritesstyle.json so it prefers local cache:
sources.protomaps.url → /martin/pm11glyphs → /fonts/{fontstack}/{range}.pbf if local fonts exist, otherwise remote Protomaps URLsprite → /sprites/v4/light if local sprites exist, otherwise remote Protomaps URLNotes:
PM11=1 and you did not set any mirroring flags, niroku defaults to NIROKU_MIRROR_ASSETS=1.NIROKU_MIRROR_ASSETS. For backward compatibility, NIROKU_MIRROR_FONTS=1 also enables assets mirroring./opt/niroku/data/fonts and /opt/niroku/data/sprites for symmetry.When PM11=1 is set, the installer will:
pm11.pmtiles (~1.4 GB) from https://tunnel.optgeo.org/pm11.pmtiles to /opt/niroku/data/pm11.pmtiles/opt/niroku/data/pm11//pm11.pmtiles fileNotes:
After installation with PM11:
# Access the PM11 viewer
http://localhost/pm11/
# Or from another device on the network
http://[YOUR_PI_IP]/pm11/
The viewer provides:
If you want to verify the PM11 viewer and sprites without opening a browser (useful on headless devices), the repository includes a small check script.
On the device (or where Caddy is serving):
# Run quick checks against localhost (or pass host as the first arg)
sudo bash /opt/niroku/scripts/check_pm11_sprites.sh
The script checks:
http://<host>/pm11/style.json is reachablehttp://<host>/sprites/v4/light.json and light.png are reachablelight.json is parseable JSON (requires jq)If the script reports failures, inspect /opt/niroku/data/sprites/v4 and /opt/niroku/data/pm11/style.json, and view Caddy logs:
sudo journalctl -u caddy-niroku -n 200
sudo cat /tmp/niroku_install.log
Skip the bundled verification script
If you prefer not to run the bundled PM11 verification script as part of post-install smoke checks, set the environment variable NIROKU_SKIP_PM11_CHECK=1 when running the installer. This is useful for automated deployments or when you want to postpone checks.
# Skip the PM11 verification step during install
sudo NIROKU_SKIP_PM11_CHECK=1 ./install.sh
# Or when piping the installer
curl -fsSL https://unvt.github.io/niroku/install.sh | sudo -E NIROKU_SKIP_PM11_CHECK=1 bash -
PM11 is automatically removed when you run the uninstall script:
sudo ./uninstall.sh
This will remove both /opt/niroku/data/pm11.pmtiles and /opt/niroku/data/pm11/ directory.
After installation completes:
# Find your Raspberry Pi's IP address
hostname -I
# Access via web browser at:
# http://[YOUR_IP_ADDRESS]
# Martin tile server at:
# http://[YOUR_IP_ADDRESS]/martin
Verify TileJSON URLs include the /martin prefix:
# Replace [SOURCE] with your PMTiles source name (e.g., pm11)
curl -fsSL http://[YOUR_IP_ADDRESS]/martin/[SOURCE] | jq '.tiles[0]'
# Expected: a URL containing "/martin/", e.g. "http://[YOUR_IP_ADDRESS]/martin/[SOURCE]/{z}/{x}/{y}"
Place PMTiles files in /opt/niroku/data. Martin will automatically detect and serve them.
Access tiles at: http://[YOUR_IP]/martin/[filename]/{z}/{x}/{y}
# Check service status
systemctl status martin
systemctl status caddy-niroku
# View logs
journalctl -u martin -f
journalctl -u caddy-niroku -f
# Restart services
systemctl restart martin caddy-niroku
qrencode tool# Check installed versions
node --version # Node.js LTS
npm --version
vite --version
docker --version # Docker Engine
cloudflared --version # Cloudflare Tunnel
tippecanoe --version # Vector tile tool
pmtiles --version # PMTiles CLI
martin --version # Tile server
caddy version # Web server
# Example: Convert GeoJSON to PMTiles
tippecanoe -o output.pmtiles input.geojson
# Example: Inspect PMTiles metadata
pmtiles show /opt/niroku/data/yourfile.pmtiles
For non-interactive installations (e.g., automated deployments), you can use these environment variables:
# Skip OS compatibility check
export NIROKU_FORCE_OS=1
# Keep existing installation (default is to overwrite)
export NIROKU_KEEP_EXISTING=1
# Install PM11 (11-country planet.pmtiles subset) and viewer
export PM11=1
# Mirror basemaps assets (fonts + sprites)
# Default: enabled when PM11=1 (set NIROKU_MIRROR_ASSETS=0 to disable)
export NIROKU_MIRROR_ASSETS=1
# Backward-compatible alias (also enables asset mirroring when set to 1)
export NIROKU_MIRROR_FONTS=1
# Example: Full non-interactive install (overwrites existing by default)
sudo NIROKU_FORCE_OS=1 ./install.sh
# Example: Non-interactive install that keeps existing installation
sudo NIROKU_FORCE_OS=1 NIROKU_KEEP_EXISTING=1 ./install.sh
# Example: Install with PM11 feature (mirroring ON by default)
sudo PM11=1 ./install.sh
# Example: Install with PM11 but disable assets mirroring
sudo NIROKU_MIRROR_ASSETS=0 PM11=1 ./install.sh
http://[YOUR_IP]/martin.base_path: /martin so that TileJSON URLs always include the /martin prefix./martin and proxies to 127.0.0.1:3000, forwarding these headers upstream:
- X-Forwarded-Proto, X-Forwarded-Host, X-Forwarded-Port, and Host.X-Rewrite-URL because base_path is used..tiles[0] includes /martin/.⚠️ Important Security Notes:
set -e to exit on errors# Ensure you're using sudo
sudo ./install.sh
The installer creates a detailed log file for troubleshooting:
# View installation log
sudo cat /tmp/niroku_install.log
# View uninstallation log
sudo cat /tmp/niroku_uninstall.log
# Check Martin status
sudo systemctl status martin
# Check Caddy status
sudo systemctl status caddy-niroku
# View logs
sudo journalctl -u martin -n 50
sudo journalctl -u caddy-niroku -n 50
If you see errors about missing repository files (e.g., legacy cloudflared repository), the installer will automatically clean them up. If issues persist:
# Manually remove problematic repository files
sudo rm -f /etc/apt/sources.list.d/cloudflared.list
sudo rm -f /etc/apt/keyrings/cloudflare-main.gpg
sudo apt-get update
Martin does not provide prebuilt binaries for armv6l architecture (Raspberry Pi Zero W). If you need Martin on Pi Zero, you must build it from source:
# Prerequisites: Rust toolchain and build dependencies
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
source ~/.cargo/env
# Install build dependencies
sudo apt-get install -y cmake protobuf-compiler libsqlite3-dev libssl-dev build-essential
# Disable /tmp tmpfs to avoid "No space left" errors
sudo systemctl mask tmp.mount
sudo reboot
# Build Martin (takes several hours on Pi Zero)
cargo install martin --locked --jobs 1
# Install the binary
sudo install -m 0755 ~/.cargo/bin/martin /usr/local/bin/martin
# Verify installation
martin --version
Note: Building Martin on Pi Zero W with --jobs 1 can take 4-6 hours. Consider building on a faster machine with cross-compilation, or use Pi Zero for Caddy-only setups.
niroku can cache some npm packages during installation so devices with limited or no internet access can still install required frontend packages.
@latest tag for certainty: vite@latest, maplibre-gl@latest, pmtiles@latest.# On a machine with internet access (after running install.sh):
sudo tar -C /usr/lib -czf /tmp/npm-global-cache.tar.gz node_modules
sudo chown $USER:$USER /tmp/npm-global-cache.tar.gz
# Copy the archive to the offline device (e.g., via USB or scp)
sudo tar -C /usr/lib -xzf /tmp/npm-global-cache.tar.gz
sudo npm rebuild -g || true
Note: Global package paths vary by distribution and Node.js packaging. On Debian-based NodeSource installs, global modules typically live under /usr/lib/node_modules.
For automated testing we use expect to wrap scp and ssh so the test harness can authenticate to test devices using the niroku user with password niroku. Example expect one-liners used in testing:
expect -c 'set timeout 30; spawn scp -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 ./install.sh niroku@m333.local:/var/tmp/install.sh; expect -re {password:}; send "niroku\r"; expect eof'
expect -c 'set timeout 1200; spawn ssh -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 niroku@m333.local "sudo /var/tmp/install.sh"; expect -re {password:}; send "niroku\r"; expect eof'
Use these only for temporary testing harnesses. Do not hard-code credentials or expose them in production documentation.
# Check what's using port 80
sudo lsof -i :80
# Stop conflicting service if needed
sudo systemctl stop [service-name]
# Or edit the Caddyfile to use a different port
sudo nano /opt/niroku/Caddyfile
sudo systemctl restart caddy-niroku
# Ensure files are in the correct directory
ls -la /opt/niroku/data/
# Check file permissions
sudo chmod 644 /opt/niroku/data/*.pmtiles
# Restart Martin
sudo systemctl restart martin
The installer automatically falls back to /var/tmp if /tmp is not writable. If you encounter issues:
# Check /tmp permissions
ls -ld /tmp
# Should be: drwxrwxrwt (permissions 1777)
sudo chmod 1777 /tmp
If Docker fails to install or the GPG key conflicts occur:
# Remove existing Docker GPG key
sudo rm -f /etc/apt/keyrings/docker.gpg
# Re-run the installer
sudo ./install.sh
To remove niroku and all installed components:
curl -fsSL https://unvt.github.io/niroku/uninstall.sh | sudo -E bash -
# Download the uninstall script
curl -fsSL https://unvt.github.io/niroku/uninstall.sh -o uninstall.sh
# Review the script
less uninstall.sh
# Make it executable
chmod +x uninstall.sh
# Run the uninstaller
sudo ./uninstall.sh
The uninstaller will:
/usr/local/bin/martinThis release contains a small but important refactor to make the installer and uninstaller more maintainable and more robust in the field.
Highlights
refactor: add a reusable download helper try_download() to centralize robust curl downloads and candidate URL attempts
refactor: add create_systemd_service() helper to centralize systemd unit creation, enable and start logic
refactor: make uninstall.sh symmetric with install.sh by adding remove_* helpers (e.g. remove_martin, remove_caddy, remove_go_pmtiles) so each install step has a clear uninstall counterpart
feature: lightweight post-install smoke checks to verify service status and basic HTTP responses
Why this matters
Reduces duplicated code paths and makes future changes safe and easier to audit
Better logging and clearer failure points when used in automated test harnesses
Compatibility notes
Raspberry Pi 4/400 (arm64): Full support — prebuilt binaries are used for Martin and other components
Raspberry Pi Zero W (armv6l): Limited support — Martin prebuilt binaries are not available and must be built from source on-device (see “Building Martin on Raspberry Pi Zero (armv6l)” in Troubleshooting). Docker CE is also not officially supported on armv6l.
Testing
m333 (Raspberry Pi 400 / aarch64) as part of this release verification. Logs are available on the device at /tmp/niroku_install.log and /tmp/niroku_uninstall.log.If you depend on automatic deployments or CI, consider running the installer in a VM or test device first and reviewing /tmp/niroku_install.log after the run.
This is a maintenance release focused on PM11 viewer reliability and offline asset handling.
Highlights
hash: true) and adds a ScaleControl using metric units for consistent measurements.package.json before running npm install to avoid JSON parse errors during the PM11 build.sprites/v4/light.json and light.png are present, attempts to download missing sprites when mirroring is enabled, and creates a light@2x.png fallback when necessary.style.json and sprite endpoints and log the results to /tmp/niroku_install.log.Why this matters
Testing
m333 (Raspberry Pi 400 / aarch64). Installation logs live at /tmp/niroku_install.log on-device.
/usr/local/bin/pmtiles/opt/niroku)aria2, btop, tree, ncdu, gdal-bin, libgdal-dev, jq, ruby, tmux, vim)/tmp/niroku_uninstall.logThe script will ask for confirmation before removing anything and show you exactly what will be removed.
This project is part of the UNVT (United Nations Vector Tile Toolkit) ecosystem.
This project is released under the CC0 1.0 Universal license, dedicating it to the public domain. See LICENSE for details.
niroku is developed as part of the UNVT project to support disaster response and field operations worldwide, with special focus on JICA Knowledge Co-creation Programs.