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
This commit is contained in:
zhengxiao.wu
2026-05-06 19:12:52 +08:00
parent 029d89f860
commit c5cb241ff8
7 changed files with 663 additions and 439 deletions
+33
View File
@@ -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
}
}
+35 -46
View File
@@ -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:
+7 -2
View File
@@ -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
+39 -202
View File
@@ -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 <http://127.0.0.1:8020/console>, paste your
API key into Settings, click Save. The "Authorize an MCP client" form is
now available.
3. **Sign in to the console** at <http://127.0.0.1:1934/console> (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 <https://my.ov/console>, 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)
+262
View File
@@ -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
+35 -189
View File
@@ -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**:访问 <http://127.0.0.1:8020/console>,把 API Key 粘到
Settings → 点 Save。"Authorize an MCP client" 表单现在可用。
3. **登录 console**:访问 <http://127.0.0.1:1934/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` 后访问 <https://my.ov/console>,登录 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)
+252
View File
@@ -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 管理