Kuromesi/Glorion

Plan, pin, and explore.

★ 0Forks 0RustGitHub ↗Compare

README

Glorion mark

Glorion

Map the journey. Shape it with AI.

A map-first, AI-assisted travel planner for turning real places into trips you can actually take.

Quick start · What it does · Architecture

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.

What Glorion does

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

Available now

  • 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.

In progress

  • A polished public Explore experience.
  • Authenticated real-time co-editing and presence.

Quick start

The Docker Compose bundle is the shortest path to a complete local stack.

1. Configure the stack

cd deploy/docker
cp .env.example .env

Open .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.

2. Start Glorion

docker compose up -d --build

Open 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 -d

The 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:8080

Start the optional Caddy profile:

docker compose --profile caddy up -d

Caddy 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

Local development

Prerequisites

  • Rust 1.88+
  • Node.js 22+
  • PostgreSQL 16+
  • AMap JavaScript and Web Service credentials
  • An LLM provider when testing AI features

Set up

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 stop

Or 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 --workspace

MCP tools

The 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-server

MCP_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.

Streamable HTTP and SSE

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-server

HTTP 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_TOKEN

GET /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.

Architecture

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

Technology

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

Configuration

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.

Deployment notes

  • Terminate TLS upstream. Enable the optional Compose caddy profile, or put a trusted load balancer/CDN/reverse proxy in front of the stack.
  • Restrict browser map keys. VITE_AMAP_JS_KEY and VITE_AMAP_SECURITY_JSCODE are compiled into the frontend and are public by design; apply an AMap domain whitelist.
  • Trust proxies deliberately. Only accept X-Forwarded-For from 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.

Repository automation

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

Contributors

Kuromesidependabot[bot]

Issues