Map the journey. Shape it with AI.
A map-first, AI-assisted travel planner for turning real places into trips you can actually take.
Note
Glorion is under active development. Map planning, AI assistance, weather, member invitations, custom models, and administration are available today. Public discovery and authenticated real-time co-editing are still being polished.
Glorion keeps the map, itinerary, and travel context in one workspace. Pin real places, arrange them into days, connect the route, and ask an AI planning partner to find or refine the next stop.
| Plan visually | Travel with context | Build with AI |
|---|---|---|
| Pin places on an AMap canvas | Inspect POIs and live weather | Search for places conversationally |
| Arrange multi-day itineraries | Invite travel companions | Review candidate pins before saving |
| Drag, reorder, and connect stops | Keep public or private plans | Choose platform or custom models |
| See the trip as a route | Manage users and quotas | Use OpenAI-compatible or Anthropic APIs |
- Map-first planning — search real places, add pins, build day-by-day itineraries, and connect stops into routes.
- AI travel partner — stream Markdown responses, suggest candidate places, and help shape a plan without silently changing it.
- Travel context — POI details, photos, map overlays, and weather at the point of planning.
- Shared plans — invite members and control plan visibility and access.
- Bring your own model — configure multiple providers or store a personal API key encrypted at rest.
- Operations console — bootstrap an administrator, manage users, inspect configuration, and enforce per-user AI quotas.
- A polished public Explore experience.
- Authenticated real-time co-editing and presence.
The Docker Compose bundle is the shortest path to a complete local stack.
cd deploy/docker
cp .env.example .envOpen .env and set at least:
| Variable | Requirement |
|---|---|
POSTGRES_PASSWORD |
A strong PostgreSQL password |
JWT_SECRET |
At least 32 characters; generate with openssl rand -hex 32 |
API_KEY_SECRET |
Exactly 64 hex characters; generate with openssl rand -hex 32 |
ADMIN_PASSWORD |
Password for the bootstrap administrator |
VITE_AMAP_JS_KEY |
AMap JavaScript API key for the browser map |
VITE_AMAP_SECURITY_JSCODE |
AMap JavaScript security code |
For place search and AI planning, also configure AMAP_WEB_SERVICE_KEY, LLM_BASE_URL, and LLM_API_KEY as required by your providers.
docker compose up -d --buildOpen http://localhost. The API runs behind the bundled Nginx proxy, and database migrations run automatically when the backend starts.
To use the optional Agent Gateway profile:
docker compose --profile agentgateway up -dThe mirrored Agent Gateway image is pinned to the v1.3.1 linux/amd64 digest; production hosts for this bundle must use amd64.
To expose Glorion with automatically managed HTTPS, point a domain's DNS A record
at the server (and add AAAA only when IPv6 is publicly reachable), then set these
values in deploy/docker/.env:
CADDY_DOMAIN=glorion.example.com
FRONTEND_URL=https://glorion.example.com
FRONTEND_PORT=127.0.0.1:8080Start the optional Caddy profile:
docker compose --profile caddy up -dCaddy owns public ports 80/443, obtains and renews the certificate, redirects HTTP to HTTPS, and proxies API, upload, SSE, and WebSocket traffic. Keep ports 80 and 443 reachable from the internet for certificate issuance. To combine both optional services, enable both profiles:
docker compose --profile caddy --profile agentgateway up -d- Rust 1.88+
- Node.js 22+
- PostgreSQL 16+
- AMap JavaScript and Web Service credentials
- An LLM provider when testing AI features
cp backend/.env.example backend/.env
cp frontend/.env.example frontend/.env.local
cd frontend
npm install
cd ..Before starting the backend, set API_KEY_SECRET in backend/.env to the 64-character hex output of openssl rand -hex 32, and replace the example ADMIN_PASSWORD.
Then start both services:
make start # backend :3000, frontend :5173
make status # processes and health checks
make logs # follow both logs
make stopOr work on each service directly:
# Frontend
cd frontend
npm run dev
npm run test
npm run lint
npm run build
# Backend
cd ../backend
cargo run -p api-server
cargo test --workspaceThe internal AI agent and the MCP server share the same canonical 18-tool runtime. The MCP process is a thin stdio protocol adapter; it does not contain a second implementation of plan or map operations.
Launch it with a short-lived user access token:
cd backend
MCP_ACCESS_TOKEN='<access-token>' \
MCP_SCOPES='maps:read,weather:read,plans:read' \
cargo run -p mcp-serverMCP_SCOPES defaults to the read-only value shown above. Write capabilities
must be explicitly enabled with plans:write; destructive point deletion also
requires plans:delete. Every write or destructive call uses MCP elicitation
to obtain explicit user approval; clients without elicitation support cannot
perform mutations. The access token and current account status are revalidated
for every call.
The MCP binary intentionally does not load backend/.env. Supply only the
required variables from a trusted local MCP host, use a dedicated least-
privilege database role, and never commit credentials to an environment file.
For network clients, run the same tool server with the modern Streamable HTTP transport. Its single MCP endpoint supports POST requests, GET SSE streams, and session deletion:
cd backend
MCP_TRANSPORT=http \
MCP_HTTP_BIND=127.0.0.1:8000 \
MCP_HTTP_PATH=/mcp \
MCP_HTTP_ALLOWED_HOSTS=localhost,127.0.0.1 \
MCP_HTTP_ALLOWED_ORIGINS=http://localhost:5173 \
MCP_SCOPES='maps:read,weather:read,plans:read' \
cargo run -p mcp-serverHTTP mode only accepts five-minute glorion-mcp audience tokens. Exchange a
normal authenticated web session for a least-privilege MCP token:
curl -X POST http://localhost:3000/api/v1/auth/mcp-token \
-H "Authorization: Bearer $GLORION_WEB_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"scopes":["maps:read","weather:read","plans:read"]}'The granted token scopes are intersected with the server's MCP_SCOPES maximum.
Every HTTP request must send this token as Authorization: Bearer <token>;
ordinary glorion-web tokens are rejected. MCP_ACCESS_TOKEN is only used by
stdio mode. To register the HTTP endpoint in Codex without putting the token in
its config file:
export GLORION_MCP_TOKEN='<short-lived-access-token>'
codex mcp add glorion-http \
--url http://127.0.0.1:8000/mcp \
--bearer-token-env-var GLORION_MCP_TOKENGET /health is an unauthenticated liveness check. Streamable HTTP includes
SSE and replaces the deprecated legacy /sse plus /message transport; those
legacy endpoints are intentionally not enabled. Bind public deployments behind
TLS, configure exact hosts and origins, and use a least-privilege database role.
Non-loopback binds fail at startup unless MCP_HTTP_BEHIND_TLS_PROXY=true is
set explicitly.
React SPA
│
▼
Axum API ───────────────► AMap / Weather / LLM providers
│
├──── Rig adapter ───► Tool Kernel ◄─── MCP adapter
│ │
▼ ▼
Application services ◄───────┘
│
▼
Domain model ◄──────────► PostgreSQL
The backend is a Rust workspace organized around clean architecture boundaries:
together/
├── frontend/ React 19 + TypeScript 6 + Vite 8
│ └── src/
│ ├── pages/ Route-level screens
│ ├── components/ Shared product UI
│ ├── api/ HTTP and streaming clients
│ ├── hooks/ Data and interaction logic
│ └── stores/ Zustand state
├── backend/
│ └── crates/
│ ├── domain/ Entities and value objects
│ ├── application/ Use cases and service interfaces
│ ├── infrastructure/ Persistence and external providers
│ ├── api-server/ Axum handlers and middleware
│ ├── mcp-server/ Model Context Protocol server
│ └── migration/ Database migrations
├── deploy/docker/ Compose, optional Caddy, Nginx, and gateway config
└── Makefile Local service orchestration
| Layer | Stack |
|---|---|
| Frontend | React 19, TypeScript 6, Vite 8, Zustand, TanStack Query, React Router 7, GSAP, AMap |
| Backend | Rust 2024, Axum 0.8, Tokio, SQLx, JWT, Argon2 |
| Data | PostgreSQL 16 |
| AI | OpenAI-compatible and Anthropic formats, multi-model configuration, MCP |
| Delivery | Docker Compose, optional Caddy HTTPS, Nginx, non-root containers |
Runtime configuration follows this precedence:
built-in defaults < config.toml < environment variables
Non-secret behavior lives in backend/config.toml or deploy/docker/config.toml. Keep credentials such as JWT_SECRET, API_KEY_SECRET, DATABASE_URL, LLM_*, and AMAP_* in the environment.
Runtime tuning
| Area | Config | Environment override |
|---|---|---|
| Authentication | auth.access_token_ttl_secs |
AUTH_ACCESS_TOKEN_TTL_SECS |
| AI quota | ai.daily_limit |
AI_DAILY_LIMIT |
| Auth rate limit | rate_limit.auth_per_sec, auth_burst |
RATE_LIMIT_AUTH_PER_SEC, RATE_LIMIT_AUTH_BURST |
| Global rate limit | rate_limit.global_per_sec, global_burst |
RATE_LIMIT_GLOBAL_PER_SEC, RATE_LIMIT_GLOBAL_BURST |
| Uploads | uploads.max_files, max_file_bytes, max_body_bytes |
UPLOAD_MAX_FILES, UPLOAD_MAX_FILE_BYTES, UPLOAD_MAX_BODY_BYTES |
| Logging | — | RUST_LOG |
Model definitions and the optional bootstrap administrator remain in TOML; their credentials remain in environment variables.
- Terminate TLS upstream. Enable the optional Compose
caddyprofile, or put a trusted load balancer/CDN/reverse proxy in front of the stack. - Restrict browser map keys.
VITE_AMAP_JS_KEYandVITE_AMAP_SECURITY_JSCODEare compiled into the frontend and are public by design; apply an AMap domain whitelist. - Trust proxies deliberately. Only accept
X-Forwarded-Forfrom load balancer CIDRs you control. - Never commit secrets. Keep production credentials outside TOML files and rotate any credential that is exposed.
Production workflow configuration
Repository secrets provide the ACR credentials and frontend build variables. The
production GitHub Environment must provide SSH_HOST, SSH_USER,
SSH_PRIVATE_KEY, and SSH_KNOWN_HOSTS; the last value contains the server's
verified host-key line. Do not generate it during deployment—verify it over a
separate trusted channel before storing it.
Optional repository variables control the profiles used by release deployments.
Store DEPLOY_URL as a repository variable too, because deployment inputs are
validated before the protected environment is entered:
| Variable | Effect |
|---|---|
DEPLOY_AGENTGATEWAY=true |
Enables the agentgateway profile for release deployments |
DEPLOY_CADDY=true |
Enables the caddy profile for release deployments |
DEPLOY_URL=https://... |
Runs a public post-deploy smoke check |
Manual deployments expose equivalent profile checkboxes. Configure required
reviewers on the production Environment so successful CI is necessary but not
by itself sufficient to deploy. Production hosts require Docker Compose 2.20 or
newer. Deployments use full sha-<commit> image tags (configure ACR tag
immutability for registry-side enforcement), validate a staged
configuration bundle before replacing live files, and restore the previous
bundle and images when a remote health check fails. Registry publication and
SSH deployment are separate protected jobs, so GitHub may request approval at
both production gates.
Dependabot checks frontend npm packages, backend Cargo crates, GitHub Actions,
and both application Dockerfiles each week. On pull requests, repository owners,
members, and collaborators can comment /help or /retest; the latter reruns
only failed jobs from the latest completed Test workflow for the PR's current
commit. Deployment is intentionally not exposed as a comment command.
Reverse proxy and upgrade details
When another load balancer sits in front of the bundled Nginx, recover the client address only from that trusted network:
set_real_ip_from <trusted-LB-CIDR>;
real_ip_header X-Forwarded-For;
real_ip_recursive on;Older deployments may have a root-owned backend_data volume. Either recreate it or repair ownership before moving to the non-root backend image:
docker run --rm -v <project>_backend_data:/data alpine chown -R 1000:1000 /data