<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Travel Planner on Producthunt daily</title>
        <link>https://producthunt.programnotes.cn/en/tags/travel-planner/</link>
        <description>Recent content in Travel Planner on Producthunt daily</description>
        <generator>Hugo -- gohugo.io</generator>
        <language>en</language>
        <lastBuildDate>Fri, 26 Jun 2026 18:35:07 +0800</lastBuildDate><atom:link href="https://producthunt.programnotes.cn/en/tags/travel-planner/index.xml" rel="self" type="application/rss+xml" /><item>
        <title>TREK</title>
        <link>https://producthunt.programnotes.cn/en/p/trek/</link>
        <pubDate>Fri, 26 Jun 2026 18:35:07 +0800</pubDate>
        
        <guid>https://producthunt.programnotes.cn/en/p/trek/</guid>
        <description>&lt;img src="https://images.unsplash.com/photo-1599498768887-b2472d987494?ixid=M3w0NjAwMjJ8MHwxfHJhbmRvbXx8fHx8fHx8fDE3ODI0Njk5Nzh8&amp;ixlib=rb-4.1.0" alt="Featured image of post TREK" /&gt;&lt;h1 id=&#34;mauriceboetrek&#34;&gt;&lt;a class=&#34;link&#34; href=&#34;https://github.com/mauriceboe/TREK&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;mauriceboe/TREK&lt;/a&gt;
&lt;/h1&gt;&lt;div align=&#34;center&#34;&gt;
&lt;picture&gt;
  &lt;source media=&#34;(prefers-color-scheme: dark)&#34; srcset=&#34;docs/logo-trek-light.gif&#34; /&gt;
  &lt;source media=&#34;(prefers-color-scheme: light)&#34; srcset=&#34;docs/logo-trek-dark.gif&#34; /&gt;
  &lt;img src=&#34;docs/logo-trek-dark.gif&#34; alt=&#34;TREK&#34; height=&#34;96&#34; /&gt;
&lt;/picture&gt;
&lt;br /&gt;
&lt;picture&gt;
  &lt;source media=&#34;(prefers-color-scheme: dark)&#34; srcset=&#34;docs/subtitle-light.png&#34; /&gt;
  &lt;source media=&#34;(prefers-color-scheme: light)&#34; srcset=&#34;docs/subtitle-dark.png&#34; /&gt;
  &lt;img src=&#34;docs/subtitle-dark.png&#34; alt=&#34;Your trips. Your plan. Your server.&#34; height=&#34;28&#34; /&gt;
&lt;/picture&gt;
&lt;p&gt;A self-hosted, real-time collaborative travel planner — with maps, budgets, packing lists, a journal, and AI built in.&lt;/p&gt;
&lt;br /&gt;
&lt;p&gt;&lt;a href=&#34;https://demo.liketrek.com&#34;&gt;&lt;img alt=&#34;Demo&#34; src=&#34;https://img.shields.io/badge/Demo-try-111827?style=for-the-badge&#34; /&gt;&lt;/a&gt;
 
&lt;a href=&#34;https://hub.docker.com/r/mauriceboe/trek&#34;&gt;&lt;img alt=&#34;Docker&#34; src=&#34;https://img.shields.io/badge/Docker-ready-2496ED?style=for-the-badge&#34; /&gt;&lt;/a&gt;
 
&lt;a href=&#34;https://discord.gg/NhZBDSd4qW&#34;&gt;&lt;img alt=&#34;Discord&#34; src=&#34;https://img.shields.io/badge/Discord-join-5865F2?style=for-the-badge&#34; /&gt;&lt;/a&gt;
 
&lt;a href=&#34;https://kanban.pakulat.org/shared/I4wxF6inOOMB0C6hH6kQm3efyNxFjwyI&#34;&gt;&lt;img alt=&#34;Roadmap&#34; src=&#34;https://img.shields.io/badge/Roadmap-view-0EA5E9?style=for-the-badge&#34; /&gt;&lt;/a&gt;
&lt;br /&gt;
&lt;a href=&#34;https://ko-fi.com/mauriceboe&#34;&gt;&lt;img alt=&#34;Ko-fi&#34; src=&#34;https://img.shields.io/badge/Ko--fi-support-FF5E5B?style=for-the-badge&#34; /&gt;&lt;/a&gt;
 
&lt;a href=&#34;https://www.buymeacoffee.com/mauriceboe&#34;&gt;&lt;img alt=&#34;BMAC&#34; src=&#34;https://img.shields.io/badge/BMAC-support-FFDD00?style=for-the-badge&#34; /&gt;&lt;/a&gt;
&lt;br /&gt;
&lt;a href=&#34;LICENSE&#34;&gt;&lt;img alt=&#34;License&#34; src=&#34;https://img.shields.io/badge/license-AGPL_v3-6B7280?style=flat-square&#34; /&gt;&lt;/a&gt;
&lt;a href=&#34;https://github.com/mauriceboe/TREK/releases&#34;&gt;&lt;img alt=&#34;Latest Release&#34; src=&#34;https://img.shields.io/github/v/release/mauriceboe/TREK?include_prereleases&amp;style=flat-square&amp;color=6B7280&#34; /&gt;&lt;/a&gt;
&lt;a href=&#34;https://hub.docker.com/r/mauriceboe/trek&#34;&gt;&lt;img alt=&#34;Docker Pulls&#34; src=&#34;https://img.shields.io/docker/pulls/mauriceboe/trek?style=flat-square&amp;color=6B7280&#34; /&gt;&lt;/a&gt;
&lt;a href=&#34;https://github.com/mauriceboe/TREK&#34;&gt;&lt;img alt=&#34;Stars&#34; src=&#34;https://img.shields.io/github/stars/mauriceboe/TREK?style=flat-square&amp;color=6B7280&#34; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;hr&gt;
&lt;div align=&#34;center&#34;&gt;
&lt;img src=&#34;https://github.com/mauriceboe/trek-media/releases/download/readme-assets/TREK1.gif&#34; alt=&#34;TREK — 60-second tour&#34; width=&#34;100%&#34; /&gt;
&lt;/div&gt;
&lt;br /&gt;
&lt;div align=&#34;center&#34;&gt;
  &lt;a href=&#34;docs/screenshots/dashboard.png&#34;&gt;&lt;img src=&#34;docs/screenshots/dashboard.png&#34; alt=&#34;Dashboard&#34; width=&#34;49%&#34; /&gt;&lt;/a&gt;
  &lt;a href=&#34;docs/screenshots/trip-planner.png&#34;&gt;&lt;img src=&#34;docs/screenshots/trip-planner.png&#34; alt=&#34;Trip planner with 3D map&#34; width=&#34;49%&#34; /&gt;&lt;/a&gt;
  &lt;a href=&#34;docs/screenshots/journey.png&#34;&gt;&lt;img src=&#34;docs/screenshots/journey.png&#34; alt=&#34;Journey journal&#34; width=&#34;49%&#34; /&gt;&lt;/a&gt;
  &lt;a href=&#34;docs/screenshots/budget.png&#34;&gt;&lt;img src=&#34;docs/screenshots/budget.png&#34; alt=&#34;Costs · expense splitting&#34; width=&#34;49%&#34; /&gt;&lt;/a&gt;
  &lt;a href=&#34;docs/screenshots/atlas.png&#34;&gt;&lt;img src=&#34;docs/screenshots/atlas.png&#34; alt=&#34;Atlas · visited countries&#34; width=&#34;49%&#34; /&gt;&lt;/a&gt;
  &lt;a href=&#34;docs/screenshots/vacay.png&#34;&gt;&lt;img src=&#34;docs/screenshots/vacay.png&#34; alt=&#34;Vacay planner&#34; width=&#34;49%&#34; /&gt;&lt;/a&gt;
  &lt;a href=&#34;docs/screenshots/trip-iceland.png&#34;&gt;&lt;img src=&#34;docs/screenshots/trip-iceland.png&#34; alt=&#34;Trip planner · day plan and route&#34; width=&#34;49%&#34; /&gt;&lt;/a&gt;
  &lt;a href=&#34;docs/screenshots/admin.png&#34;&gt;&lt;img src=&#34;docs/screenshots/admin.png&#34; alt=&#34;Admin panel&#34; width=&#34;49%&#34; /&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;hr&gt;
&lt;h2 id=&#34;what-you-get&#34;&gt;What you get
&lt;/h2&gt;&lt;picture&gt;
  &lt;source media=&#34;(max-width: 700px)&#34; srcset=&#34;docs/tiles/grid-mobile.svg&#34; /&gt;
  &lt;img src=&#34;docs/tiles/grid-desktop.svg&#34; alt=&#34;TREK feature tiles&#34; width=&#34;100%&#34; /&gt;
&lt;/picture&gt;
&lt;details&gt;
&lt;summary&gt;&lt;b&gt;See all features&lt;/b&gt;&lt;/summary&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;td width=&#34;50%&#34; valign=&#34;top&#34;&gt;
&lt;h4 id=&#34;-trip-planning&#34;&gt;🧭 Trip planning
&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Drag &amp;amp; drop planner&lt;/strong&gt; — organise places into day plans with reordering and cross-day moves&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Interactive map&lt;/strong&gt; — Leaflet or Mapbox GL with 3D buildings, terrain, photo markers, clustering, route visualization&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Place search&lt;/strong&gt; — Google Places (photos, ratings, hours) or OpenStreetMap (free, no API key)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Place import&lt;/strong&gt; — shared Google Maps / Naver Maps lists, plus GPX and KML/KMZ/GeoJSON map files&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Day notes&lt;/strong&gt; — timestamped, icon-tagged notes with drag-and-drop reordering&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Route optimisation&lt;/strong&gt; — auto-sort places and export to Google Maps&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Weather forecasts&lt;/strong&gt; — 16-day via Open-Meteo (no key) + historical climate fallback&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Category filter&lt;/strong&gt; — show only matching pins on the map&lt;/li&gt;
&lt;/ul&gt;
&lt;/td&gt;
&lt;td width=&#34;50%&#34; valign=&#34;top&#34;&gt;
&lt;h4 id=&#34;-travel-management&#34;&gt;🧳 Travel management
&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Reservations&lt;/strong&gt; — flights, accommodations, restaurants with status, confirmation numbers, files; import from booking confirmation emails and PDFs (&lt;a class=&#34;link&#34; href=&#34;https://invent.kde.org/pim/kitinerary&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;KDE Itinerary&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Costs&lt;/strong&gt; — track and split trip expenses (Splitwise-style): per-person / per-day breakdowns, settle-up, multi-currency&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Packing lists&lt;/strong&gt; — categories, templates, user assignment, progress tracking&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bag tracking&lt;/strong&gt; — optional weight tracking with iOS-style distribution&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Document manager&lt;/strong&gt; — attach docs, tickets, PDFs to trips / places / reservations (≤ 50 MB each)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PDF export&lt;/strong&gt; — full trip plan as PDF with cover page, images, notes&lt;/li&gt;
&lt;/ul&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td width=&#34;50%&#34; valign=&#34;top&#34;&gt;
&lt;h4 id=&#34;-collaboration&#34;&gt;👥 Collaboration
&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Real-time sync&lt;/strong&gt; — WebSocket. Changes appear instantly across all connected users&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Multi-user trips&lt;/strong&gt; — invite members with role-based access&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Invite links&lt;/strong&gt; — one-time or reusable links with expiry&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SSO (OIDC)&lt;/strong&gt; — Google, Apple, Authentik, Keycloak, or any OIDC provider&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;2FA&lt;/strong&gt; — TOTP + backup codes&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Passkeys&lt;/strong&gt; — passwordless WebAuthn login (fingerprint / face / PIN / security key), admin-toggleable&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Collab suite&lt;/strong&gt; — group chat, shared notes, polls, day check-ins&lt;/li&gt;
&lt;/ul&gt;
&lt;/td&gt;
&lt;td width=&#34;50%&#34; valign=&#34;top&#34;&gt;
&lt;h4 id=&#34;-mobile--pwa&#34;&gt;📱 Mobile &amp;amp; PWA
&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Installable&lt;/strong&gt; — iOS and Android, straight from the browser, no App Store needed&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Offline support&lt;/strong&gt; — Service Worker caches tiles, API, uploads via Workbox&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Native feel&lt;/strong&gt; — fullscreen standalone, themed status bar, splash screen&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Touch optimised&lt;/strong&gt; — mobile-specific layouts with safe-area handling&lt;/li&gt;
&lt;/ul&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td width=&#34;50%&#34; valign=&#34;top&#34;&gt;
&lt;h4 id=&#34;-addons-admin-toggleable&#34;&gt;🧩 Addons (admin-toggleable)
&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Lists&lt;/strong&gt; — packing lists + to-dos with templates, member assignments, optional bag tracking&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Costs&lt;/strong&gt; — expense tracker with splits and settle-up (who owes whom), multi-currency&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Documents&lt;/strong&gt; — file attachments on trips, places, and reservations&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Collab&lt;/strong&gt; — chat, notes, polls, day-by-day attendance&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Vacay&lt;/strong&gt; — personal vacation planner with calendar, 100+ country holidays, carry-over tracking&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Atlas&lt;/strong&gt; — world map of visited countries, bucket list, travel stats, streak tracking, liquid-glass UI&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Journey&lt;/strong&gt; — magazine-style travel journal with entries, photos (Immich/Synology), maps, moods&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AirTrail&lt;/strong&gt; — connect a self-hosted AirTrail instance to import and sync flights into reservations&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MCP&lt;/strong&gt; — expose TREK to AI assistants via OAuth 2.1&lt;/li&gt;
&lt;/ul&gt;
&lt;/td&gt;
&lt;td width=&#34;50%&#34; valign=&#34;top&#34;&gt;
&lt;h4 id=&#34;-ai--mcp&#34;&gt;🤖 AI / MCP
&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Built-in MCP server&lt;/strong&gt; — OAuth 2.1 authenticated. 150+ tools, 30 resources&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Granular scopes&lt;/strong&gt; — 27 OAuth scopes across 13 permission groups&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Full automation&lt;/strong&gt; — AI can create trips, plan days, build packing lists, manage budgets, mark countries visited&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pre-built prompts&lt;/strong&gt; — &lt;code&gt;trip-summary&lt;/code&gt;, &lt;code&gt;packing-list&lt;/code&gt;, &lt;code&gt;budget-overview&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Addon-aware&lt;/strong&gt; — exposes Atlas, Collab, Vacay when those addons are on&lt;/li&gt;
&lt;/ul&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td colspan=&#34;2&#34; valign=&#34;top&#34;&gt;
&lt;h4 id=&#34;-admin--customisation&#34;&gt;⚙️ Admin &amp;amp; customisation
&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Dashboard views&lt;/strong&gt; — card grid or compact list · &lt;strong&gt;Dark mode&lt;/strong&gt; — full theme with matching status bar&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;20 languages&lt;/strong&gt; — EN, DE, ES, FR, IT, NL, HU, RU, ZH, ZH-TW, PL, CS, AR (RTL), BR, ID, TR, JA, KO, UK, GR&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Admin panel&lt;/strong&gt; — users, invites, packing templates, categories, addons, API keys, backups, GitHub history&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Notifications&lt;/strong&gt; — per-user preferences across email (SMTP), webhook, ntfy, and an in-app notification center&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Auto-backups&lt;/strong&gt; — scheduled with configurable retention · &lt;strong&gt;Units&lt;/strong&gt; — °C/°F, 12h/24h, map tile sources, default coordinates&lt;/li&gt;
&lt;/ul&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/details&gt;
&lt;br /&gt;
&lt;h2 id=&#34;get-started-in-30-seconds&#34;&gt;Get started in 30 seconds
&lt;/h2&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;ENCRYPTION_KEY&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;$(&lt;/span&gt;openssl rand -hex 32&lt;span class=&#34;k&#34;&gt;)&lt;/span&gt; docker run -d -p 3000:3000 &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  -e &lt;span class=&#34;nv&#34;&gt;ENCRYPTION_KEY&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$ENCRYPTION_KEY&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  -v ./data:/app/data -v ./uploads:/app/uploads mauriceboe/trek
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Open &lt;code&gt;http://localhost:3000&lt;/code&gt;. On first boot TREK seeds an admin account — if you set &lt;code&gt;ADMIN_EMAIL&lt;/code&gt;/&lt;code&gt;ADMIN_PASSWORD&lt;/code&gt; those are used, otherwise the credentials are printed to the container log (&lt;code&gt;docker logs trek&lt;/code&gt;).&lt;/p&gt;
&lt;div align=&#34;center&#34;&gt;
&lt;p&gt;  ·  &lt;a href=&#34;#docker-compose-production&#34;&gt;Docker Compose&lt;/a&gt;  ·  &lt;a href=&#34;#helm-kubernetes&#34;&gt;Helm / Kubernetes&lt;/a&gt;  ·  &lt;a href=&#34;#install-as-app-pwa&#34;&gt;Install as PWA&lt;/a&gt;  ·  &lt;a href=&#34;#reverse-proxy&#34;&gt;Reverse Proxy&lt;/a&gt;  ·  &lt;/p&gt;
&lt;/div&gt;
&lt;br /&gt;
&lt;h2 id=&#34;tech-stack&#34;&gt;Tech stack
&lt;/h2&gt;&lt;div align=&#34;center&#34;&gt;
&lt;p&gt;&lt;img src=&#34;https://img.shields.io/badge/Node.js_22-339933?style=flat-square&amp;amp;logo=node.js&amp;amp;logoColor=white&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Node.js&#34;
	
	
&gt;
&lt;img src=&#34;https://img.shields.io/badge/NestJS_11-E0234E?style=flat-square&amp;amp;logo=nestjs&amp;amp;logoColor=white&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;NestJS&#34;
	
	
&gt;
&lt;img src=&#34;https://img.shields.io/badge/SQLite-003B57?style=flat-square&amp;amp;logo=sqlite&amp;amp;logoColor=white&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;SQLite&#34;
	
	
&gt;
&lt;img src=&#34;https://img.shields.io/badge/React_19-61DAFB?style=flat-square&amp;amp;logo=react&amp;amp;logoColor=black&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;React&#34;
	
	
&gt;
&lt;img src=&#34;https://img.shields.io/badge/Vite-646CFF?style=flat-square&amp;amp;logo=vite&amp;amp;logoColor=white&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Vite&#34;
	
	
&gt;
&lt;img src=&#34;https://img.shields.io/badge/TypeScript-3178C6?style=flat-square&amp;amp;logo=typescript&amp;amp;logoColor=white&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;TypeScript&#34;
	
	
&gt;
&lt;img src=&#34;https://img.shields.io/badge/Tailwind-06B6D4?style=flat-square&amp;amp;logo=tailwindcss&amp;amp;logoColor=white&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Tailwind&#34;
	
	
&gt;
&lt;img src=&#34;https://img.shields.io/badge/Leaflet-199900?style=flat-square&amp;amp;logo=leaflet&amp;amp;logoColor=white&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Leaflet&#34;
	
	
&gt;
&lt;img src=&#34;https://img.shields.io/badge/Docker-2496ED?style=flat-square&amp;amp;logo=docker&amp;amp;logoColor=white&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Docker&#34;
	
	
&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Real-time sync via WebSocket (&lt;code&gt;ws&lt;/code&gt;). 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.&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id=&#34;docker-compose-production&#34;&gt;Docker Compose (production)&lt;/h2&gt;
&lt;details&gt;
&lt;summary&gt;Full compose example with secure defaults&lt;/summary&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;18
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;19
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;20
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;21
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;22
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;23
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;24
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;25
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;26
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;27
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;28
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;29
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;30
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;31
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;32
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;33
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;34
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;35
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;36
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;37
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;38
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;39
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;40
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;41
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;42
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;43
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;services&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;app&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;image&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;mauriceboe/trek:latest&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;container_name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;trek&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;read_only&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;true&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;security_opt&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;- &lt;span class=&#34;kc&#34;&gt;no&lt;/span&gt;-&lt;span class=&#34;l&#34;&gt;new-privileges:true&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;cap_drop&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;ALL&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;cap_add&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;CHOWN&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;SETUID&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;SETGID&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;tmpfs&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;/tmp:noexec,nosuid,size=64m&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;ports&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;- &lt;span class=&#34;s2&#34;&gt;&amp;#34;3000:3000&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;environment&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;NODE_ENV=production&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;PORT=3000&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;ENCRYPTION_KEY=${ENCRYPTION_KEY:-}  &lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c&#34;&gt;# generate with: openssl rand -hex 32&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;TZ=${TZ:-UTC}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;LOG_LEVEL=${LOG_LEVEL:-info}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;ALLOWED_ORIGINS=${ALLOWED_ORIGINS:-}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;APP_URL=${APP_URL:-}                &lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c&#34;&gt;# required for OIDC + email links&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;c&#34;&gt;# - FORCE_HTTPS=true                   # behind a TLS-terminating proxy&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;c&#34;&gt;# - TRUST_PROXY=1&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;c&#34;&gt;# - OIDC_ISSUER=https://auth.example.com&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;c&#34;&gt;# - OIDC_CLIENT_ID=trek&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;c&#34;&gt;# - OIDC_CLIENT_SECRET=supersecret&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;c&#34;&gt;# - OIDC_DISPLAY_NAME=SSO&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;c&#34;&gt;# - OIDC_ADMIN_CLAIM=groups&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;c&#34;&gt;# - OIDC_ADMIN_VALUE=app-trek-admins&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;volumes&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;./data:/app/data&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;./uploads:/app/uploads&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;restart&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;unless-stopped&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;healthcheck&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;test&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;CMD&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;wget&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;-qO-&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;http://localhost:3000/api/health&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;interval&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;30s&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;timeout&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;10s&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;retries&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;m&#34;&gt;3&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;start_period&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;15s&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Then:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;docker compose up -d
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;strong&gt;HTTPS notes:&lt;/strong&gt; &lt;code&gt;FORCE_HTTPS=true&lt;/code&gt; is optional — it adds a 301 redirect, HSTS, CSP upgrade-insecure-requests, and forces the &lt;code&gt;secure&lt;/code&gt; cookie flag. Only use it behind a TLS-terminating reverse proxy. &lt;code&gt;TRUST_PROXY=1&lt;/code&gt; tells the server how many proxies sit in front so real client IPs and &lt;code&gt;X-Forwarded-Proto&lt;/code&gt; work.&lt;/p&gt;
&lt;/details&gt;
&lt;br /&gt;
&lt;h2 id=&#34;helm-kubernetes&#34;&gt;Helm (Kubernetes)&lt;/h2&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;helm repo add trek https://mauriceboe.github.io/TREK
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;helm repo update
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;helm install trek trek/trek
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;See &lt;a class=&#34;link&#34; href=&#34;https://github.com/mauriceboe/TREK/blob/main/charts/README.md&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;&lt;code&gt;charts/README.md&lt;/code&gt;&lt;/a&gt; for values.&lt;/p&gt;
&lt;h2 id=&#34;install-as-app-pwa&#34;&gt;Install as App (PWA)&lt;/h2&gt;
&lt;p&gt;TREK works as a Progressive Web App — no App Store needed.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Open TREK in the browser (HTTPS required)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;iOS&lt;/strong&gt;: Share ▸ &lt;em&gt;Add to Home Screen&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Android&lt;/strong&gt;: Menu ▸ &lt;em&gt;Install app&lt;/em&gt; (or &lt;em&gt;Add to Home Screen&lt;/em&gt;)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;TREK then launches fullscreen with its own icon, just like a native app.&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id=&#34;updating&#34;&gt;Updating
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Docker Compose:&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;docker compose pull &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; docker compose up -d
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Docker run&lt;/strong&gt; — reuse the original volume paths:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;docker pull mauriceboe/trek
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;docker rm -f trek
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;docker run -d --name trek -p 3000:3000 -v ./data:/app/data -v ./uploads:/app/uploads --restart unless-stopped mauriceboe/trek
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;Not sure which paths you used? &lt;code&gt;docker inspect trek --format &#39;{{json .Mounts}}&#39;&lt;/code&gt; before removing the container.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Your data stays in the mounted &lt;code&gt;data&lt;/code&gt; and &lt;code&gt;uploads&lt;/code&gt; volumes — updates never touch it.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]
Mount &lt;strong&gt;only&lt;/strong&gt; the data and uploads directories — &lt;code&gt;-v ./data:/app/data -v ./uploads:/app/uploads&lt;/code&gt;. &lt;strong&gt;Never mount a volume at &lt;code&gt;/app&lt;/code&gt;.&lt;/strong&gt; Doing so hides the application code shipped in the image and the container fails to start with &lt;code&gt;Cannot find module &#39;tsconfig-paths/register&#39;&lt;/code&gt;. If you previously mounted &lt;code&gt;/app&lt;/code&gt;, switch to the two mounts above; your data in &lt;code&gt;data/&lt;/code&gt; and &lt;code&gt;uploads/&lt;/code&gt; is preserved.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Rotating the Encryption Key&lt;/h3&gt;
&lt;p&gt;If you need to rotate &lt;code&gt;ENCRYPTION_KEY&lt;/code&gt; (e.g. upgrading from a version that derived encryption from &lt;code&gt;JWT_SECRET&lt;/code&gt;):&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;docker &lt;span class=&#34;nb&#34;&gt;exec&lt;/span&gt; -it trek node --import tsx scripts/migrate-encryption.ts
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The script creates a timestamped DB backup before making changes and prompts for old + new keys (input is not echoed).&lt;/p&gt;
&lt;h2 id=&#34;reverse-proxy&#34;&gt;Reverse Proxy&lt;/h2&gt;
&lt;p&gt;For production, put TREK behind a TLS-terminating reverse proxy. TREK uses WebSockets for real-time sync, so the proxy &lt;strong&gt;must&lt;/strong&gt; support WebSocket upgrades on &lt;code&gt;/ws&lt;/code&gt;.&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;Nginx&lt;/summary&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;18
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;19
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;20
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;21
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;22
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;23
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;24
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;25
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;26
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;27
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;28
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;29
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;30
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;31
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;32
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;33
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;34
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-nginx&#34; data-lang=&#34;nginx&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;server&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kn&#34;&gt;listen&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;80&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kn&#34;&gt;server_name&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;trek.yourdomain.com&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kn&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;301&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;https://&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$host$request_uri&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;server&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kn&#34;&gt;listen&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;443&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;ssl&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;http2&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kn&#34;&gt;server_name&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;trek.yourdomain.com&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kn&#34;&gt;ssl_certificate&lt;/span&gt;     &lt;span class=&#34;s&#34;&gt;/etc/ssl/fullchain.pem&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kn&#34;&gt;ssl_certificate_key&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;/etc/ssl/privkey.pem&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;# 500 MB covers backup-restore uploads (capped at 500 MB server-side).
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kn&#34;&gt;client_max_body_size&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;500m&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kn&#34;&gt;location&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;/&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;kn&#34;&gt;proxy_pass&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;http://localhost:3000&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;kn&#34;&gt;proxy_http_version&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;.1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;kn&#34;&gt;proxy_set_header&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;Host&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$host&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;kn&#34;&gt;proxy_set_header&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;X-Real-IP&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$remote_addr&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;kn&#34;&gt;proxy_set_header&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;X-Forwarded-For&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$proxy_add_x_forwarded_for&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;kn&#34;&gt;proxy_set_header&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;X-Forwarded-Proto&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$scheme&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kn&#34;&gt;location&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;/ws&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;kn&#34;&gt;proxy_pass&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;http://localhost:3000&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;kn&#34;&gt;proxy_http_version&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;.1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;kn&#34;&gt;proxy_set_header&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;Upgrade&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$http_upgrade&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;kn&#34;&gt;proxy_set_header&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;Connection&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;upgrade&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;kn&#34;&gt;proxy_set_header&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;Host&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$host&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;kn&#34;&gt;proxy_read_timeout&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;86400&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/details&gt;
&lt;details&gt;
&lt;summary&gt;Caddy&lt;/summary&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-caddy&#34; data-lang=&#34;caddy&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gh&#34;&gt;trek.yourdomain.com&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;reverse_proxy&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;localhost&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;3000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Caddy handles TLS and WebSockets automatically.&lt;/p&gt;
&lt;/details&gt;
&lt;br /&gt;
&lt;h2 id=&#34;environment-variables&#34;&gt;Environment variables
&lt;/h2&gt;&lt;details&gt;
&lt;summary&gt;&lt;b&gt;Full reference&lt;/b&gt;&lt;/summary&gt;
&lt;br /&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Variable&lt;/th&gt;
					&lt;th&gt;Description&lt;/th&gt;
					&lt;th&gt;Default&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;Core&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;PORT&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;Server port&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;3000&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;NODE_ENV&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;Environment (&lt;code&gt;production&lt;/code&gt; / &lt;code&gt;development&lt;/code&gt;)&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;production&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;ENCRYPTION_KEY&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;At-rest encryption key for stored secrets (API keys, MFA, SMTP, OIDC). Recommended: generate with &lt;code&gt;openssl rand -hex 32&lt;/code&gt;. If unset, falls back to &lt;code&gt;data/.jwt_secret&lt;/code&gt; (existing installs) or auto-generates a key (fresh installs).&lt;/td&gt;
					&lt;td&gt;Auto&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;TZ&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;Timezone for logs, reminders and cron jobs (e.g. &lt;code&gt;Europe/Berlin&lt;/code&gt;)&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;UTC&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;LOG_LEVEL&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;info&lt;/code&gt; = concise user actions, &lt;code&gt;debug&lt;/code&gt; = verbose details&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;info&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;DEFAULT_LANGUAGE&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;Default language on the login page for users with no saved preference. Browser/OS language is auto-detected first; this is the fallback. Supported: &lt;code&gt;de&lt;/code&gt;, &lt;code&gt;en&lt;/code&gt;, &lt;code&gt;es&lt;/code&gt;, &lt;code&gt;fr&lt;/code&gt;, &lt;code&gt;hu&lt;/code&gt;, &lt;code&gt;nl&lt;/code&gt;, &lt;code&gt;br&lt;/code&gt;, &lt;code&gt;cs&lt;/code&gt;, &lt;code&gt;pl&lt;/code&gt;, &lt;code&gt;ru&lt;/code&gt;, &lt;code&gt;zh&lt;/code&gt;, &lt;code&gt;zh-TW&lt;/code&gt;, &lt;code&gt;it&lt;/code&gt;, &lt;code&gt;ar&lt;/code&gt;, &lt;code&gt;id&lt;/code&gt;, &lt;code&gt;tr&lt;/code&gt;, &lt;code&gt;ja&lt;/code&gt;, &lt;code&gt;ko&lt;/code&gt;, &lt;code&gt;uk&lt;/code&gt;, &lt;code&gt;gr&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;en&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;ALLOWED_ORIGINS&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;Comma-separated origins for CORS and email links&lt;/td&gt;
					&lt;td&gt;same-origin&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;FORCE_HTTPS&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;Optional. When &lt;code&gt;true&lt;/code&gt;: 301-redirects HTTP to HTTPS, sends HSTS, adds CSP &lt;code&gt;upgrade-insecure-requests&lt;/code&gt;, forces the session cookie &lt;code&gt;secure&lt;/code&gt; flag. Useful behind a TLS-terminating reverse proxy. Requires &lt;code&gt;TRUST_PROXY&lt;/code&gt;.&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;HSTS_INCLUDE_SUBDOMAINS&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;When &lt;code&gt;true&lt;/code&gt;: adds the &lt;code&gt;includeSubDomains&lt;/code&gt; directive to the HSTS header, extending HTTPS enforcement to all subdomains. Only effective when HSTS is active (&lt;code&gt;FORCE_HTTPS=true&lt;/code&gt; or &lt;code&gt;NODE_ENV=production&lt;/code&gt;). Leave &lt;code&gt;false&lt;/code&gt; if you run other services on sibling subdomains over plain HTTP.&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;COOKIE_SECURE&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;Controls the &lt;code&gt;secure&lt;/code&gt; flag on the &lt;code&gt;trek_session&lt;/code&gt; cookie. Auto-derived: on when &lt;code&gt;NODE_ENV=production&lt;/code&gt; or &lt;code&gt;FORCE_HTTPS=true&lt;/code&gt;. Escape hatch: set &lt;code&gt;false&lt;/code&gt; to allow session cookies over plain HTTP. Not recommended in production.&lt;/td&gt;
					&lt;td&gt;auto&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;SESSION_DURATION&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;How long a login session stays valid when &lt;strong&gt;&amp;ldquo;Remember me&amp;rdquo; is unchecked&lt;/strong&gt; (the default): sets the &lt;code&gt;trek_session&lt;/code&gt; JWT &lt;code&gt;exp&lt;/code&gt; and issues a browser-session cookie (cleared when the browser closes). Accepts &lt;code&gt;ms&lt;/code&gt;-style strings: &lt;code&gt;1h&lt;/code&gt;, &lt;code&gt;12h&lt;/code&gt;, &lt;code&gt;7d&lt;/code&gt;, &lt;code&gt;30d&lt;/code&gt;, &lt;code&gt;90d&lt;/code&gt;. Invalid values warn at startup and fall back to the default.&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;24h&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;SESSION_DURATION_REMEMBER&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;Session length when &lt;strong&gt;&amp;ldquo;Remember me&amp;rdquo; is ticked&lt;/strong&gt; at login: a longer-lived JWT plus a persistent &lt;code&gt;trek_session&lt;/code&gt; cookie that survives browser restarts. Same format and startup-fallback behaviour as &lt;code&gt;SESSION_DURATION&lt;/code&gt;.&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;30d&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;TRUST_PROXY&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;Number of trusted reverse proxies. Tells the server to read client IP from &lt;code&gt;X-Forwarded-For&lt;/code&gt; and protocol from &lt;code&gt;X-Forwarded-Proto&lt;/code&gt;. Defaults to &lt;code&gt;1&lt;/code&gt; in production; off in dev unless set.&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;1&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;ALLOW_INTERNAL_NETWORK&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;Allow outbound requests to private/RFC-1918 IPs (e.g. Immich on your LAN). Loopback and link-local addresses remain blocked.&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;APP_URL&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;Public base URL of this instance (e.g. &lt;code&gt;https://trek.example.com&lt;/code&gt;). Required when OIDC is enabled; used as base for email notification links.&lt;/td&gt;
					&lt;td&gt;—&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;OIDC / SSO&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;OIDC_ISSUER&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;OpenID Connect provider URL&lt;/td&gt;
					&lt;td&gt;—&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;OIDC_CLIENT_ID&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;OIDC client ID&lt;/td&gt;
					&lt;td&gt;—&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;OIDC_CLIENT_SECRET&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;OIDC client secret&lt;/td&gt;
					&lt;td&gt;—&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;OIDC_DISPLAY_NAME&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;Label shown on the SSO login button&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;SSO&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;OIDC_ONLY&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;Force SSO-only mode: disables password login + registration, regardless of Admin &amp;gt; Settings. The first SSO login becomes admin.&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;OIDC_ADMIN_CLAIM&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;OIDC claim used to identify admin users&lt;/td&gt;
					&lt;td&gt;—&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;OIDC_ADMIN_VALUE&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;Value of the OIDC claim that grants admin role&lt;/td&gt;
					&lt;td&gt;—&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;OIDC_SCOPE&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;Space-separated OIDC scopes. &lt;strong&gt;Fully replaces&lt;/strong&gt; the default — always include &lt;code&gt;openid email profile&lt;/code&gt;.&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;openid email profile&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;OIDC_DISCOVERY_URL&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;Override the auto-constructed OIDC discovery endpoint (e.g. Authentik: &lt;code&gt;.../application/o/trek/.well-known/openid-configuration&lt;/code&gt;)&lt;/td&gt;
					&lt;td&gt;—&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;Initial setup&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;ADMIN_EMAIL&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;Email for the first admin on initial boot. Must be set together with &lt;code&gt;ADMIN_PASSWORD&lt;/code&gt;. If either is omitted a random password is printed to the server log. No effect once a user exists.&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;admin@trek.local&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;ADMIN_PASSWORD&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;Password for the first admin on initial boot. Pairs with &lt;code&gt;ADMIN_EMAIL&lt;/code&gt;.&lt;/td&gt;
					&lt;td&gt;random&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;Other&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;DEMO_MODE&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;Enable demo mode (hourly data resets)&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;MCP_RATE_LIMIT&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;Max MCP API requests per user per minute&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;300&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;MCP_MAX_SESSION_PER_USER&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;Max concurrent MCP sessions per user&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;20&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;/details&gt;
&lt;br /&gt;
&lt;h2 id=&#34;data--backups&#34;&gt;Data &amp;amp; Backups
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Database&lt;/strong&gt; — SQLite, stored in &lt;code&gt;./data/travel.db&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Uploads&lt;/strong&gt; — stored in &lt;code&gt;./uploads/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Logs&lt;/strong&gt; — &lt;code&gt;./data/logs/trek.log&lt;/code&gt; (auto-rotated)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Backups&lt;/strong&gt; — create and restore via Admin Panel&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Auto-Backups&lt;/strong&gt; — configurable schedule and retention in Admin Panel&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;h2 id=&#34;data-sources&#34;&gt;Data sources
&lt;/h2&gt;&lt;p&gt;The Atlas map&amp;rsquo;s country and sub-national (province/county) boundaries come from
&lt;a class=&#34;link&#34; href=&#34;https://www.geoboundaries.org/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;&lt;strong&gt;geoBoundaries&lt;/strong&gt;&lt;/a&gt; (Runfola et al., 2020), licensed
&lt;a class=&#34;link&#34; href=&#34;https://creativecommons.org/licenses/by/4.0/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;CC BY 4.0&lt;/a&gt;. See &lt;a class=&#34;link&#34; href=&#34;NOTICE.md&#34; &gt;NOTICE.md&lt;/a&gt;
for full third-party attributions.&lt;/p&gt;
&lt;h2 id=&#34;license&#34;&gt;License
&lt;/h2&gt;&lt;p&gt;TREK is &lt;a class=&#34;link&#34; href=&#34;LICENSE&#34; &gt;AGPL v3&lt;/a&gt;. 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.&lt;/p&gt;
</description>
        </item>
        
    </channel>
</rss>
