jubnl aa32b1f372 fix(migrations): qualify provider column in trip_photos JOIN (migration 98)
Both trip_photos (alias tp) and trek_photos (alias tkp) have a provider
column. Using the bare identifier 'provider' in the JOIN condition was
ambiguous and caused SQLite to throw SQLITE_ERROR, failing migration 98
and taking down the entire test suite setup.

Fix: introduce providerJoinExpr = 'tp.provider' when the legacy
trip_photos table already carries a provider column, used only in the
two-table JOIN. The single-table INSERT keeps the unqualified form.
2026-04-14 13:39:28 +02:00
2026-04-08 19:02:45 +02:00
2026-03-19 13:01:55 +01:00

TREK
Your Trips. Your Plan.

Discord License: AGPL v3 Docker Pulls GitHub Stars Last Commit

A self-hosted, real-time collaborative travel planner with interactive maps, budgets, packing lists, and more.
Live Demo — Try TREK without installing. Resets hourly.

TREK Screenshot TREK Screenshot 2

More Screenshots
Plan Detail Bookings
Budget Packing List
Collab

Features

Trip Planning

  • Drag & Drop Planner — Organize places into day plans with reordering and cross-day moves
  • Interactive Map — Leaflet map with photo markers, clustering, route visualization, and customizable tile sources
  • Place Search — Search via Google Places (with photos, ratings, opening hours) or OpenStreetMap (free, no API key needed)
  • Day Notes — Add timestamped, icon-tagged notes to individual days with drag & drop reordering
  • Route Optimization — Auto-optimize place order and export to Google Maps
  • Weather Forecasts — 16-day forecasts via Open-Meteo (no API key needed) with historical climate averages as fallback
  • Map Category Filter — Filter places by category and see only matching pins on the map

Travel Management

  • Reservations & Bookings — Track flights, accommodations, restaurants with status, confirmation numbers, and file attachments
  • Budget Tracking — Category-based expenses with pie chart, per-person/per-day splitting, and multi-currency support
  • Packing Lists — Category-based checklists with user assignment, packing templates, and progress tracking
  • Packing Templates — Create reusable packing templates in the admin panel with categories and items, apply to any trip
  • Bag Tracking — Optional weight tracking and bag assignment for packing items with iOS-style weight distribution (admin-toggleable)
  • Document Manager — Attach documents, tickets, and PDFs to trips, places, or reservations (up to 50 MB per file)
  • PDF Export — Export complete trip plans as PDF with cover page, images, notes, and TREK branding

Mobile & PWA

  • Progressive Web App — Install on iOS and Android directly from the browser, no App Store needed
  • Offline Support — Service Worker caches map tiles, API data, uploads, and static assets via Workbox
  • Native App Feel — Fullscreen standalone mode, custom app icon, themed status bar, and splash screen
  • Touch Optimized — Responsive design with mobile-specific layouts, touch-friendly controls, and safe area handling

Collaboration

  • Real-Time Sync — Plan together via WebSocket — changes appear instantly across all connected users
  • Multi-User — Invite members to collaborate on shared trips with role-based access
  • Invite Links — Create one-time registration links with configurable max uses and expiry for easy onboarding
  • Single Sign-On (OIDC) — Login with Google, Apple, Authentik, Keycloak, or any OIDC provider
  • Two-Factor Authentication (MFA) — TOTP-based 2FA with QR code setup, works with Google Authenticator, Authy, etc.
  • Collab — Chat with your group, share notes, create polls, and track who's signed up for each day's activities

Addons (modular, admin-toggleable)

  • Vacay — Personal vacation day planner with calendar view, public holidays (100+ countries), company holidays, user fusion with live sync, and carry-over tracking
  • Atlas — Interactive world map with visited countries, bucket list with planned travel dates, travel stats, continent breakdown, streak tracking, and liquid glass UI effects
  • Collab — Chat with your group, share notes, create polls, and track who's signed up for each day's activities
  • Dashboard Widgets — Currency converter and timezone clock, toggleable per user

AI / MCP Integration

  • MCP Server — Built-in Model Context Protocol server with OAuth 2.1 authentication exposes 80+ tools and 27 resources so AI assistants (Claude, Cursor, etc.) can read and modify your trips
  • Granular Scopes — 24 OAuth scopes across 13 permission groups let you control exactly what data your AI client can access
  • Full Trip Automation — AI can create trips, plan itineraries, build packing lists, manage budgets, send collab messages, mark countries visited, and more in a single conversation
  • Prompts — Pre-built trip-summary, packing-list, and budget-overview prompts give AI clients instant structured context
  • Addon-Aware — Atlas, Collab, and Vacay features are exposed automatically when those addons are enabled

Customization & Admin

  • Dashboard Views — Toggle between card grid and compact list view on the My Trips page
  • Dark Mode — Full light and dark theme with dynamic status bar color matching
  • Multilingual — English, German, Spanish, French, Russian, Chinese (Simplified), Dutch, Arabic (with RTL support)
  • Admin Panel — User management, invite links, packing templates, global categories, addon management, API keys, backups, and GitHub release history
  • Auto-Backups — Scheduled backups with configurable interval and retention
  • Customizable — Temperature units, time format (12h/24h), map tile sources, default coordinates

Tech Stack

  • Backend: Node.js 22 + Express + SQLite (better-sqlite3)
  • Frontend: React 18 + Vite + Tailwind CSS
  • PWA: vite-plugin-pwa + Workbox
  • Real-Time: WebSocket (ws)
  • State: Zustand
  • Auth: JWT + OAuth 2.1 + OIDC + TOTP (MFA)
  • Maps: Leaflet + react-leaflet-cluster + Google Places API (optional)
  • Weather: Open-Meteo API (free, no key required)
  • Icons: lucide-react

Helm (Kubernetes)

A hosted Helm repository is available:

helm repo add trek https://mauriceboe.github.io/TREK
helm repo update
helm install trek trek/trek

See charts/README.md for configuration options.

Quick Start

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

The app runs on port 3000. The first user to register becomes the admin.

Install as App (PWA)

TREK works as a Progressive Web App — no App Store needed:

  1. Open your TREK instance in the browser (HTTPS required)
  2. iOS: Share button → "Add to Home Screen"
  3. Android: Menu → "Install app" or "Add to Home Screen"
  4. TREK launches fullscreen with its own icon, just like a native app
Docker Compose (recommended for production)
services:
  app:
    image: mauriceboe/trek:latest
    container_name: trek
    read_only: true
    security_opt:
      - no-new-privileges:true
    cap_drop:
      - ALL
    cap_add:
      - CHOWN
      - SETUID
      - SETGID
    tmpfs:
      - /tmp:noexec,nosuid,size=64m
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
      - PORT=3000
      - ENCRYPTION_KEY=${ENCRYPTION_KEY:-} # Recommended. Generate with: openssl rand -hex 32. If unset, falls back to data/.jwt_secret (existing installs) or auto-generates a key (fresh installs).
      - TZ=${TZ:-UTC} # Timezone for logs, reminders and scheduled tasks (e.g. Europe/Berlin)
      - LOG_LEVEL=${LOG_LEVEL:-info} # info = concise user actions; debug = verbose admin-level details
      - ALLOWED_ORIGINS=${ALLOWED_ORIGINS:-} # Comma-separated origins for CORS and email notification links
      # - FORCE_HTTPS=true # Optional. Enables HTTPS redirect, HSTS, CSP upgrade-insecure-requests, and secure cookies behind a TLS proxy
      # - COOKIE_SECURE=false # Escape hatch: force session cookies over plain HTTP even in production. Not recommended.
      # - TRUST_PROXY=1 # Trusted proxy count for X-Forwarded-For / X-Forwarded-Proto. Required for FORCE_HTTPS to work.
      # - ALLOW_INTERNAL_NETWORK=true # Uncomment if Immich or other services are on your local network (RFC-1918 IPs)
      - APP_URL=${APP_URL:-} # Base URL of this instance — required when OIDC is enabled; must match the redirect URI registered with your IdP; Also used as the base URL for email notifications and other external links
      # - OIDC_ISSUER=https://auth.example.com # OpenID Connect provider URL
      # - OIDC_CLIENT_ID=trek # OpenID Connect client ID
      # - OIDC_CLIENT_SECRET=supersecret # OpenID Connect client secret
      # - OIDC_DISPLAY_NAME=SSO # Label shown on the SSO login button
      # - OIDC_ONLY=false # Set to true to force SSO-only login (disables password login and registration). Equivalent to toggling those off in Admin > Settings, but takes priority over any DB setting and cannot be changed at runtime.
      # - OIDC_ADMIN_CLAIM=groups # OIDC claim used to identify admin users
      # - OIDC_ADMIN_VALUE=app-trek-admins # Value of the OIDC claim that grants admin role
      # - OIDC_SCOPE=openid email profile # Fully overrides the default. Add extra scopes as needed (e.g. add groups if using OIDC_ADMIN_CLAIM)
      # - OIDC_DISCOVERY_URL= # Override the OIDC discovery endpoint for providers with non-standard paths (e.g. Authentik)
      # - DEMO_MODE=false # Enable demo mode (resets data hourly)
      # - ADMIN_EMAIL=admin@trek.local # Initial admin e-mail — only used on first boot when no users exist
      # - ADMIN_PASSWORD=changeme      # Initial admin password — only used on first boot when no users exist
      # - MCP_RATE_LIMIT=300 # Max MCP API requests per user per minute (default: 300)
      # - MCP_MAX_SESSION_PER_USER=20 # Max concurrent MCP sessions per user (default: 20)
    volumes:
      - ./data:/app/data
      - ./uploads:/app/uploads
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "wget", "-qO-", "http://localhost:3000/api/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 15s

This example is aimed at reverse-proxy deployments where nginx, Caddy, Traefik, or a similar proxy terminates TLS in front of TREK. The three HTTPS-related variables work together:

  • FORCE_HTTPS is 100% optional. When set to true it does four things: adds an HTTP-to-HTTPS 301 redirect, sends an HSTS header (max-age=31536000), adds the CSP upgrade-insecure-requests directive, and forces the session cookie secure flag on. It only makes sense behind a TLS-terminating proxy.
  • TRUST_PROXY tells Express how many proxies sit in front of TREK so it can read the real client IP from X-Forwarded-For and the protocol from X-Forwarded-Proto. Without it, FORCE_HTTPS redirects will loop because Express never sees the request as secure. In production (NODE_ENV=production) this defaults to 1 automatically; in development it is off unless explicitly set.
  • COOKIE_SECURE is normally auto-derived — the session cookie is marked secure whenever NODE_ENV=production or FORCE_HTTPS=true. Setting COOKIE_SECURE=false is an escape hatch that disables the secure flag even in production (e.g. testing over plain HTTP on a LAN). Do not disable it in real deployments.

If you access TREK directly on http://<host>:3000 with no reverse proxy, leave FORCE_HTTPS unset (or remove it) and remove TRUST_PROXY to avoid redirect loops to a non-existent HTTPS endpoint.

docker compose up -d

Updating

Docker Compose (recommended):

docker compose pull && docker compose up -d

Docker Run — use the same volume paths from your original docker run command:

docker pull mauriceboe/trek
docker rm -f trek
docker run -d --name trek -p 3000:3000 -v ./data:/app/data -v ./uploads:/app/uploads --restart unless-stopped mauriceboe/trek

Tip: Not sure which paths you used? Run docker inspect trek --format '{{json .Mounts}}' before removing the container.

Your data is persisted in the mounted data and uploads volumes — updates never touch your existing data.

Rotating the Encryption Key

If you need to rotate ENCRYPTION_KEY (e.g. you are upgrading from a version that derived encryption from JWT_SECRET), use the migration script to re-encrypt all stored secrets under the new key without starting the app:

docker exec -it trek node --import tsx scripts/migrate-encryption.ts

The script will prompt for your old and new keys interactively (input is not echoed). It creates a timestamped database backup before making any changes and exits with a non-zero code if anything fails.

Upgrading from a previous version? Your old JWT secret is in ./data/.jwt_secret. Use its contents as the "old key" and your new ENCRYPTION_KEY value as the "new key".

For production, put TREK behind a reverse proxy with HTTPS (e.g. Nginx, Caddy, Traefik).

Important: TREK uses WebSockets for real-time sync. Your reverse proxy must support WebSocket upgrades on the /ws path.

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 /path/to/fullchain.pem;
    ssl_certificate_key /path/to/privkey.pem;

    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_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_read_timeout 86400;
    }

    location / {
        proxy_pass http://localhost:3000;
        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;
    }
}
Caddy

Caddy handles WebSocket upgrades automatically:

trek.yourdomain.com {
    reverse_proxy localhost:3000
}

Environment Variables

Variable Description Default
Core
PORT Server port 3000
NODE_ENV Environment (production / development) production
ENCRYPTION_KEY At-rest encryption key for stored secrets (API keys, MFA, SMTP, OIDC). Recommended: generate with openssl rand -hex 32. If unset, falls back to data/.jwt_secret (existing installs) or auto-generates a key (fresh installs). Auto
TZ Timezone for logs, reminders and cron jobs (e.g. Europe/Berlin) UTC
LOG_LEVEL info = concise user actions, debug = verbose details info
ALLOWED_ORIGINS Comma-separated origins for CORS and email links same-origin
FORCE_HTTPS Optional. When true: 301-redirects HTTP to HTTPS, sends HSTS (max-age=31536000), adds CSP upgrade-insecure-requests, and forces the session cookie secure flag. Only useful behind a TLS-terminating reverse proxy. Requires TRUST_PROXY to be set so Express can detect the forwarded protocol. false
COOKIE_SECURE Controls the secure flag on the trek_session cookie. Auto-derived: secure is on when NODE_ENV=production or FORCE_HTTPS=true. Set to false as an escape hatch to allow session cookies over plain HTTP (e.g. LAN testing without TLS). Not recommended to disable in production. auto (true in production)
TRUST_PROXY Number of trusted reverse proxies. Tells Express to read client IP from X-Forwarded-For and protocol from X-Forwarded-Proto. Activates automatically in production (defaults to 1); off in development unless explicitly set. Must be set for FORCE_HTTPS redirects to work correctly. 1 (when active)
ALLOW_INTERNAL_NETWORK Allow outbound requests to private/RFC-1918 IP addresses. Set to true if Immich or other integrated services are hosted on your local network. Loopback (127.x) and link-local/metadata addresses (169.254.x) are always blocked regardless of this setting. false
APP_URL Public base URL of this instance (e.g. https://trek.example.com). Required when OIDC is enabled — must match the redirect URI registered with your IdP. Also used as the base URL for external links in email notifications.
OIDC / SSO
OIDC_ISSUER OpenID Connect provider URL
OIDC_CLIENT_ID OIDC client ID
OIDC_CLIENT_SECRET OIDC client secret
OIDC_DISPLAY_NAME Label shown on the SSO login button SSO
OIDC_ONLY Force SSO-only mode: disables password login and password registration, regardless of the granular toggles in Admin > Settings. The first SSO login becomes admin. Use when you want this enforced at the infrastructure level and not overridable via the UI. false
OIDC_ADMIN_CLAIM OIDC claim used to identify admin users
OIDC_ADMIN_VALUE Value of the OIDC claim that grants admin role
OIDC_SCOPE Space-separated OIDC scopes to request. Fully replaces the default — always include openid email profile plus any extra scopes you need (e.g. add groups when using OIDC_ADMIN_CLAIM) openid email profile
OIDC_DISCOVERY_URL Override the auto-constructed OIDC discovery endpoint. Useful for providers that expose it at a non-standard path (e.g. Authentik: https://auth.example.com/application/o/trek/.well-known/openid-configuration)
Initial Setup
ADMIN_EMAIL Email for the first admin account created on initial boot. Must be set together with ADMIN_PASSWORD. If either is omitted a random password is generated and printed to the server log. Has no effect once any user exists. admin@trek.local
ADMIN_PASSWORD Password for the first admin account created on initial boot. Must be set together with ADMIN_EMAIL. random
Other
DEMO_MODE Enable demo mode (hourly data resets) false
MCP_RATE_LIMIT Max MCP API requests per user per minute 300
MCP_MAX_SESSION_PER_USER Max concurrent MCP sessions per user 20

Optional API Keys

API keys are configured in the Admin Panel after login. Keys set by the admin are automatically shared with all users — no per-user configuration needed.

Google Maps (Place Search & Photos)

  1. Go to Google Cloud Console
  2. Create a project and enable the Places API (New)
  3. Create an API key under Credentials
  4. In TREK: Admin Panel → Settings → Google Maps

Building from Source

git clone https://github.com/mauriceboe/TREK.git
cd TREK
docker build -t trek .

Data & Backups

  • Database: SQLite, stored in ./data/travel.db
  • Uploads: Stored in ./uploads/
  • Logs: ./data/logs/trek.log (auto-rotated)
  • Backups: Create and restore via Admin Panel
  • Auto-Backups: Configurable schedule and retention in Admin Panel

License

AGPL-3.0

S
Description
A self-hosted travel/trip planner with real-time collaboration, interactive maps, PWA support, SSO, budgets, packing lists, and more.
Readme AGPL-3.0 150 MiB
Languages
TypeScript 99%
CSS 0.5%
JavaScript 0.4%