From c5cb241ff85168334a8a81fab6a759eda62a8c65 Mon Sep 17 00:00:00 2001 From: "zhengxiao.wu" Date: Wed, 6 May 2026 19:12:52 +0800 Subject: [PATCH] docs: add public access guide + default port 1934 aggregated proxy - Add Caddyfile with :1934 HTTP aggregated proxy (merges 1933+8020) - Enable Caddy service by default in docker-compose.yml on port 1934 - Add docs/{en,zh}/guides/12-public-access.md with full HTTPS setup guide - Simplify 11-oauth.md: replace inline reverse proxy config with refs to 12 - Add HTTPS requirement callout to OAuth recommended setup - Update 03-deployment.md to mention port 1934 as recommended entry point --- Caddyfile | 33 ++++ docker-compose.yml | 81 ++++----- docs/en/guides/03-deployment.md | 9 +- docs/en/guides/11-oauth.md | 241 +++++--------------------- docs/en/guides/12-public-access.md | 262 +++++++++++++++++++++++++++++ docs/zh/guides/11-oauth.md | 224 ++++-------------------- docs/zh/guides/12-public-access.md | 252 +++++++++++++++++++++++++++ 7 files changed, 663 insertions(+), 439 deletions(-) create mode 100644 Caddyfile create mode 100644 docs/en/guides/12-public-access.md create mode 100644 docs/zh/guides/12-public-access.md diff --git a/Caddyfile b/Caddyfile new file mode 100644 index 000000000..4d431e72e --- /dev/null +++ b/Caddyfile @@ -0,0 +1,33 @@ +# OpenViking aggregated reverse proxy +# +# Port 1934 merges the API server (1933) and Console (8020) under one origin. +# This block always serves plain HTTP — suitable for local dev, internal +# networks, and as an upstream behind your own TLS-terminating proxy. +# +# To add public HTTPS, append a domain block below (Caddy auto-provisions +# Let's Encrypt certs): +# +# {$OPENVIKING_PUBLIC_BASE_URL} { +# @console path /console /console/* +# handle @console { +# reverse_proxy openviking:8020 +# } +# handle { +# reverse_proxy openviking:1933 +# } +# # Optional: pin ACME email +# # tls {$OV_ACME_EMAIL} +# } +# +# Then expose ports 80/443 in docker-compose.yml and set +# OPENVIKING_PUBLIC_BASE_URL=https://your-domain.com in .env. + +:1934 { + @console path /console /console/* + handle @console { + reverse_proxy openviking:8020 + } + handle { + reverse_proxy openviking:1933 + } +} diff --git a/docker-compose.yml b/docker-compose.yml index 0d2a59637..33b6822ff 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,34 +1,30 @@ version: "3.8" -# Set OPENVIKING_PUBLIC_BASE_URL in a `.env` file next to this compose file — -# both services read the same variable, so the domain is configured once. +# Set OPENVIKING_PUBLIC_BASE_URL in a `.env` file next to this compose file +# when you need public HTTPS (OAuth, MCP clients on the internet). # -# .env example for local-only on 127.0.0.1 -# (leave the variable empty or unset) +# .env example for local-only (leave unset or empty): +# # no .env needed — just `docker compose up -d` # -# .env example for public HTTPS deployment with Caddy +# .env example for public HTTPS: # OPENVIKING_PUBLIC_BASE_URL=https://ov.your-domain.com -# OV_ACME_EMAIL=admin@your-domain.com # optional, for Let's Encrypt +# OV_ACME_EMAIL=admin@your-domain.com # -# When OPENVIKING_PUBLIC_BASE_URL is unset, the server falls back to -# request-header inference (works for direct localhost connections). +# See docs/en/guides/12-public-access.md for the full walkthrough. services: openviking: image: ghcr.io/volcengine/openviking:latest container_name: openviking + # Direct access to individual services (optional — caddy:1934 is the + # recommended single entry point). Comment these out once Caddy is your + # only ingress. ports: - "1933:1933" - "8020:8020" volumes: - # All persistent state (ov.conf, ovcli.conf, workspace) lives here. - ~/.openviking:/app/.openviking environment: - # Public-facing URL the server publishes to MCP clients (issuer in - # OAuth metadata, host in WWW-Authenticate hints, base URL the - # `add_resource` tool tells agents to upload to). Behind a reverse - # proxy you should set this explicitly — request-header inference - # only works if the proxy chain forwards X-Forwarded-Host correctly. OPENVIKING_PUBLIC_BASE_URL: ${OPENVIKING_PUBLIC_BASE_URL:-} healthcheck: test: ["CMD-SHELL", "curl -fsS http://127.0.0.1:1933/health || exit 1"] @@ -37,41 +33,34 @@ services: retries: 3 start_period: 30s restart: unless-stopped - # If you need to override the default command (which runs openviking-server), - # you can do so here. For example, to run the CLI: - # command: ["openviking", "--help"] - # --------------------------------------------------------------------------- - # Optional Caddy reverse proxy with automatic HTTPS via Let's Encrypt. + # Aggregated reverse proxy — merges API (1933) + Console (8020) on port + # 1934 under one origin. Always enabled, plain HTTP by default. # - # Required for OAuth-only MCP clients (Claude.ai, Claude Desktop, ChatGPT, - # Cursor, …) which only accept https:// MCP server URLs. To enable: - # - # 1. Set OPENVIKING_PUBLIC_BASE_URL=https://ov.your-domain.com in `.env`. - # 2. Drop a Caddyfile next to this compose file (template in - # docs/en/guides/11-mcp-oauth.md). Caddy reads the URL from the - # OPENVIKING_PUBLIC_BASE_URL env var via {$OPENVIKING_PUBLIC_BASE_URL}. - # 3. Point your DNS A/AAAA record at this host's public IP. - # 4. Uncomment the caddy service + the `volumes:` block at the bottom. - # 5. `docker compose up -d`. ACME issuance happens on the first request. - # --------------------------------------------------------------------------- - # caddy: - # image: caddy:2 - # container_name: openviking-caddy - # restart: unless-stopped - # ports: - # - "80:80" - # - "443:443" - # volumes: - # - ./Caddyfile:/etc/caddy/Caddyfile:ro - # - caddy_data:/data - # - caddy_config:/config - # environment: - # OPENVIKING_PUBLIC_BASE_URL: ${OPENVIKING_PUBLIC_BASE_URL} - # OV_ACME_EMAIL: ${OV_ACME_EMAIL:-} - # depends_on: - # - openviking + # For public HTTPS: edit the Caddyfile to add a domain block, set + # OPENVIKING_PUBLIC_BASE_URL in .env, uncomment the 80/443 port lines + # below, and add the caddy volumes at the bottom. + caddy: + image: caddy:2 + container_name: openviking-caddy + restart: unless-stopped + ports: + - "1934:1934" + # Uncomment for public HTTPS (after adding a domain block to Caddyfile): + # - "80:80" + # - "443:443" + volumes: + - ./Caddyfile:/etc/caddy/Caddyfile:ro + # Uncomment for public HTTPS (Caddy stores certs here): + # - caddy_data:/data + # - caddy_config:/config + environment: + OPENVIKING_PUBLIC_BASE_URL: ${OPENVIKING_PUBLIC_BASE_URL:-} + OV_ACME_EMAIL: ${OV_ACME_EMAIL:-} + depends_on: + - openviking +# Uncomment for public HTTPS: # volumes: # caddy_data: # caddy_config: diff --git a/docs/en/guides/03-deployment.md b/docs/en/guides/03-deployment.md index 1ca447478..01a0f3a9a 100644 --- a/docs/en/guides/03-deployment.md +++ b/docs/en/guides/03-deployment.md @@ -276,8 +276,11 @@ docker compose up -d ``` After startup, you can access: -- API service: `http://localhost:1933` -- Console UI: `http://localhost:8020` +- Aggregated entry point: `http://localhost:1934` (Caddy merges API + Console) +- API service (direct): `http://localhost:1933` +- Console UI (direct): `http://localhost:8020` + +For public HTTPS access, see the [Public Access Guide](12-public-access.md). To build the image yourself, pass an explicit OpenViking version: `docker build --build-arg OPENVIKING_VERSION=0.3.12 -t openviking:latest .` @@ -314,6 +317,8 @@ Use `/health` for Kubernetes liveness probes and `/ready` for readiness probes. ## Related Documentation +- [Public Access & Reverse Proxy](12-public-access.md) - HTTPS, Caddy, nginx - [Authentication](04-authentication.md) - API key setup +- [OAuth Guide](11-oauth.md) - OAuth 2.1 for MCP clients - [Observability & Diagnostics](05-observability.md) - Health checks, tracing, and debugging - [API Overview](../api/01-overview.md) - Complete API reference diff --git a/docs/en/guides/11-oauth.md b/docs/en/guides/11-oauth.md index 6cc79b5c4..2019e004f 100644 --- a/docs/en/guides/11-oauth.md +++ b/docs/en/guides/11-oauth.md @@ -10,46 +10,24 @@ work just as well. ## Recommended setup -This is the minimum config that works end to end with Claude.ai and similar -public clients. You'll need a public domain, ports 80/443 reachable, and -docker compose. +> **Prerequisite**: public HTTPS. OAuth 2.1 (and the MCP SDK) **requires +> HTTPS** for any non-localhost issuer. See the +> [Public Access Guide](12-public-access.md) for how to set up HTTPS with +> Caddy or nginx. -1. **Create `.env` next to `docker-compose.yml`:** +1. **Set up HTTPS** — follow [Public Access Guide](12-public-access.md) to + get `https://ov.your-domain.com` working (Caddy + `.env` + + `docker compose up`). - ```dotenv - OPENVIKING_PUBLIC_BASE_URL=https://ov.your-domain.com - OV_ACME_EMAIL=admin@your-domain.com # optional; recommended for Let's Encrypt - ``` - -2. **Drop a `Caddyfile` next to `docker-compose.yml`:** - - ```caddyfile - {$OPENVIKING_PUBLIC_BASE_URL} { - @console path /console /console/* - handle @console { - reverse_proxy openviking:8020 - } - handle { - reverse_proxy openviking:1933 - } - } - ``` - -3. **Enable OAuth in `~/.openviking/ov.conf`** (the server otherwise ignores - the env var and the `/oauth/*` routes don't mount): +2. **Enable OAuth in `~/.openviking/ov.conf`:** ```json { "oauth": { "enabled": true } } ``` -4. **Uncomment the `caddy:` service and the `volumes:` block** at the bottom - of `docker-compose.yml`, point DNS at this host, then: +3. **Restart** (`docker compose restart openviking`). - ```bash - docker compose up -d - ``` - -5. **Connect a client.** In Claude.ai → Connectors → Add, enter +4. **Connect a client.** In Claude.ai → Connectors → Add, enter `https://ov.your-domain.com/mcp`. The browser will open an authorize page that displays a 6-character code; open `https://ov.your-domain.com/console`, sign in with your API key, paste @@ -57,8 +35,8 @@ docker compose. browser flips back to Claude.ai and the connector is live. That's the whole production path. The rest of this guide explains why each -piece exists, how to do it without docker compose, and how to verify with -curl when something doesn't work. +piece exists, how to test locally without HTTPS, and how to verify with curl +when something doesn't work. --- @@ -133,7 +111,13 @@ endpoints, so this mode is only useful for local testing with tools like } ``` -2. **Start the API server (port 1933) and the console (port 8020):** +2. **Start the services:** + + ```bash + docker compose up -d + ``` + + Or without Docker: ```bash openviking-server @@ -141,182 +125,35 @@ endpoints, so this mode is only useful for local testing with tools like python -m openviking.console.bootstrap --write-enabled ``` -3. **Sign in to the console** at , paste your - API key into Settings, click Save. The "Authorize an MCP client" form is - now available. +3. **Sign in to the console** at (or + `:8020/console` if running without the aggregated proxy), paste your API + key into Settings, click Save. The "Authorize an MCP client" form is now + available. 4. **Connect a local MCP client** (e.g. MCP Inspector) to - `http://127.0.0.1:1933/mcp`. The client will hit the OAuth flow above; copy - the 6-character code from the authorize page into the console form, click - Authorize, and the client will receive a token. + `http://127.0.0.1:1934/mcp` (or `:1933/mcp`). The client will hit the + OAuth flow above; copy the 6-character code from the authorize page into + the console form, click Authorize, and the client will receive a token. -For Claude.ai / Claude Desktop, continue to the production deployment below. +For Claude.ai / Claude Desktop on the public internet, see the +[Public Access Guide](12-public-access.md). --- ## Production deployment (HTTPS) -For OAuth clients on the public internet you need: +OAuth 2.1 **requires HTTPS** for any non-localhost issuer. The +[Public Access Guide](12-public-access.md) covers the full setup — Caddy, +nginx, docker compose, CDN — in detail. The short version: -1. A public domain pointing at your server (`my.ov` in the examples below) -2. TLS termination at a reverse proxy (Caddy or nginx) -3. Both `openviking-server` (1933) and the console (8020) running locally -4. The reverse proxy fronting them on the same domain so the OAuth page and - the console can share a session origin +1. Follow [Public Access Guide § Adding HTTPS](12-public-access.md#adding-https-for-public-access) + to get `https://your-domain.com` serving port 1934 over TLS. +2. Enable OAuth: `{ "oauth": { "enabled": true } }` in `ov.conf`. +3. Restart: `docker compose restart openviking`. +4. Set `OPENVIKING_PUBLIC_BASE_URL=https://your-domain.com` in `.env` (the + server uses this as the issuer in OAuth metadata and `WWW-Authenticate`). -### Why a reverse proxy - -The MCP and OAuth endpoints **must live at the public domain root**: - -| Path | Required by | -|---|---| -| `/.well-known/oauth-authorization-server` | RFC 8414 — clients append this path to the issuer URL | -| `/.well-known/oauth-protected-resource` | RFC 9728 — discovered via the 401 `WWW-Authenticate` hint | -| `/register`, `/authorize`, `/token` | MCP SDK, mounted at root | -| `/mcp` | Default MCP convention | - -This means **`openviking-server` (1933) is the "root" service**, and the -console (8020) lives at the `/console/...` sub-path (it was designed this way: -all of its static asset URLs are hard-coded to `/console/styles.css`, -`/console/app.js`, etc.). - -### Tell the server its public origin - -The OAuth subsystem publishes URLs that contain the public host name (issuer, -PRM, `WWW-Authenticate`). Resolution order, highest-priority first: - -1. `OPENVIKING_PUBLIC_BASE_URL` environment variable -2. `oauth.issuer` in `ov.conf` -3. `X-Forwarded-Proto` + `X-Forwarded-Host` request headers -4. The request `Host` header - -Set option 1 or 2 explicitly when you're behind a reverse proxy. Either: - -```bash -# Process env (systemd, docker, …) -export OPENVIKING_PUBLIC_BASE_URL="https://my.ov" -``` - -```jsonc -// ov.conf -{ - "oauth": { - "enabled": true, - "issuer": "https://my.ov" - } -} -``` - -### Reverse proxy config — Caddy (recommended) - -Caddy obtains and renews Let's Encrypt certificates automatically. The -console expects to live under `/console/...` (its HTML hard-codes -`/console/styles.css`, `/console/app.js`, etc.) — use `handle` rather than -`handle_path` so the prefix is **not** stripped before forwarding. - -`/etc/caddy/Caddyfile`: - -```caddyfile -my.ov { - @console path /console /console/* - handle @console { - reverse_proxy 127.0.0.1:8020 - } - handle { - # Everything else: MCP, OAuth, REST, .well-known - reverse_proxy 127.0.0.1:1933 - } -} -``` - -After `caddy reload`, browse to , sign in with your -API key, and the OAuth flow is ready. Caddy adds `X-Forwarded-Proto` and -`X-Forwarded-Host` automatically. - -### Reverse proxy config — nginx - -```nginx -server { - listen 443 ssl http2; - server_name my.ov; - - ssl_certificate /etc/letsencrypt/live/my.ov/fullchain.pem; - ssl_certificate_key /etc/letsencrypt/live/my.ov/privkey.pem; - - # 8020 console at /console/... - location /console { - proxy_pass http://127.0.0.1:8020; - proxy_set_header Host $host; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header X-Forwarded-Host $host; - } - - # Everything else → 1933 (MCP, OAuth, REST, .well-known) - location / { - proxy_pass http://127.0.0.1:1933; - proxy_set_header Host $host; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header X-Forwarded-Host $host; - } -} - -# HTTP → HTTPS redirect -server { - listen 80; - server_name my.ov; - return 301 https://$host$request_uri; -} -``` - -You don't need `proxy_pass_header Authorization` or `proxy_pass_header -X-Api-Key` — those directives forward upstream **response** headers, while -client request headers are forwarded to the upstream by default. - -### Docker Compose - -The shipped `docker-compose.yml` includes a commented-out Caddy service. To -go from "local 1933/8020" to "public HTTPS at `https://my.ov`": - -1. **Create a `.env`** next to `docker-compose.yml`. The same - `OPENVIKING_PUBLIC_BASE_URL` is read by both the OpenViking container and - Caddy — set it once: - - ```dotenv - OPENVIKING_PUBLIC_BASE_URL=https://ov.your-domain.com - OV_ACME_EMAIL=admin@your-domain.com # optional; recommended for Let's Encrypt - ``` - -2. **Drop a `Caddyfile`** next to `docker-compose.yml`. Caddy accepts a full - URL as the site address (the `https://` prefix tells it to enable HTTPS): - - ```caddyfile - {$OPENVIKING_PUBLIC_BASE_URL} { - @console path /console /console/* - handle @console { - reverse_proxy openviking:8020 - } - handle { - reverse_proxy openviking:1933 - } - } - ``` - - To pin Let's Encrypt registration to a specific email, add `tls - {$OV_ACME_EMAIL}` inside the site block. - -3. **Uncomment the `caddy:` service and the `volumes:` block** at the bottom - of `docker-compose.yml`. - -4. **Point DNS** at this host's public IP and ensure ports 80/443 are - reachable. - -5. `docker compose up -d`. The first HTTPS request triggers ACME issuance; - subsequent requests are cached. - -> The Caddy service uses the compose network's container DNS, so -> `reverse_proxy openviking:8020` works without exposing 8020 publicly. You -> can drop the host-side port mappings (`"8020:8020"`, `"1933:1933"`) once -> Caddy is the public entry point. +Once HTTPS + OAuth are both up, connect clients as described below. --- @@ -501,10 +338,10 @@ curl -i https://my.ov/mcp -d '{}' -H 'Content-Type: application/json' | grep -i ## References +- [Public Access & Reverse Proxy Guide](12-public-access.md) — HTTPS, Caddy, nginx, docker compose - [MCP Specification — Authorization](https://modelcontextprotocol.io/specification/2025-03-26/server/authorization) - [RFC 8414 — OAuth 2.0 Authorization Server Metadata](https://datatracker.ietf.org/doc/html/rfc8414) - [RFC 9728 — OAuth 2.0 Protected Resource Metadata](https://datatracker.ietf.org/doc/html/rfc9728) - [RFC 7591 — Dynamic Client Registration](https://datatracker.ietf.org/doc/html/rfc7591) - [RFC 7636 — PKCE](https://datatracker.ietf.org/doc/html/rfc7636) -- [Caddyfile syntax](https://caddyserver.com/docs/caddyfile) - [OpenViking MCP Integration Guide](06-mcp-integration.md) diff --git a/docs/en/guides/12-public-access.md b/docs/en/guides/12-public-access.md new file mode 100644 index 000000000..d770092a5 --- /dev/null +++ b/docs/en/guides/12-public-access.md @@ -0,0 +1,262 @@ +# Public Access & Reverse Proxy + +OpenViking runs two internal services: + +| Port | Service | Handles | +|------|---------|---------| +| 1933 | API server | REST API, MCP, OAuth, `.well-known/*` | +| 8020 | Console | Web UI at `/console/...` | + +The bundled **Caddy reverse proxy** merges them into a single port — **1934** — so +clients only need one URL. This works out of the box with `docker compose up`. + +## Port overview + +``` + ┌────────────────────────┐ +Internet / LAN ──► │ Caddy :1934 (HTTP) │ + │ │ + │ /console/* → :8020 │ + │ /* → :1933 │ + └────────────────────────┘ +``` + +Port 1934 is plain HTTP — fine for local development, internal networks, and +as an upstream target behind your own TLS-terminating proxy or CDN. + +For **public HTTPS** (required for OAuth with MCP clients like Claude.ai, +ChatGPT, Cursor), Caddy can also serve port 443 with automatic Let's Encrypt +certificates. See [Adding HTTPS](#adding-https-for-public-access) below. + +## Quick start (local / internal) + +```bash +docker compose up -d +``` + +Both services are now reachable on one port: + +```bash +curl http://localhost:1934/health # API server health +curl http://localhost:1934/api/v1/system/status \ + -H "X-Api-Key: YOUR_KEY" # REST API +open http://localhost:1934/console # Web console +``` + +You can still access 1933 and 8020 directly — those port mappings are left in +`docker-compose.yml` for debugging. Once you're confident everything works +through 1934, feel free to comment them out. + +## Adding HTTPS for public access + +You need: a public domain, ports 80 + 443 reachable, DNS pointing here. + +### 1. Create `.env` + +```dotenv +OPENVIKING_PUBLIC_BASE_URL=https://ov.your-domain.com +OV_ACME_EMAIL=admin@your-domain.com # optional; recommended for Let's Encrypt +``` + +`OPENVIKING_PUBLIC_BASE_URL` is read by both the OpenViking container (it +publishes this URL in OAuth metadata and `WWW-Authenticate` headers) and by +Caddy (as the site address for the HTTPS block). + +### 2. Add a domain block to `Caddyfile` + +Append this below the existing `:1934` block: + +```caddyfile +{$OPENVIKING_PUBLIC_BASE_URL} { + @console path /console /console/* + handle @console { + reverse_proxy openviking:8020 + } + handle { + reverse_proxy openviking:1933 + } + # Pin ACME registration email (optional): + # tls {$OV_ACME_EMAIL} +} +``` + +The `:1934` block stays — it continues to serve HTTP for local/internal +access. The new block serves HTTPS on 443 for the public domain. + +### 3. Uncomment HTTPS lines in `docker-compose.yml` + +Three places: + +```yaml +# In caddy.ports — uncomment: +- "80:80" +- "443:443" + +# In caddy.volumes — uncomment: +- caddy_data:/data +- caddy_config:/config + +# At the bottom — uncomment: +volumes: + caddy_data: + caddy_config: +``` + +### 4. Launch + +```bash +docker compose up -d +``` + +The first HTTPS request triggers ACME certificate issuance. Subsequent +requests use the cached cert. Caddy handles renewal automatically. + +### 5. Verify + +```bash +curl https://ov.your-domain.com/health +# {"status": "ok"} + +# OAuth metadata (if oauth.enabled = true): +curl https://ov.your-domain.com/.well-known/oauth-authorization-server +``` + +## Using your own reverse proxy + +If you already have nginx, Traefik, or another proxy handling TLS, point it +at port 1934 instead of juggling 1933 + 8020 separately. Port 1934 already +does the path-based routing internally. + +### nginx (TLS termination at nginx, plain HTTP to 1934) + +```nginx +server { + listen 443 ssl http2; + server_name ov.your-domain.com; + + ssl_certificate /etc/letsencrypt/live/ov.your-domain.com/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/ov.your-domain.com/privkey.pem; + + location / { + proxy_pass http://127.0.0.1:1934; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Host $host; + } +} + +server { + listen 80; + server_name ov.your-domain.com; + return 301 https://$host$request_uri; +} +``` + +With this setup, remove the Caddy HTTPS block from the Caddyfile — keep only +the `:1934` block. + +### Caddy (external, without docker-compose Caddy) + +If you run Caddy on the host rather than inside compose: + +```caddyfile +ov.your-domain.com { + reverse_proxy 127.0.0.1:1934 +} +``` + +### Cloudflare / CDN + +Point the CDN origin at `http://your-server-ip:1934`. Set +`OPENVIKING_PUBLIC_BASE_URL=https://ov.your-domain.com` so the server knows +its public-facing origin. Ensure the CDN forwards `Host`, +`X-Forwarded-Proto`, and `X-Forwarded-Host` headers. + +## Tell the server its public URL + +OAuth metadata, `WWW-Authenticate` headers, and resource URLs all need to +contain the public-facing origin. Resolution order (highest priority first): + +1. `OPENVIKING_PUBLIC_BASE_URL` environment variable +2. `oauth.issuer` in `ov.conf` +3. `X-Forwarded-Proto` + `X-Forwarded-Host` request headers +4. The request `Host` header + +Behind any reverse proxy, set option 1 explicitly: + +```bash +export OPENVIKING_PUBLIC_BASE_URL="https://ov.your-domain.com" +``` + +Or in `ov.conf`: + +```jsonc +{ + "oauth": { + "enabled": true, + "issuer": "https://ov.your-domain.com" + } +} +``` + +## HTTPS and OAuth + +OAuth 2.1 (and the MCP SDK) **requires HTTPS** for any non-localhost issuer. +If the server's published origin is `http://` on a non-loopback address, MCP +clients will refuse to connect with an "Issuer URL must be HTTPS" error. + +This is a protocol-level requirement, not an OpenViking limitation. For local +testing, `http://127.0.0.1:1934` works without HTTPS. For anything else, set +up TLS as described above. + +Non-OAuth API access (API key auth) works fine over HTTP if you accept the +risk — the protocol doesn't enforce TLS for bearer tokens. + +## Without Docker + +If you run `openviking-server` and the console directly (systemd, bare +metal, etc.), install Caddy on the host and use the same Caddyfile pattern +with `127.0.0.1` upstreams: + +```caddyfile +:1934 { + @console path /console /console/* + handle @console { + reverse_proxy 127.0.0.1:8020 + } + handle { + reverse_proxy 127.0.0.1:1933 + } +} + +# Add domain block for HTTPS if needed: +# ov.your-domain.com { ... } +``` + +Or with nginx: + +```nginx +server { + listen 1934; + + location /console { + proxy_pass http://127.0.0.1:8020; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Host $host; + } + + location / { + proxy_pass http://127.0.0.1:1933; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Host $host; + } +} +``` + +## Related + +- [Deployment Guide](03-deployment.md) — Docker, systemd, Kubernetes +- [OAuth Guide](11-oauth.md) — OAuth 2.1 configuration and client setup +- [Authentication](04-authentication.md) — API key management diff --git a/docs/zh/guides/11-oauth.md b/docs/zh/guides/11-oauth.md index b7a23b742..67e6bc097 100644 --- a/docs/zh/guides/11-oauth.md +++ b/docs/zh/guides/11-oauth.md @@ -8,52 +8,29 @@ token、metadata)由官方 `mcp.server.auth` SDK 提供,整体遵循 OAuth 2 ## 推荐配置 -下面是能让 Claude.ai 等公网客户端跑通的最小配置。前提:你有一个公网域名, -80/443 端口可达,并安装了 docker compose。 +> **前提**:公网 HTTPS。OAuth 2.1(以及 MCP SDK)对非 localhost 的 issuer +> **强制要求 HTTPS**。请参阅[公网访问指南](12-public-access.md)了解如何配置 +> Caddy 或 nginx 的 HTTPS。 -1. **在 `docker-compose.yml` 旁创建 `.env`:** +1. **配置 HTTPS** — 按[公网访问指南](12-public-access.md)设置好 + `https://ov.your-domain.com`(Caddy + `.env` + `docker compose up`)。 - ```dotenv - OPENVIKING_PUBLIC_BASE_URL=https://ov.your-domain.com - OV_ACME_EMAIL=admin@your-domain.com # 可选;推荐用于 Let's Encrypt - ``` - -2. **在 `docker-compose.yml` 旁放一份 `Caddyfile`:** - - ```caddyfile - {$OPENVIKING_PUBLIC_BASE_URL} { - @console path /console /console/* - handle @console { - reverse_proxy openviking:8020 - } - handle { - reverse_proxy openviking:1933 - } - } - ``` - -3. **在 `~/.openviking/ov.conf` 启用 OAuth**(不开的话服务端会忽略 env - 变量,`/oauth/*` 路由也不会挂载): +2. **在 `~/.openviking/ov.conf` 启用 OAuth:** ```json { "oauth": { "enabled": true } } ``` -4. **取消 `docker-compose.yml` 末尾的 `caddy:` service 和 `volumes:` 段的 - 注释**,DNS 指向本机,然后: +3. **重启**(`docker compose restart openviking`)。 - ```bash - docker compose up -d - ``` - -5. **接入客户端。** Claude.ai → Connectors → Add → 输入 +4. **接入客户端。** Claude.ai → Connectors → Add → 输入 `https://ov.your-domain.com/mcp`。浏览器打开授权页,显示一个 6 字符码; 再打开 `https://ov.your-domain.com/console`,用 API Key 登录,把码粘到 Settings → "Authorize an MCP client" → 点 Authorize。浏览器自动跳回 Claude.ai,连接器就位。 -线上路径就这五步。后续章节解释每一块为什么这样设计、不用 docker compose -怎么做、出问题时怎么用 curl 排查。 +线上路径就这四步。后续章节解释每一块为什么这样设计、本地怎么不走 HTTPS 做 +联调、出问题时怎么用 curl 排查。 --- @@ -125,7 +102,13 @@ Claude.ai / Claude Desktop 等线上客户端**只接受公网 HTTPS**,所以 } ``` -2. **同时启动 API server (1933) 和 console (8020):** +2. **启动:** + + ```bash + docker compose up -d + ``` + + 或不用 Docker: ```bash openviking-server @@ -133,170 +116,33 @@ Claude.ai / Claude Desktop 等线上客户端**只接受公网 HTTPS**,所以 python -m openviking.console.bootstrap --write-enabled ``` -3. **登录 console**:访问 ,把 API Key 粘到 - Settings → 点 Save。"Authorize an MCP client" 表单现在可用。 +3. **登录 console**:访问 (不走聚合代理时用 + `:8020/console`),把 API Key 粘到 Settings → 点 Save。"Authorize an MCP + client" 表单现在可用。 4. **接一个本地 MCP 客户端**(例如 MCP Inspector)到 - `http://127.0.0.1:1933/mcp`。客户端会走上面那套流程;把 authorize 页显示 - 的 6 字符码复制到 console 表单 → 点 Authorize → 客户端拿到 token。 + `http://127.0.0.1:1934/mcp`(或 `:1933/mcp`)。客户端会走上面那套流程;把 + authorize 页显示的 6 字符码复制到 console 表单 → 点 Authorize → 客户端拿到 + token。 -线上接 Claude.ai / Claude Desktop 走下面的 HTTPS 部署。 +线上接 Claude.ai / Claude Desktop 走[公网访问指南](12-public-access.md)。 --- ## 生产部署(HTTPS) -线上 OAuth 客户端需要: +OAuth 2.1 对非 localhost 的 issuer **强制要求 HTTPS**。 +[公网访问指南](12-public-access.md)详细介绍了 Caddy、nginx、docker compose、 +CDN 的配置方法。简要步骤: -1. 一个公网域名指向你的服务器(下面例子里用 `my.ov`) -2. 在反代层做 TLS 终止(Caddy 或 nginx) -3. 本机同时跑 `openviking-server` (1933) 和 console (8020) -4. 反代把两者放到**同一域名**下,OAuth 页才能与 console 同源(quick-authorize 才能用) +1. 按[公网访问指南 § 添加 HTTPS](12-public-access.md#添加-https公网访问) + 配置好 `https://your-domain.com`,使 1934 端口走 TLS。 +2. 启用 OAuth:`ov.conf` 里 `{ "oauth": { "enabled": true } }`。 +3. 重启:`docker compose restart openviking`。 +4. 在 `.env` 设置 `OPENVIKING_PUBLIC_BASE_URL=https://your-domain.com` + (服务端用它作为 OAuth 元数据和 `WWW-Authenticate` 的 issuer)。 -### 为什么需要反代 - -MCP 与 OAuth 端点**必须挂在公网域名根下**: - -| 路径 | 来源 | -|---|---| -| `/.well-known/oauth-authorization-server` | RFC 8414 — 客户端拼 issuer URL + 此路径 | -| `/.well-known/oauth-protected-resource` | RFC 9728 — 通过 401 `WWW-Authenticate` 头发现 | -| `/register`、`/authorize`、`/token` | MCP SDK 默认挂在根 | -| `/mcp` | MCP 默认惯例 | - -所以 **`openviking-server` (1933) 是"根域服务"**,console (8020) 住在 -`/console/...` 子路径下(这是 console 现状:HTML 里硬编码了 -`/console/styles.css`、`/console/app.js` 等等)。 - -### 告诉服务端自己的公网地址 - -OAuth 子系统会在多处发布带域名的 URL(issuer、PRM、`WWW-Authenticate`)。 -解析顺序,**优先级从高到低**: - -1. `OPENVIKING_PUBLIC_BASE_URL` 环境变量 -2. `ov.conf` 里的 `oauth.issuer` -3. `X-Forwarded-Proto` + `X-Forwarded-Host` 请求头 -4. 请求的 `Host` 头 - -反代后强烈建议显式设置 1 或 2,二选一: - -```bash -# 进程环境(systemd / docker / …) -export OPENVIKING_PUBLIC_BASE_URL="https://my.ov" -``` - -```jsonc -// ov.conf -{ - "oauth": { - "enabled": true, - "issuer": "https://my.ov" - } -} -``` - -### 反代配置 — Caddy(推荐) - -Caddy 自动签发并续期 Let's Encrypt 证书。console 自己住在 `/console/...` 下 -(HTML 里硬编码),所以用 `handle` 而不是 `handle_path`,**不**剥前缀。 - -`/etc/caddy/Caddyfile`: - -```caddyfile -my.ov { - @console path /console /console/* - handle @console { - reverse_proxy 127.0.0.1:8020 - } - handle { - # 其他都走 1933:MCP、OAuth、REST、.well-known - reverse_proxy 127.0.0.1:1933 - } -} -``` - -`caddy reload` 后访问 ,登录 API Key,OAuth 流程 -就绪。Caddy 自动加 `X-Forwarded-Proto` 与 `X-Forwarded-Host`。 - -### 反代配置 — nginx - -```nginx -server { - listen 443 ssl http2; - server_name my.ov; - - ssl_certificate /etc/letsencrypt/live/my.ov/fullchain.pem; - ssl_certificate_key /etc/letsencrypt/live/my.ov/privkey.pem; - - # 8020 console 在 /console/... 下 - location /console { - proxy_pass http://127.0.0.1:8020; - proxy_set_header Host $host; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header X-Forwarded-Host $host; - } - - # 其他都走 1933(MCP、OAuth、REST、.well-known) - location / { - proxy_pass http://127.0.0.1:1933; - proxy_set_header Host $host; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header X-Forwarded-Host $host; - } -} - -# HTTP → HTTPS 跳转 -server { - listen 80; - server_name my.ov; - return 301 https://$host$request_uri; -} -``` - -不需要 `proxy_pass_header Authorization` 或 `proxy_pass_header X-Api-Key` — -那些指令是用来透传**上游响应**头的;客户端请求头(Authorization、X-Api-Key) -默认就会被原样转发到上游。 - -### Docker Compose - -仓库里的 `docker-compose.yml` 已带一份注释掉的 Caddy service。把 -"本地 1933/8020" 升到 "公网 HTTPS at `https://my.ov`" 的步骤: - -1. **在 `docker-compose.yml` 旁创建 `.env`**。`OPENVIKING_PUBLIC_BASE_URL` - 是公网地址的唯一来源,OpenViking 容器和 Caddy 都读它,只需设置一次: - - ```dotenv - OPENVIKING_PUBLIC_BASE_URL=https://ov.your-domain.com - OV_ACME_EMAIL=admin@your-domain.com # 可选;推荐用于 Let's Encrypt - ``` - -2. **在 `docker-compose.yml` 旁放一份 `Caddyfile`**。Caddy 接受完整 URL 作 - 为 site 地址(`https://` 前缀会启用自动 HTTPS): - - ```caddyfile - {$OPENVIKING_PUBLIC_BASE_URL} { - @console path /console /console/* - handle @console { - reverse_proxy openviking:8020 - } - handle { - reverse_proxy openviking:1933 - } - } - ``` - - 想把 Let's Encrypt 注册绑定到特定邮箱,可以在 site 块里加 - `tls {$OV_ACME_EMAIL}`。 - -3. **取消 `docker-compose.yml` 末尾的 `caddy:` service 和 `volumes:` 段的注释。** - -4. **DNS** 指向本机公网 IP,确保 80/443 端口能进。 - -5. `docker compose up -d`。首次 HTTPS 请求会触发 ACME 签发,之后会缓存。 - -> Caddy service 通过 compose 的容器 DNS 直连,`reverse_proxy openviking:8020` -> 不需要把 8020 暴露到 host。Caddy 接管公网入口后,可以删掉 -> `"8020:8020"` 与 `"1933:1933"` 这两条 host 端口映射。 +HTTPS + OAuth 就绪后,按下面的方式接入客户端。 --- @@ -470,10 +316,10 @@ curl -i https://my.ov/mcp -d '{}' -H 'Content-Type: application/json' | grep -i ## 参考 +- [公网访问与反向代理指南](12-public-access.md) — HTTPS、Caddy、nginx、docker compose - [MCP 规范 — Authorization](https://modelcontextprotocol.io/specification/2025-03-26/server/authorization) - [RFC 8414 — OAuth 2.0 Authorization Server Metadata](https://datatracker.ietf.org/doc/html/rfc8414) - [RFC 9728 — OAuth 2.0 Protected Resource Metadata](https://datatracker.ietf.org/doc/html/rfc9728) - [RFC 7591 — Dynamic Client Registration](https://datatracker.ietf.org/doc/html/rfc7591) - [RFC 7636 — PKCE](https://datatracker.ietf.org/doc/html/rfc7636) -- [Caddyfile 语法](https://caddyserver.com/docs/caddyfile) - [OpenViking MCP 集成指南](06-mcp-integration.md) diff --git a/docs/zh/guides/12-public-access.md b/docs/zh/guides/12-public-access.md new file mode 100644 index 000000000..93f7409f8 --- /dev/null +++ b/docs/zh/guides/12-public-access.md @@ -0,0 +1,252 @@ +# 公网访问与反向代理 + +OpenViking 运行两个内部服务: + +| 端口 | 服务 | 处理内容 | +|------|------|---------| +| 1933 | API 服务 | REST API、MCP、OAuth、`.well-known/*` | +| 8020 | Console | Web 管理界面,路径前缀 `/console/...` | + +自带的 **Caddy 反向代理**将两者合并到一个端口 — **1934** — 客户端只需一个 +URL。`docker compose up` 即可开箱使用。 + +## 端口总览 + +``` + ┌────────────────────────┐ +Internet / LAN ──► │ Caddy :1934 (HTTP) │ + │ │ + │ /console/* → :8020 │ + │ /* → :1933 │ + └────────────────────────┘ +``` + +1934 端口是纯 HTTP — 适用于本地开发、内网环境,也可以作为外部 TLS 终止代理 +或 CDN 的上游。 + +如果需要**公网 HTTPS**(Claude.ai、ChatGPT、Cursor 等 MCP 客户端走 OAuth 时 +必须),Caddy 还能在 443 端口自动签发 Let's Encrypt 证书。见下方 +[添加 HTTPS](#添加-https-公网访问)。 + +## 快速开始(本地 / 内网) + +```bash +docker compose up -d +``` + +两个服务在一个端口上可达: + +```bash +curl http://localhost:1934/health # API 健康检查 +curl http://localhost:1934/api/v1/system/status \ + -H "X-Api-Key: YOUR_KEY" # REST API +open http://localhost:1934/console # Web Console +``` + +1933 和 8020 仍可直接访问 — `docker-compose.yml` 里保留了端口映射方便调试。 +确认 1934 一切正常后,可以把那两行注释掉。 + +## 添加 HTTPS(公网访问) + +前提:有公网域名、80 + 443 端口可达、DNS 已指向。 + +### 1. 创建 `.env` + +```dotenv +OPENVIKING_PUBLIC_BASE_URL=https://ov.your-domain.com +OV_ACME_EMAIL=admin@your-domain.com # 可选;推荐用于 Let's Encrypt +``` + +`OPENVIKING_PUBLIC_BASE_URL` 同时被 OpenViking 容器(发布在 OAuth 元数据和 +`WWW-Authenticate` 头中)和 Caddy(作为 HTTPS 站点地址)读取。 + +### 2. 在 `Caddyfile` 追加域名块 + +在已有的 `:1934` 块下面添加: + +```caddyfile +{$OPENVIKING_PUBLIC_BASE_URL} { + @console path /console /console/* + handle @console { + reverse_proxy openviking:8020 + } + handle { + reverse_proxy openviking:1933 + } + # 绑定 ACME 注册邮箱(可选): + # tls {$OV_ACME_EMAIL} +} +``` + +`:1934` 块保留 — 继续为本地/内网提供 HTTP 访问。新块在 443 端口为公网域名 +提供 HTTPS。 + +### 3. 取消 `docker-compose.yml` 中的 HTTPS 注释 + +三处: + +```yaml +# caddy.ports 里取消注释: +- "80:80" +- "443:443" + +# caddy.volumes 里取消注释: +- caddy_data:/data +- caddy_config:/config + +# 文件末尾取消注释: +volumes: + caddy_data: + caddy_config: +``` + +### 4. 启动 + +```bash +docker compose up -d +``` + +首次 HTTPS 请求触发 ACME 证书签发,后续使用缓存。Caddy 自动续期。 + +### 5. 验证 + +```bash +curl https://ov.your-domain.com/health +# {"status": "ok"} + +# OAuth 元数据(如果 oauth.enabled = true): +curl https://ov.your-domain.com/.well-known/oauth-authorization-server +``` + +## 使用自己的反向代理 + +如果你已有 nginx、Traefik 或其他 TLS 终止代理,直接指向 1934 端口,不用分别 +处理 1933 + 8020。1934 内部已经做好了路径路由。 + +### nginx(TLS 在 nginx 终止,HTTP 到 1934) + +```nginx +server { + listen 443 ssl http2; + server_name ov.your-domain.com; + + ssl_certificate /etc/letsencrypt/live/ov.your-domain.com/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/ov.your-domain.com/privkey.pem; + + location / { + proxy_pass http://127.0.0.1:1934; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Host $host; + } +} + +server { + listen 80; + server_name ov.your-domain.com; + return 301 https://$host$request_uri; +} +``` + +这种方案下,Caddyfile 只保留 `:1934` 块,不加域名块。 + +### Caddy(宿主机运行,不走 compose) + +```caddyfile +ov.your-domain.com { + reverse_proxy 127.0.0.1:1934 +} +``` + +### Cloudflare / CDN + +CDN 源站指向 `http://your-server-ip:1934`。设置 +`OPENVIKING_PUBLIC_BASE_URL=https://ov.your-domain.com` 让服务端知道自己的公网 +地址。确保 CDN 转发 `Host`、`X-Forwarded-Proto`、`X-Forwarded-Host` 头。 + +## 告诉服务端公网 URL + +OAuth 元数据、`WWW-Authenticate` 头、资源 URL 都需要包含公网 origin。 +解析顺序(**优先级从高到低**): + +1. `OPENVIKING_PUBLIC_BASE_URL` 环境变量 +2. `ov.conf` 里的 `oauth.issuer` +3. `X-Forwarded-Proto` + `X-Forwarded-Host` 请求头 +4. 请求的 `Host` 头 + +在反代后面,务必显式设置选项 1: + +```bash +export OPENVIKING_PUBLIC_BASE_URL="https://ov.your-domain.com" +``` + +或者 `ov.conf`: + +```jsonc +{ + "oauth": { + "enabled": true, + "issuer": "https://ov.your-domain.com" + } +} +``` + +## HTTPS 与 OAuth + +OAuth 2.1(以及 MCP SDK)对非 localhost 的 issuer **强制要求 HTTPS**。 +如果服务端发布的 origin 是非回环地址的 `http://`,MCP 客户端会拒绝连接并报 +"Issuer URL must be HTTPS"。 + +这是协议层面的要求,不是 OpenViking 的限制。本地测试时 +`http://127.0.0.1:1934` 无需 HTTPS。其他场景请按上面的方式配置 TLS。 + +非 OAuth 的 API 访问(API Key 认证)在 HTTP 下也能正常工作 — 协议本身不强制 +bearer token 走 TLS,用户自行评估风险。 + +## 不用 Docker + +如果直接运行 `openviking-server` 和 console(systemd、裸机等),在宿主机装 +Caddy 并使用相同的 Caddyfile 模式,上游改为 `127.0.0.1`: + +```caddyfile +:1934 { + @console path /console /console/* + handle @console { + reverse_proxy 127.0.0.1:8020 + } + handle { + reverse_proxy 127.0.0.1:1933 + } +} + +# 需要 HTTPS 时追加域名块: +# ov.your-domain.com { ... } +``` + +或者 nginx: + +```nginx +server { + listen 1934; + + location /console { + proxy_pass http://127.0.0.1:8020; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Host $host; + } + + location / { + proxy_pass http://127.0.0.1:1933; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Host $host; + } +} +``` + +## 相关文档 + +- [部署指南](03-deployment.md) — Docker、systemd、Kubernetes +- [OAuth 指南](11-oauth.md) — OAuth 2.1 配置与客户端接入 +- [认证](04-authentication.md) — API Key 管理