No description
Find a file
2026-08-03 14:47:33 +01:00
frontend extend more info timeout to 30s 2026-08-03 14:47:33 +01:00
migrations first build 2026-08-03 09:42:13 +01:00
src add gpx editing 2026-08-03 14:21:40 +01:00
.env.example first build 2026-08-03 09:42:13 +01:00
.gitignore first build 2026-08-03 09:42:13 +01:00
Cargo.lock extend more info timeout to 30s 2026-08-03 14:47:33 +01:00
Cargo.toml extend more info timeout to 30s 2026-08-03 14:47:33 +01:00
DESIGN.md first build 2026-08-03 09:42:13 +01:00
mapserver.service first build 2026-08-03 09:42:13 +01:00
nginx-snippet.conf first build 2026-08-03 09:42:13 +01:00
postgres.container first build 2026-08-03 09:42:13 +01:00
README.md readme 2026-08-03 14:29:14 +01:00
rust_push.sh first build 2026-08-03 09:42:13 +01:00

rs_maps

A self-hosted replacement for the parts of Google Maps most people actually use: saving places you care about, and planning walking routes. It runs as a single Rust binary with one Postgres container behind it, and it's built for a handful of trusted users rather than the public.

It is not a Google Maps clone. There are no reviews, no photos, no live traffic and no turn-by-turn navigation, and there is no plan to add them.

What it does

Places. Click the map to drop a tagged marker with a name, kind, colour and description. Markers are private by default; flip one to shared and everyone else with an account sees it too.

Search. Type a place or address and get up to eight matches with their type and full address, so two branches of the same chain are distinguishable. Picking one drops a pin and shows what was found. "More details" pulls opening hours, phone, website and similar from OpenStreetMap where they exist — and says so plainly where they don't. Any hit can be saved as a place in one click.

Routes. Draw a walking route by clicking waypoints on the map. The line snaps to real paths and tracks, updating as you drag points around, with the routed distance and total ascent shown. Save it and the GPX lands in a shared folder both users can see. Saved routes can be reopened and edited later.

GPX files. Open a .gpx from your device to view it without uploading anything, or save it to the shared folder if you want it on the server. Files in the shared folder can be rendered on the map by anyone logged in.

Live location. Toggle it on to see where you are while the map is open. It's a dot on a map, not navigation guidance.

What it's built on

  • Rust binary (axum) serving the API and the whole frontend, which is embedded into the executable at compile time. One file to deploy.
  • Postgres in a rootless podman container, holding users, sessions and markers.
  • A plain directory for GPX files. No database involvement, no metadata.
  • Nothing else self-hosted. Map tiles come from OpenStreetMap, search from Nominatim, route snapping from brouter.de, and place details from Overpass — called directly from the browser, or proxied through one endpoint. Hosting any of them would mean gigabytes of data for a tool used a few times a week.

Requirements

  • A Linux server with systemd and podman
  • nginx (or equivalent) terminating TLS — not optional: browser geolocation refuses to run without HTTPS, and session cookies are Secure
  • A Rust toolchain to build, or a release binary
  • A domain

Quick start

git clone <this repo> && cd rs_maps
cargo build --release

Database and directories:

mkdir -p ~/.config/containers/systemd ~/gpx
cp deploy/postgres.container ~/.config/containers/systemd/

# Hex, not base64: / and + in a password break DATABASE_URL parsing.
openssl rand -hex 32 | tr -d '\n' | podman secret create rs_maps-db-password -

systemctl --user daemon-reload
systemctl --user start postgres

Copy .env.example to .env and fill in at least these:

Key Notes
DATABASE_URL postgres://rs_maps:PASSWORD@127.0.0.1:5432/rs_maps
REG_TOKEN openssl rand -hex 32. Anyone with this can register — it is the only access control
PUBLIC_URL Origin only: https://example.com. No trailing slash, no subpath
BASE_PATH /maps if served under a subpath, empty for the domain root
GPX_DIR Absolute path to a directory the service can write to
BIND_ADDR 127.0.0.1:8080 — loopback only, nginx is the only client

PUBLIC_URL must match the browser's Origin header exactly. A mismatch (www. versus bare, or a stray trailing slash) means every save fails with a 403 while pages load normally — a confusing thing to debug.

Run it:

./target/release/rs_maps

Migrations apply on startup. Point nginx at it using deploy/nginx-snippet.conf — note the client_max_body_size 25m on the upload path and the redirect for the bare subpath. Then open the site, register with your REG_TOKEN, and you're in. The first account isn't special: there are no roles and no admin UI.

For the service unit, systemd hardening and the release/upgrade scripts, see deploy/. DESIGN.md covers why the architecture is the way it is.

Passwords and email

Email is optional and used only for password resets. Give a bad address and the only thing you lose is your own ability to reset — nothing else depends on it. With SMTP_HOST empty, reset links are written to the log rather than sent, which is enough to test the flow without an email provider.

Changing a password affects future logins only. Sessions already active on other devices stay valid; clearing those means deleting the session rows directly. That's a deliberate trade for a two-person deployment.

Being a good citizen

Tiles, search, routing and place details all come from volunteer-run infrastructure. The app is built to stay inside their usage policies: search runs on submit rather than as you type, route previews are debounced, and place details are fetched only when asked for. If you fork this and put it in front of a lot of users, read those policies before scaling up.

Tests

cargo test

No database needed — the tests cover base-path handling, bbox parsing, GPX filename safety, waypoint embedding, coordinate ordering and the rate limiter.