2026-08-03 09:42:13 +01:00
|
|
|
<!-- README.md -->
|
|
|
|
|
|
2026-08-03 14:29:14 +01:00
|
|
|
# rs_maps
|
2026-08-03 09:42:13 +01:00
|
|
|
|
2026-08-03 14:29:14 +01:00
|
|
|
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.
|
2026-08-03 09:42:13 +01:00
|
|
|
|
2026-08-03 14:29:14 +01:00
|
|
|
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.
|
2026-08-03 09:42:13 +01:00
|
|
|
|
2026-08-03 14:29:14 +01:00
|
|
|
## What it does
|
2026-08-03 09:42:13 +01:00
|
|
|
|
2026-08-03 14:29:14 +01:00
|
|
|
**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.
|
2026-08-03 09:42:13 +01:00
|
|
|
|
2026-08-03 14:29:14 +01:00
|
|
|
**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.
|
2026-08-03 09:42:13 +01:00
|
|
|
|
2026-08-03 14:29:14 +01:00
|
|
|
**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.
|
2026-08-03 09:42:13 +01:00
|
|
|
|
2026-08-03 14:29:14 +01:00
|
|
|
**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.
|
2026-08-03 09:42:13 +01:00
|
|
|
|
2026-08-03 14:29:14 +01:00
|
|
|
**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.
|
2026-08-03 09:42:13 +01:00
|
|
|
|
2026-08-03 14:29:14 +01:00
|
|
|
## 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
|
2026-08-03 09:42:13 +01:00
|
|
|
|
2026-08-03 14:29:14 +01:00
|
|
|
- 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
|
2026-08-03 09:42:13 +01:00
|
|
|
|
2026-08-03 14:29:14 +01:00
|
|
|
## Quick start
|
2026-08-03 09:42:13 +01:00
|
|
|
|
|
|
|
|
```bash
|
2026-08-03 14:29:14 +01:00
|
|
|
git clone <this repo> && cd rs_maps
|
|
|
|
|
cargo build --release
|
2026-08-03 09:42:13 +01:00
|
|
|
```
|
|
|
|
|
|
2026-08-03 14:29:14 +01:00
|
|
|
Database and directories:
|
2026-08-03 09:42:13 +01:00
|
|
|
|
2026-08-03 14:29:14 +01:00
|
|
|
```bash
|
|
|
|
|
mkdir -p ~/.config/containers/systemd ~/gpx
|
|
|
|
|
cp deploy/postgres.container ~/.config/containers/systemd/
|
2026-08-03 09:42:13 +01:00
|
|
|
|
2026-08-03 14:29:14 +01:00
|
|
|
# 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 -
|
2026-08-03 09:42:13 +01:00
|
|
|
|
|
|
|
|
systemctl --user daemon-reload
|
2026-08-03 14:29:14 +01:00
|
|
|
systemctl --user start postgres
|
2026-08-03 09:42:13 +01:00
|
|
|
```
|
|
|
|
|
|
2026-08-03 14:29:14 +01:00
|
|
|
Copy `.env.example` to `.env` and fill in at least these:
|
2026-08-03 09:42:13 +01:00
|
|
|
|
2026-08-03 14:29:14 +01:00
|
|
|
| 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 |
|
2026-08-03 09:42:13 +01:00
|
|
|
|
2026-08-03 14:29:14 +01:00
|
|
|
`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.
|
2026-08-03 09:42:13 +01:00
|
|
|
|
2026-08-03 14:29:14 +01:00
|
|
|
Run it:
|
2026-08-03 09:42:13 +01:00
|
|
|
|
2026-08-03 14:29:14 +01:00
|
|
|
```bash
|
|
|
|
|
./target/release/rs_maps
|
|
|
|
|
```
|
2026-08-03 09:42:13 +01:00
|
|
|
|
2026-08-03 14:29:14 +01:00
|
|
|
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.
|
2026-08-03 09:42:13 +01:00
|
|
|
|
2026-08-03 14:29:14 +01:00
|
|
|
For the service unit, systemd hardening and the release/upgrade scripts, see
|
|
|
|
|
`deploy/`. `DESIGN.md` covers why the architecture is the way it is.
|
2026-08-03 09:42:13 +01:00
|
|
|
|
2026-08-03 14:29:14 +01:00
|
|
|
## 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.
|
2026-08-03 09:42:13 +01:00
|
|
|
|
2026-08-03 14:29:14 +01:00
|
|
|
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.
|
2026-08-03 09:42:13 +01:00
|
|
|
|
2026-08-03 14:29:14 +01:00
|
|
|
## Being a good citizen
|
2026-08-03 09:42:13 +01:00
|
|
|
|
2026-08-03 14:29:14 +01:00
|
|
|
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.
|
2026-08-03 09:42:13 +01:00
|
|
|
|
2026-08-03 14:29:14 +01:00
|
|
|
## Tests
|
2026-08-03 09:42:13 +01:00
|
|
|
|
2026-08-03 14:29:14 +01:00
|
|
|
```bash
|
|
|
|
|
cargo test
|
|
|
|
|
```
|
2026-08-03 09:42:13 +01:00
|
|
|
|
2026-08-03 14:29:14 +01:00
|
|
|
No database needed — the tests cover base-path handling, bbox parsing, GPX
|
|
|
|
|
filename safety, waypoint embedding, coordinate ordering and the rate limiter.
|