Files
TREK/README.md
T
jubnl d7c4324afc chore(capacitor): base files for capacitor (#2020)
* chore(capacitor): base files for capacitor

* chore(capacitor): base plugins

* feat(auth): hand a browser sign-in over to the native app

The native apps show the server's own UI in a WebView, where neither SSO nor
passkeys can run. They sign in through the system browser instead and need a
way to carry that session back.

The app keeps a random verifier and sends only its sha256 into the browser.
Once signed in there, the browser trades that challenge for a one-time code
(cookie session only, so an API or MCP token cannot mint itself a browser
session), and the WebView redeems the code together with the verifier. The
code rides through a URL scheme any app could claim, so on its own it is
worthless; it is single use and good for a minute.

Also: /api/health/features reports nativeApp so the apps can refuse servers
that predate this, and hashed files under assets/ are served immutable,
which the iOS app needs to start offline without a service worker.

* feat(native): the apps load the server's own UI

The apps ship only an address screen now. Once a server is picked, the
native side points the WebView at it: TrekBridgeViewController on iOS and a
config copy with server.url on Android. The UI then comes from that server,
in the version it runs and on its own origin, so the session cookie,
uploads, /ws and plugins work exactly as in a browser and no app release
has to keep pace with the servers out there.

The address screen (native-shell/, built into native-dist/) probes
/api/health/features through native HTTP, tries https before http when no
scheme was typed, and refuses servers without the nativeApp flag. It also
serves as the error page when the remembered server does not load: retry,
change server, or on iOS the stored copy of the server's start page.

TrekShell is the app's own plugin for all of that: remember, change and
reload the server, sign in through ASWebAuthenticationSession or a Custom
Tab, and on iOS keep and open the offline copy of the start page, because
WKWebView runs no service worker for a domain the app does not list.

Plugins the frame does not use are gone (push, camera, geolocation,
calendar, file transfer, in-app browser and more): the WebView covers the
camera and location itself, and push needs a relay before it is worth
shipping. With them go the push entitlement, the calendar usage strings
and the Firebase bits. The iOS scheme falls back to capacitor://, since
Capacitor drops an iosScheme of https, and the arm64 capability replaces
armv7.

* feat(native): the UI knows when it runs inside the app

Everything the server's UI does differently inside the iOS and Android app
lives under src/native and is inert in a browser.

Sign-in: SSO and the passkey button go through the system browser
(signInThroughBrowser), because identity providers refuse embedded
browsers and WebAuthn needs a domain association no build can have for
every server. The browser end is the new /native-handoff page: it signs
the user in if needed and hands the session over only after an explicit
tap, so a link carrying somebody else's challenge cannot pass a session
on silently. The verifier is hashed with @noble/hashes because
crypto.subtle is missing on the plain-http servers many home networks
use. An OIDC-only install no longer bounces to the IdP inside the
WebView, and passkey management is hidden in the app.

Downloads: the WebView cannot save a blob, so downloadBlob, downloadFile
and openFile write to the app cache and open the share sheet. The eight
places that built their own download anchor now use downloadBlob, which
they should have anyway.

The rest: "Change server" on the login page and in the account settings,
the splash hides after the first frame, Android's back button walks
history before it leaves the app, the status bar follows the scheme, and
on iOS the start page is refreshed and every chunk of the build (listed
in the new asset-manifest.json) is pulled into WebKit's cache so the app
can start without network.

* ci(native): build the Android and iOS shells on every change to them

Compiles the Swift side on a macOS runner so nobody needs a Mac to know it
still builds, and leaves a debug APK on every run to install on a phone.
Unsigned: signing and store uploads need secrets fork PRs do not get.

* feat(native): offline vector maps in the iOS app

In a browser the service worker answers MapLibre from the gl-map-offline
cache that glPrefetcher fills for trips marked for offline. The iOS app has
no service worker, so nothing read that cache there and the map went blank
without network.

A trek-offline:// protocol takes the service worker's place. Inside the iOS
app the MapLibre style URL is rewritten to it, the handler rewrites every
https URL inside the style and its TileJSON the same way, and then serves
documents network first and tiles, glyphs and sprites cache first. Outside
the iOS app nothing changes. The default OpenFreeMap basemap is covered;
Mapbox GL has no protocol hook and stays online-only in the app, as does a
custom raster template, whose opaque responses only a service worker can
store.

* fix(native): keep the iOS offline copy inside the app

loadSimulatedRequest went to the server URL without its trailing slash, and
Capacitor keeps a navigation in the WebView only when it starts with the
bridge's serverURL, slash included. The offline start page would have
opened in Safari instead. Also covers the platform helpers and the
hand-off API module.

* feat(native): the small things that give a WebView away

- A short haptic tap on success, warning and error toasts, which is where
  every save and delete already reports. Info toasts stay silent.
- No "‹ › Done" strip above the iOS keyboard.
- No Safari preview when a link is long-pressed.
- The swipe from the left edge goes back on iOS, through the same history
  Android's back button walks.

* test(mobile): the backup-code download revokes its URL after the shared delay

The mobile twin of the AccountTab test still expected the object URL to be
revoked in the same tick. Since both screens download through downloadBlob,
the revoke happens after its 100 ms grace period.

* fix(native): remember the login by default inside the app

With the switch off the app got a session cookie, which dies with the app
process, so every time the phone unloaded TREK the user had to sign in
again. Found while running the Android build in the emulator.

* fix(native): no overscroll stretch on Android

The UI asks for no overscroll effect with overscroll-behavior: none on html,
and Chrome honours it. The Android WebView draws the stretch on its root
scroller natively, though, and it scales the whole view, so the top bar and
the bottom dock were pulled along with the page. Found in the emulator.

* fix(native): Android shows "server not reachable" instead of a blank page

Capacitor injects its bridge on Android only into the server's origin once
server.url is set, so the local error page it switches to when the server
does not load had no bridge. It could neither retry nor change the server
and stayed blank.

TrekWebViewClient now catches a failed page load (a network error, or a 5xx
from a proxy while TREK restarts) and the app starts over on the bundled
address screen, where the bridge is present and the screen offers exactly
those two choices. The server config no longer carries errorPath.

Two traps on the way, both found in the emulator: a restart during launch
has to be a fresh task, since recreate() leaves the system splash lying over
the new page; and a restart shows no launch splash at all, because the
splash plugin cannot take it down again when the page hides it early.

* feat(native): TREK's own app icon and launch screen, named just TREK

The shells still carried Capacitor's placeholder icon and splash, the blue
X on a grid. They now show the white TREK mark on a dark, slightly glassy
tile: a soft light from the top left and a sheen over the upper half, in
the app's launch colour #09090b. Android 12+ draws its launch screen on the
same colour instead of light grey.

scripts/generate-app-icons.mjs renders the sources into assets/ from
public/icons/icon-white.svg, and `npm run icons:app` turns them into every
size both platforms ask for through @capacitor/assets. The Android
foreground mark is sized for the adaptive icon's round mask.

The home screen label is TREK: the old "TREK - Plan your trips!" was cut
off under the icon, and the longer title belongs to the store listings.

* fix(native): new lint warnings and the MapLibre protocol type after the dev merge

Awaits that sat in loops in the native-auth tests run together, the hand-off hook marks its navigations as ignored, the native tests drop their non-null assertions, and the offline protocol answers with the data type MapLibre 6.15 expects.

* fix(native): strict types in the address screen, Prettier on the native-auth e2e suite, file-local exports

The address screen's element helper returns the plain element instead of Object.assign's intersection, which strict mode could not represent; the haptics test reads an optional argument as optional; three exports only their own file used are local now.

* fix(native): Prettier on the native client files, rate-limit e2e requests in sequence

Formats the files the client formatting gate listed, and NATIVE-E2E-005 sends its twenty exchange attempts one after another: all at once they reset the test server's connections.

* fix(native): hide passkeys in the app through usePasskeys

The flag in useAccountSettings grew the identical destructuring in both account views past the duplication threshold; the shared passkeys hook already decides when its card is hidden, so the app check lives there now.

* feat(native): everything the App Store and Google Play submissions need

- iOS: microphone usage text for "Take Video", no-export-compliance flag,
  ExportOptions.plist for App Store Connect uploads, the unused push
  forwarders gone; CI builds with Xcode 26 and lints the property lists.
- Android: version and signing from Gradle properties (unsigned without
  them), R8 with keep rules for TREK's and Capacitor's reflective classes,
  no Auto Backup of the session cookie, no external file provider path,
  template and Firebase leftovers removed, keystores and service accounts
  ignored, full-size adaptive icon layers with a monochrome layer; CI also
  builds the release APK.
- native-release.yml: signed bundle to a Play testing track and archive to
  TestFlight, by hand or on a v* tag, behind the store environment.
- Version handshake: the server reports nativeApi 1, the app refuses a
  server below its minimum and tolerates an app older than the offline copy.
- Address screen: privacy policy link, "Try the demo" and "What is a TREK
  server?"; the privacy link also in the app's server settings. Three new
  native strings in all 27 locales.
- Donation links and the notices asking for one are hidden in the app.
- Store listings for "TREK Travel Planner" in client/fastlane: texts, review
  notes with placeholders, privacy and account deletion page drafts, the
  feature graphic and icon, a length and image check in CI, and a Playwright
  store project with the scripts that turn its captures into screenshots.
- Docs: wiki/Native-Apps.md, README no longer says there is no store app.

* fix(native): read a notice button's href without a weak type

An action button carries no href, so a parameter typed as only an optional href refused the notice DTO; the button type now names its kind as well.

* feat(native): a welcoming onboarding for the address screen

The app's own screen now opens on a welcome: the TREK mark, a line about what
TREK is, and two clear paths — connect the TREK you already open in the browser,
or get TREK Cloud. Both lead to polished screens: the address field with live
validation, a shake and haptic on a bad address and a success tick before the
server takes over; and a TREK Cloud intro with what it offers and a way in.
Screens slide and stagger in, buttons give a tap, and it all respects the safe
areas, light and dark, and reduced motion. Self-hosters are unaffected: cloud is
its own path, never in the way. New native strings are added across all locales
(English placeholders outside en/de, to translate).

* feat(native): a plan picker for TREK Cloud, with live prices

The Get TREK Cloud path now leads to a plan picker that reads the live plans
from the site (/api/plans), so the prices and quotas always match the website.
Monthly and yearly in a segmented toggle, the year marked with its saving, each
plan a card with its price and what it includes. Subscribe is wired to finish on
the web order for now; it becomes an Apple/Google in-app purchase once the store
products exist. New strings across all locales (en and de translated).

* feat(native): light status bar and a Go key on the address screen

The address screen sits on the near-black surface, so set the status bar to draw
its clock and icons in light while it is up; the server's own UI takes over the
style once it loads. The address field's on-screen return key now reads Go. Both
fail quietly where there is no native status bar (web, the simulator).

* i18n(native): translate the onboarding into es, fr, it, nl and pl

The welcome, TREK Cloud and plan-picker strings were English placeholders in
every locale but en and de. The five largest remaining European languages are
now translated in full; the others still carry the English text and are a
follow-up for native speakers.

* feat(native): the in-app TREK Cloud subscription flow

After picking a plan, the app now carries the whole purchase itself when the build
can take one: choose your address (checked for free as you type, against the same
endpoint the website uses), your email, and subscribe. The receipt goes to
/api/iap/redeem, and a Setting up your TREK screen waits on the new address and
loads it the moment it answers.

The purchase sits behind an IapClient seam (src/native/iap.ts): a fake drives the
whole flow in development and preview, the native client reports itself
unavailable until the store plugin and products exist, and until then the plan
picker still finishes on the web, so nothing half-wired faces a customer. New
claim and provisioning strings across all locales (en, de and the five largest
European languages translated).

* i18n(native): translate the app into the remaining 20 languages

The onboarding, plan picker, claim and provisioning strings were English
placeholders outside en, de and the five largest European languages. They are
now translated in full across the other 20 locales (ar, az, br, ca, cs, et, gr,
hu, id, ja, ko, ru, sk, sv, th, tr, uk, vi, zh, zh-TW), placeholders and brand
kept. The app now speaks every language TREK does.

* fix(i18n): add the Hebrew native strings, and correct the LoginPage size baseline

The he locale had every domain but native.ts, which broke i18n parity, the shared
coverage specs and the client parity test once the native strings existed. Add
he/native.ts with all native keys translated. Also correct the LoginPage size
baseline to its current line count.

* i18n(native): excuse the TREK Cloud brand, mark Email same-as-en, exempt the quota counts

The strict i18n checks, which the feature-branch CI does not run, catch three
things now that the native strings exist in every locale: TREK Cloud read as an
untranslated English phrase (it is a product name, like Vacay and Atlas, so add
it to the brand list); Email left identical in Italian, Indonesian and
Vietnamese, where it is the local word too (mark same-as-en) and in Greek (give
it a Greek term); and the GB and AI-document quota strings carrying {n} without
being plural groups (both are figures that never show a singular, like the other
unit-symbol counts in NOT_PLURAL).

* fix(native): typecheck, lint and knip on the native code; wire he's native bundle

The full suite, which the feature-branch CI does not run, catches four things the
native work still had: a non-narrowed union in the claim flow (tsc), a helper
named useFake that the react-hooks rule read as a hook (eslint), two iap types
exported but used only inside the file (knip), and the Hebrew native strings
present as a file but never spread into he's merged bundle (the aggregated key
parity test). All four fixed.

* fix(native): a flat redeem result, so the claim flow typechecks

The discriminated union did not narrow under the native-shell tsconfig, so the
failure code read as absent on the whole type. Use a flat result with optional
instanceUrl and code instead: both are always present on the type, no narrowing
needed, and the ok plus instanceUrl guard still routes success from refusal.

* style: prettier-format the native and i18n changes

The feature-branch CI does not run the formatting check, so the conflict-resolved
files (AboutTab, useAccountSettings, useTripExport), the native shell and iap
module, and the i18n files touched by the translation scripts were never run
through Prettier. Format them all; no behaviour changes.

* chore(native): re-trigger CI on the formatted tree

---------

Co-authored-by: Maurice <mauriceboe@icloud.com>
2026-11-05 21:09:04 +01:00

24 KiB

TREK

Every part of your trip. One app you own.

TREK has grown from a travel planner into a whole ecosystem for trips: plan together in real time on a live map, build hikes, tours and road trips, keep bookings and shared costs in order, pack, write the journal, lay out the photo book and hand the busy work to the built-in AI. Self-hosted, on the web, on your phone and offline.


Demo   Docker   Sonar Quality Gate   GitHub Actions Workflow Status   Discord   Roadmap   Ko-fi   BMAC   License   Latest Release   Docker Pulls   Stars

PUBLIC SERVICES PROUDLY SPONSORED BY
dataforest cloud


TREK, a tour of the app

What you get

Plan, track, share
See all features

Most of what follows is an addon an admin switches on or off. Lists, Costs, Documents, Collab, Tours, Vacay and Atlas ship on; Journey, Collections, MCP, the AI assistant, AI Parsing, Road trip, Dawarich and AirTrail ship off and are marked below.

🧭 Planning

  • Day plans: drag places, notes and bookings between days with undo, or drop a map marker straight onto a day
  • Maps: Leaflet, Mapbox GL or MapLibre GL (OpenFreeMap, no token) with clustering, photo markers, route lines and a whole-trip route overview
  • Places: search the TREK Places index and OpenStreetMap with no key, or Google Places and Amap with one. Descriptions, hours and photos from Wikipedia, Wikidata and Wikimedia Commons, plus a rating and a custom image per place
  • Explore: nearby places by category for the map view, from the TREK Places index with Overpass as fallback
  • Routes: auto-sort a day (nearest neighbour then 2-opt, locked stops and hotels stay put), a travel mode per leg over OSRM or Valhalla, then open the day in Google Maps or CoMaps and a place in seven navigation apps
  • Public transport: door-to-door itineraries over Transitous, or Google where Transitous has no data
  • Import and export: Google Maps and Naver Maps lists, Google Maps directions links, GPX, KML and KMZ in; GPX and an ICS feed per trip or across all trips out
  • Weather: 16-day forecast from Open-Meteo, no key, and the archive for dates beyond it
  • Trip dates: move a trip and its days re-date themselves, with the bookings along or re-anchored. Trips also copy and archive
  • Road trip (off by default): drives with stops, day windows and arrival times that recompute, route alternatives, hazards and EV charging with range

🥾 Tours

  • Seven tour types: hike, city walk, run, bike (touring, road, gravel, MTB), paddle (kayak, canoe, SUP), horse ride, and ski and winter (alpine, cross-country, touring, snowshoe, winter hike)
  • Routing: legs routed over Valhalla with shaping points, hand-drawn legs, route variants, round hikes of a wished length and trip places as stops, all with undo
  • Hikes: routed within a maximum SAC grade (T1 to T6), the trail grades along the way, and via ferratas on the map
  • Terrain: elevation profile, ascent and descent, and a 3D terrain map with grade colouring and a flyover, no token needed. Topo, hiking, cycling and satellite bases with sea marks and ski pistes as overlays
  • Multi-day tours: one route cut into stages and spread across the trip's days
  • Along the way: viewpoints, huts, water, food and more within a corridor of the route, inserted as a stop with the detour it adds
  • Ski, paddle and city walks: a piste router by difficulty and lift type; a water router with portages, locks and a wind forecast; city walks with stays, opening hours and transit between stops
  • Plan against reality: link a recorded GPX to a planned day and compare distance, ascent and time. Import trails from a wanderer instance, save tours to a collection and take them into another trip

🧳 Bookings and money

  • Reservations: 18 booking types with status, confirmation code, travellers and attached files
  • Flights and trains: several legs with stopovers and endpoint timezones, against 4,045 bundled airports so local times resolve without a key
  • Accommodation: a stay spans a range of nights with a check-in window
  • Booking import: EML, PDF, PKPass, HTML, TXT and photos of confirmations, through KItinerary or an AI model, with a review step before anything is saved. Prices become linked costs
  • AirTrail (off by default): import flights from a self-hosted AirTrail instance and keep them in sync
  • Costs: split in integer cents with equal or custom shares, several payers, settle-up suggestions, a per-person summary, a settlement log and CSV export. A receipt photo can fill in an expense
  • Currencies: one per expense with the rate frozen at entry, from Frankfurter with no key
  • Packing lists and to-dos: categories, templates, assignees and visibility tiers; bags with weights as an admin switch; to-dos with due dates, priorities and reminders
  • Files: attach to a trip, place, day or booking, 50 MB each and 500 MB for video, with trash and restore
  • PDF export: cover, place photos, day notes, bookings and costs, with an optional page break per day

👥 Collaboration

  • Real-time sync (WebSocket): edits land live for everyone who has the trip open
  • Members: add by email or username, hand over ownership, or add guests who have no login at all
  • Permissions: an admin maps each of 17 trip actions to admin, trip owner, trip member or everybody
  • Invites: one reusable link per trip with an optional expiry, and admin signup invites that drop a new account straight into a trip
  • Public share: a read-only trip page without an account, with switches for map, bookings, packing, costs and chat
  • Collab: group chat with replies, reactions and link previews, shared notes, links, polls and a What's Next list, each one its own switch
  • Notifications: an inbox grouped by day with answers right from the notification, and a per-user matrix of events against in-app, email, webhook, ntfy, Web Push and plugin channels

📔 Journey and TREK Studio

  • Journey (off by default): dated entries with story, mood, weather and tags, photos and video from uploads, Immich or Synology Photos, map views, co-authors, drafts and a public share link
  • Linked journal: a trip can show one journal as its own, with the members picked on the trip as viewers or editors
  • Recorded days (with Dawarich): keep a day's recorded route and turn its stays into entries
  • TREK Studio: turn a journal into a printable photo book. 34 print-licensed typefaces, 17 themes, 108 designed pages in twelve styles, 160 recolourable stickers, text effects, layers and a Travel tab with route maps, country stamps and trip figures
  • Auto layout: lays the whole book on the designed pages in the style of its theme, every photo in a frame, or rebuilds one spread
  • Print and share: PDF export as pages or spreads with crop marks, several people editing at once, and design files to move a spread into another book

🗺️ Atlas, Vacay and Collections

  • Atlas: countries and regions visited on geoBoundaries outlines, a bucket list, travel stats, and a tour activities tab with planned against recorded totals
  • Vacay: leave calendar with half days, public holidays from date.nager.at, school holidays for 16 European countries, company holidays, carry-over between years, and joint or read-only sharing
  • Collections (off by default): a place library outside any trip with lists, labels, ratings, a status per place, cover images, GPX and file export, copy into a trip, and sharing by invite
  • Dawarich (off by default): link a Dawarich instance for recorded tracks, suggested stays and countries for the Atlas

🤖 AI assistant and MCP

  • AI assistant (off by default): a chat that plans with you in a trip, across all your trips, in a journal and in Studio, and works with tours, costs, bookings, packing, Vacay and the Atlas
  • Your own model: an API key for Anthropic, OpenAI, Gemini or OpenRouter, a ChatGPT plan, or any OpenAI-compatible endpoint, set for the instance or per user
  • You stay in charge: reads run at once, every change waits as a card to apply or discard and can be undone for 15 minutes. Nothing leaves before you agree to the provider, and a file's text only with your consent
  • Attachments: drop a booking confirmation, a GPX file or a photo into the chat, and the assistant offers what to do with it
  • MCP server (off by default): OAuth 2.1 with mandatory PKCE and dynamic client registration, 410 tools, 30 resources and 4 prompts, 35 scopes in 17 groups ticked on the consent screen, and every call in the audit log
  • Addon-aware: twelve addon gates decide which tools a session sees, and flipping one re-registers live sessions

🧩 Plugins

  • Third-party plugins: install from the TREK registry or sideload a zip, with their own pages under /plugins/<id>
  • Sandboxed: one child process per plugin, 66 grantable permissions, an admin-edited outbound host allowlist, memory and call caps
  • Trust: registry downloads are pinned by sha256 and checked against the author's minisign key, a sideloaded zip is marked unverified, and TREK_PLUGINS_ENABLED=false turns the whole system off
  • Extension points: map layers, place details, day schedules, PDF sections, Atlas layers, journal rows, trip warnings, search and photo providers, route profiles, notification channels and MCP tools
  • SDK: trek-plugin-sdk with a CLI to create, validate, run against real data, sign and publish

📱 Mobile and offline

  • Apps: TREK Travel Planner for iPhone, iPad and Android connects to any TREK server, and the browser installs TREK as a standalone app with a themed status bar too
  • Phone layouts: their own screens for every part of the app, with a bottom bar and dashboard widgets the user orders
  • Offline reads: the app shell is precached and trips, places, tours, files and nearby places live in a per-user IndexedDB store, so a trip opens with no network
  • Offline writes: changes queue and replay with an X-Idempotency-Key, and a stale edit waits for a keep-mine or keep-theirs choice
  • Offline maps: raster and vector tiles of a trip downloaded ahead, and a switch that takes the whole app offline on purpose

⚙️ Admin, accounts and security

  • Sign-in: password, OIDC and passkeys each switch on and off on their own, OIDC_ONLY leaves SSO as the only way in, and an OIDC claim can grant admin
  • SSO and 2FA: one OIDC provider by discovery with PKCE (Authentik, Keycloak, Google and the like), TOTP with backup codes that an admin can require, and passkeys that count as the second factor
  • Sessions: every sign-in is a session you can see and end, one by one or all others at once
  • Hardening: rate limits on sign-in, reset and 2FA, a password policy, secrets encrypted at rest and masked on read, and an SSRF guard on every URL you configure
  • Admin panel: users and the permission matrix, defaults for new users, categories and templates, addons, plugins, storage, notifications, MCP tokens, backups, audit log and releases
  • Backups and storage: scheduled backups with retention that restore their own secrets, and local or S3-compatible storage per content category with replicas
  • Appearance: light, dark or follow the OS, seven colour schemes plus a custom accent, transparency, density, reduced motion and text size
  • 28 languages: en, de, es, fr, it, nl, hu, ru, zh, zh-TW, pl, cs, ar (right to left), he (right to left), br, id, tr, ja, ko, uk, gr, sv, vi, ca, az, et, sk, th
  • Help center: a ? in the top bar with 381 step-by-step guides with screenshots, searched together with the wiki that ships in the image

Atlas · dashboard · trip planner Mobile PWA · dashboard, day plan, map, costs

Powered by dataforest cloud


dataforest cloud

Every TREK installation in the world searches through servers that dataforest cloud sponsors.

The place index with over 88 million places, the address search in every TREK language and the planet-wide routing all run on dataforest cloud in Frankfurt, Germany, free of charge for everyone who self-hosts TREK. Without this sponsorship, these services would not exist in this form.

Thank you, dataforest, for backing open source this generously. 💚


Visit dataforest cloud

Looking for a server for your own TREK? dataforest cloud is where TREK's own services live.


AI usage

We use LLM-assisted coding tools across parts of this codebase. Nothing ships that a maintainer has not read and understood: every change goes through a pull request, is reviewed and tested, and has a human who can answer for it. "The AI wrote that" is not an answer any of us would accept from ourselves.

See How we use AI in TREK for the details.


Get started in 30 seconds

ENCRYPTION_KEY=$(openssl rand -hex 32) docker run -d --name trek -p 3000:3000 \
  -e ENCRYPTION_KEY=$ENCRYPTION_KEY \
  -e COOKIE_SECURE=false \
  -v ./data:/app/data -v ./uploads:/app/uploads mauriceboe/trek

Open http://localhost:3000, or http://<server-ip>:3000 from another machine. On first boot TREK seeds an admin account — if you set ADMIN_EMAIL/ADMIN_PASSWORD those are used, otherwise the credentials are printed to the container log (docker logs trek).

COOKIE_SECURE=false is only for trying TREK over plain HTTP: without it the browser drops the session cookie and the login does not stick. Once TREK sits behind HTTPS (see Reverse Proxy), remove it.

Tip

No server yet? TREK's own public services run on dataforest cloud, our infrastructure sponsor. A small seed is plenty for a TREK instance and is ready within a minute.


Tech stack

Node.js NestJS SQLite React Vite TypeScript Tailwind Leaflet Docker

Real-time sync via WebSocket (ws). Backend on NestJS 11. State with Zustand. Auth via JWT + OAuth 2.1 + OIDC + Passkeys (WebAuthn) + TOTP MFA. Weather via Open-Meteo (no key required). Maps with Leaflet and Mapbox GL.


Docker Compose (production)

The repository ships a ready-to-use docker-compose.yml with secure defaults and every option documented inline. Download it, then:

docker compose up -d

See Install with Docker Compose for the full walkthrough.


Helm (Kubernetes)

helm repo add trek https://chart.liketrek.com
helm repo update
helm install trek trek/trek

See charts/README.md for values.


Install as App (PWA)

TREK works as a Progressive Web App, and the TREK Travel Planner app for iPhone, iPad and Android connects to the same server (Native Apps). To install the web app instead:

  1. Open TREK in the browser (HTTPS required)
  2. iOS: Share ▸ Add to Home Screen
  3. Android: Menu ▸ Install app (or Add to Home Screen)

TREK then launches fullscreen with its own icon, just like a native app.


Updating

See Updating — Docker Compose, Docker run, Helm, Portainer, Unraid and Proxmox, plus the encryption-key note.


Reverse Proxy

For production, put TREK behind a TLS-terminating reverse proxy. TREK uses WebSockets for real-time sync, so the proxy must support WebSocket upgrades on /ws.

If you use the MCP addon, the proxy must also pass the Mcp-Session-Id header through in both directions on /mcp — Nginx and Caddy do this by default, but a proxy that strips it makes every tool call open a new session instead of reusing one. See the Reverse Proxy wiki page for details.

Nginx
server {
    listen 80;
    server_name trek.yourdomain.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;
    server_name trek.yourdomain.com;

    ssl_certificate     /etc/ssl/fullchain.pem;
    ssl_certificate_key /etc/ssl/privkey.pem;

    # 500 MB covers backup-restore uploads (capped at 500 MB server-side).
    client_max_body_size 500m;

    location / {
        proxy_pass http://localhost:3000;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    location /ws {
        proxy_pass http://localhost:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_read_timeout 86400;
    }

    # Only needed if you use the MCP addon. Responses are Server-Sent Events,
    # so buffering must be off or tool results arrive late.
    location /mcp {
        proxy_pass http://localhost:3000;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_buffering off;
        proxy_read_timeout 3600s;
    }
}
Caddy
trek.yourdomain.com {
    reverse_proxy localhost:3000
}

Caddy handles TLS and WebSockets automatically.


Environment variables

Every variable, its default and what it does: see Environment Variables.

Data sources

The Atlas map's country and sub-national (province/county) boundaries come from geoBoundaries (Runfola et al., 2020), licensed CC BY 4.0. See NOTICE.md for full third-party attributions.


License

TREK is AGPL v3. Self-host freely for personal or internal company use. If you modify and offer TREK as a network service to third parties, your modifications must be open-sourced under the same licence.