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.shniroku follows the proven x-24b architecture:
Web Browser ←→ Caddy (Reverse Proxy) ←→ Martin (PMTiles Server)
- Caddy: Handles HTTP serving, CORS, and reverse proxying
- Martin: Serves PMTiles vector tiles with high performance
- systemd services: Both run as system services with automatic restart
niroku is the next iteration of UNVT Portable. Compared to earlier versions, it:
- Focuses on a simpler, auditable install process
- Adds practical tools (Node.js+Vite, Docker, cloudflared, tippecanoe, go‑pmtiles)
- Improves defaults for Raspberry Pi OS trixie (e.g., /tmp tmpfs handling)
- Keeps architecture minimal: Caddy + Martin
What you get:
- Local web map server running on Raspberry Pi
- Local network access to geospatial data without internet
- Simple setup using one script
- Caddy + Martin based architecture
- Hardware:
- Raspberry Pi 4 or later (recommended, full feature support)
- Raspberry Pi Zero W (supported, Martin requires source build)
- Architecture: arm64/aarch64, armv6l (32-bit)
- OS: Raspberry Pi OS (Debian trixie or compatible)
- Storage: Minimum 16GB microSD card (128GB+ recommended for map data)
- Network: Ethernet or WiFi connectivity for initial setup
- Permissions: Root/sudo access required
- Raspberry Pi 4/400 (arm64): Full support with prebuilt binaries for all components
- Raspberry Pi Zero W (armv6l):
- Martin tile server not available as prebuilt binary (requires source build with Rust)
- Docker CE not officially supported (alternative container runtimes may work)
- All other components install normally
The niroku installer will:
- ✅ Update system packages and clean up legacy repositories
- ✅ Install base tools:
aria2,btop,tree,ncdu,gdal-bin,libgdal-dev,git,jq,ruby,tmux,vim - ✅ Install dependencies:
curl,wget,hostapd,dnsmasq,qrencode, build tools, and related packages - ✅ Detect architecture and set compatibility flags for arm64/armv6l
- ✅ Install Caddy web server (v2.10.2, reverse proxy with CORS support)
- ✅ Install Martin tile server (v0.19.3, PMTiles hosting with web UI, skipped on armv6l)
- ✅ Install Node.js LTS (v22) via NodeSource and npm packages (
vite@latest,maplibre-gl@latest,pmtiles@latest) - ✅ Install Docker Engine (CE 28.5.1) from official Docker repository (skipped on armv6l)
- ✅ Install cloudflared (2025.10.0) for Cloudflare Tunnel support
- ✅ Install tippecanoe (vector tile tool, from Debian repo or built from source)
- ✅ Install go-pmtiles (PMTiles CLI tool, v1.18.0)
- ✅ Create installation directory at
/opt/nirokuwith data subdirectory - ✅ Configure services: Both Caddy and Martin run as systemd services with automatic restart
- ✅ Set up configurations:
martin.yml(PMTiles paths, web UI, base_path:/martin) andCaddyfile(reverse proxy, CORS; forwardsX-Forwarded-Proto,X-Forwarded-Host,X-Forwarded-Port, andHost) - ✅ Disable /tmp tmpfs if present (prevents RAM exhaustion on Raspberry Pi)
- ✅ Generate installation log at
/tmp/niroku_install.logfor troubleshooting - ✅ Install PM11 (optional): When
PM11=1is set, downloads pm11.pmtiles (~1.4 GB, 11-country subset) and creates an interactive web viewer accessible athttp://localhost/pm11/ - ✅ Mirror assets (default with PM11): When
PM11=1is set, the installer mirrors Protomaps basemaps assets (fonts and sprites) by default for offline use
PM11 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:
- Clones the Protomaps basemaps assets repo (protomaps/basemaps-assets) and copies:
- fonts PBFs to
/opt/niroku/data/fonts - sprites (v4) to
/opt/niroku/data/sprites
- fonts PBFs to
- Rewrites the PM11 viewer
style.jsonso it prefers local cache:sources.protomaps.url→/martin/pm11glyphs→/fonts/{fontstack}/{range}.pbfif local fonts exist, otherwise remote Protomaps URLsprite→/sprites/v4/lightif local sprites exist, otherwise remote Protomaps URL
Notes:
- When
PM11=1and you did not set any mirroring flags, niroku defaults toNIROKU_MIRROR_ASSETS=1. - Asset mirroring flag is
NIROKU_MIRROR_ASSETS. For backward compatibility,NIROKU_MIRROR_FONTS=1also enables assets mirroring. - If you set the flag but mirroring fails (e.g., network issue), the installer keeps remote URLs to avoid 404s.
- Uninstaller removes
/opt/niroku/data/fontsand/opt/niroku/data/spritesfor symmetry.
When PM11=1 is set, the installer will:
- Download
pm11.pmtiles(~1.4 GB) from https://tunnel.optgeo.org/pm11.pmtiles to/opt/niroku/data/pm11.pmtiles - Create an interactive map viewer using Vite, MapLibre GL JS, and PMTiles
- Install the viewer site at
/opt/niroku/data/pm11/ - Configure the viewer to use the local
/pm11.pmtilesfile
Notes:
- The download size is approximately 1.4 GB. On slow or metered connections, consider preloading the file or using a wired connection.
- Ensure at least 2 GB of free space on the device for the dataset and viewer.
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:
- Interactive map with zoom and pan controls
- Vector tile rendering using MapLibre GL JS
- Layers: water, transportation, buildings, and place labels
- Direct PMTiles access without a tile server
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.shThe script checks:
http://<host>/pm11/style.jsonis reachablehttp://<host>/sprites/v4/light.jsonandlight.pngare reachable- Validates
light.jsonis parseable JSON (requiresjq)
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.logSkip 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.shThis will remove both /opt/niroku/data/pm11.pmtiles and /opt/niroku/data/pm11/ directory.
After installation completes:
- Access the web interface:
# 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]/martinVerify 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}"- Add your map data:
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}
- Manage services:
# 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- Configure WiFi Access Point (optional):
- Refer to the UNVT Portable WiFi setup guide
- Generate QR codes using the installed
qrencodetool
- Use installed tools:
# 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.pmtilesFor 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- Martin runs behind Caddy at
http://[YOUR_IP]/martin. - Martin configuration sets
base_path: /martinso that TileJSON URLs always include the/martinprefix. - Caddy strips
/martinand proxies to127.0.0.1:3000, forwarding these headers upstream: -X-Forwarded-Proto,X-Forwarded-Host,X-Forwarded-Port, andHost. - You do not need
X-Rewrite-URLbecausebase_pathis used. - To verify, fetch a TileJSON and check that
.tiles[0]includes/martin/.
-
Pipe-to-shell pattern: While convenient, piping scripts to bash can be risky. This installer:
- Is open source and auditable
- Uses
set -eto exit on errors - Validates system requirements before making changes
- Provides colored output for clear progress tracking
-
Review before running: We encourage reviewing the script before execution
-
Run with sudo: Required for system-level changes
-
Local network only: By default, niroku is designed for local network access
- UNVT Portable Wiki: https://github.com/unvt/portable/wiki
# Ensure you're using sudo
sudo ./install.shThe 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 50If 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 updateMartin 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 --versionNote: 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.
- The installer pre-installs global npm packages using the
@latesttag for certainty:vite@latest,maplibre-gl@latest,pmtiles@latest. - If you need to move the npm global cache to an offline device, you can archive the global node_modules and restore it on the target machine. Example workflow:
# 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 || trueNote: 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 martinThe 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 /tmpIf 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.shTo 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.shThe uninstaller will:
- Stop and remove Martin tile server service
- Stop and remove Caddy-niroku web server service
- Remove Martin binary from
/usr/local/bin/martin - Remove Caddy package and repository configuration
- Remove Node.js packages and NodeSource repository
This 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.shsymmetric withinstall.shby addingremove_*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
- A basic cycle test (uninstall -> install) was executed on device
m333(Raspberry Pi 400 / aarch64) as part of this release verification. Logs are available on the device at/tmp/niroku_install.logand/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
- fix: PM11 viewer now synchronizes the map position with the URL hash (
hash: true) and adds a ScaleControl using metric units for consistent measurements. - fix: GlobeControl (when available in MapLibre) is placed at the upper-right for consistent UI placement; GeolocateControl is intentionally disabled for non-HTTPS viewers.
- fix: installer now validates and recreates
package.jsonbefore runningnpm installto avoid JSON parse errors during the PM11 build. - fix: improved sprite mirroring: installer ensures
sprites/v4/light.jsonandlight.pngare present, attempts to download missing sprites when mirroring is enabled, and creates alight@2x.pngfallback when necessary. - add: PM11 smoke tests run automatically after install to verify
style.jsonand sprite endpoints and log the results to/tmp/niroku_install.log.
Why this matters
- Reduces visible client-side errors like "Invalid sprite URL" and missing sprite images in the PM11 viewer.
- Makes PM11 installation more resilient on devices with flaky networks or partial mirrors.
Testing
- The full PM11 install flow (download pm11.pmtiles, mirror assets, build viewer) was exercised on
m333(Raspberry Pi 400 / aarch64). Installation logs live at/tmp/niroku_install.logon-device.
- Remove Docker Engine packages and repository
- Remove cloudflared package
- Remove tippecanoe (if installed from source)
- Remove go-pmtiles binary from
/usr/local/bin/pmtiles - Remove UNVT Portable installation directory (
/opt/niroku) - Remove base packages (
aria2,btop,tree,ncdu,gdal-bin,libgdal-dev,jq,ruby,tmux,vim) - Optionally remove comprehensive packages (you'll be prompted)
- Clean up unused dependencies
- Generate uninstallation log at
/tmp/niroku_uninstall.log
The 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.
- Repository: https://github.com/unvt/niroku
- Issues: https://github.com/unvt/niroku/issues
- License: CC0 1.0 Universal (Public Domain)
- unvt/x-24b - Reference architecture for Caddy + Martin setup
- unvt/portable - Main UNVT Portable repository
- unvt/portable-j-22 - JICA 2022 version
- unvt/kagero - Power monitoring tool
- unvt/yata - Solar power supply
- Martin - Blazing fast and lightweight tile server
- Caddy - Fast and extensible multi-platform web server
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.