Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions web-studio/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
node_modules
dist
.git
.gitignore
.env
.env.*
.DS_Store
Dockerfile
.dockerignore
README.md
README_CN.md
*.log
coverage
.vite
.cache
29 changes: 29 additions & 0 deletions web-studio/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# syntax=docker/dockerfile:1.7
# Multi-stage image for OpenViking Web Studio in auto-proxy mode.
# Stage 1 builds the static SPA. Stage 2 ships only the runtime proxy +
# pre-built dist/, on node:22-alpine for a small footprint (~150 MB).

ARG NODE_VERSION=22

FROM node:${NODE_VERSION}-alpine AS build
WORKDIR /app
# Copy lockfile + manifest first so npm cache layers stay reusable.
COPY package.json package-lock.json* pnpm-lock.yaml* ./
# pnpm-lock.yaml exists in the repo but the publish flow uses npm; prefer npm ci
# for deterministic installs, fall back to npm install if the lockfile is stale.
RUN if [ -f package-lock.json ]; then npm ci; else npm install; fi
COPY . .
RUN npm run build

FROM node:${NODE_VERSION}-alpine AS runtime
ENV NODE_ENV=production \
OV_STUDIO_HOST=0.0.0.0
WORKDIR /app
COPY --from=build /app/server ./server
COPY --from=build /app/dist ./dist
COPY --from=build /app/package.json ./package.json
# proxy.mjs is zero-dep — no production node_modules needed.
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD wget -qO- "http://127.0.0.1:${PORT:-3000}/_studio/runtime-config.json" >/dev/null 2>&1 || exit 1
CMD ["node", "server/proxy.mjs"]
30 changes: 29 additions & 1 deletion web-studio/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,35 @@ server {

Do not set `VITE_OV_BASE_URL` to `https://ov.example.com/web-studio`. `/web-studio/` is only the frontend mount path; OpenViking API requests should still go to `https://ov.example.com/api/*` and `https://ov.example.com/bot/*`.

### 6. Docker Server Dependency
### 6. Auto-Proxy Mode (Single-Process Backend)

Auto-proxy mode is a public-friendly deployment where:

- The browser never receives the OpenViking root API key.
- The frontend talks to the **same origin** with no `X-API-Key` of its own.
- A thin Node.js backend (`server/proxy.mjs`) injects `X-API-Key` and forwards
requests to a configured upstream OpenViking Server.

This is the right mode when you want anyone who can open the website to
connect to the underlying OV cluster, with credentials kept on the server.

```bash
cd web-studio
npm ci
npm run build

OV_STUDIO_UPSTREAM=https://ov-api.example.com \
OV_STUDIO_API_KEY=$ROOT_API_KEY \
npm run proxy
```

When the proxy is active, Web Studio reads `GET /_studio/runtime-config.json`
at boot, hides the connection dialog form, and stops persisting credentials
in browser storage. See [`server/README.md`](server/README.md) for env vars
and threat model. Do not set `VITE_OV_BASE_URL` for auto-proxy builds — the
SPA must call same-origin paths so the proxy can inject auth headers.

### 7. Docker Server Dependency

The official OpenViking image can be used as the API server dependency:

Expand Down
24 changes: 23 additions & 1 deletion web-studio/README_CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,29 @@ server {

不要把 `VITE_OV_BASE_URL` 设置成 `https://ov.example.com/web-studio`。`/web-studio/` 只是前端挂载路径;OpenViking API 请求仍应访问 `https://ov.example.com/api/*` 和 `https://ov.example.com/bot/*`。

### 6. Docker 服务端依赖
### 6. 自动代理模式(单进程后端)

自动代理模式适用于希望公开访问的场景:

- 浏览器始终拿不到 OpenViking 的根 API Key。
- 前端只访问**同域**接口,不携带任何 `X-API-Key`。
- 一个极简的 Node.js 后端(`server/proxy.mjs`)在服务端注入 `X-API-Key` 并转发到上游 OpenViking Server。

适合让任何能打开网站的人都直接连上 OV 集群,凭据始终留在服务端。

```bash
cd web-studio
npm ci
npm run build

OV_STUDIO_UPSTREAM=https://ov-api.example.com \
OV_STUDIO_API_KEY=$ROOT_API_KEY \
npm run proxy
```

启动后 Web Studio 会在启动时读取 `GET /_studio/runtime-config.json`,自动隐藏连接对话框中的字段,并不再把任何凭据写入浏览器存储。环境变量与威胁模型见 [`server/README.md`](server/README.md)。自动代理模式下不要设置 `VITE_OV_BASE_URL`,前端需要走同源路径,代理才能注入鉴权头。

### 7. Docker 服务端依赖

官方 OpenViking 镜像可以作为 API server 依赖:

Expand Down
1 change: 1 addition & 0 deletions web-studio/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"dev": "vite dev --port 3000",
"build": "vite build",
"preview": "vite preview",
"proxy": "node server/proxy.mjs",
"test": "vitest run",
"lint": "eslint src --ignore-pattern 'src/gen/**' --ignore-pattern 'src/routeTree.gen.ts' --ignore-pattern 'src/components/ui/**'",
"format": "prettier --check 'src/**/*.{ts,tsx}'",
Expand Down
17 changes: 17 additions & 0 deletions web-studio/railway.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Railway deployment config for OpenViking Web Studio auto-proxy mode.
#
# Use with a Railway service rooted at this directory (Settings → Service →
# Root Directory = `web-studio`). Railway injects $PORT; the proxy honors it
# automatically. Set OV_STUDIO_UPSTREAM and OV_STUDIO_API_KEY as service
# variables before the first deploy.

[build]
builder = "DOCKERFILE"
dockerfilePath = "Dockerfile"

[deploy]
startCommand = "node server/proxy.mjs"
restartPolicyType = "ON_FAILURE"
restartPolicyMaxRetries = 5
healthcheckPath = "/_studio/runtime-config.json"
healthcheckTimeout = 10
104 changes: 104 additions & 0 deletions web-studio/server/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# Web Studio Auto-Proxy Server

`server/proxy.mjs` is an optional, zero-dependency Node.js server that lets you
deploy Web Studio as a public site without exposing the OpenViking root API
key to the browser.

It does three things:

1. Serves the built `dist/` SPA bundle on the same origin.
2. Proxies OpenViking API paths to a configured upstream OV server, injecting
`X-API-Key` (and optional account / user) server-side. Incoming
`X-API-Key`, `Authorization`, `X-OpenViking-Account`, and `X-OpenViking-User`
headers are stripped before forwarding, so the browser can't override the
server-managed identity.
3. Publishes `/_studio/runtime-config.json` so the SPA knows it is in
"auto-proxy mode" and:
- hides the connection dialog form,
- stops sending `X-API-Key` from the browser,
- never persists credentials in `localStorage` / `sessionStorage`.

## Quick start

```bash
cd web-studio
npm ci
npm run build

OV_STUDIO_UPSTREAM=https://ov-api.example.com \
OV_STUDIO_API_KEY=$ROOT_API_KEY \
npm run proxy
```

Then open <http://localhost:3000>.

## Configuration

| Env var | Default | Purpose |
| ------------------------ | -------------------------------------------------- | ----------------------------------------------------------- |
| `OV_STUDIO_UPSTREAM` | (required) | Upstream OpenViking Server origin, e.g. `https://ov.api`. |
| `OV_STUDIO_API_KEY` | (required) | Root or scoped API key injected as `X-API-Key`. |
| `OV_STUDIO_ACCOUNT_ID` | _unset_ | If set, forwarded as `X-OpenViking-Account`. |
| `OV_STUDIO_USER_ID` | _unset_ | If set, forwarded as `X-OpenViking-User`. |
| `OV_STUDIO_HOST` | `0.0.0.0` | Bind host. |
| `OV_STUDIO_PORT` | `3000` | Bind port. |
| `OV_STUDIO_DIST_DIR` | `<web-studio>/dist` | Path to the built SPA. |
| `OV_STUDIO_PROXY_PATHS` | `/api,/bot,/health,/ready,/openapi.json` | Path prefixes proxied to upstream. |
| `OV_STUDIO_CORS_ORIGINS` | _empty_ | Comma-separated allowlist; `*` allows any. Same-origin only by default. |
| `OV_STUDIO_BASE_PATH` | `/` | SPA mount base, matches Vite `--base`. |

## Threat model

- The browser sees no API key, account, or user header.
- Anyone able to reach the proxy origin can act with the configured identity.
This is intentional for "open studio" deployments. Lock the origin down with
network policy / SSO if you need finer access control.
- The proxy strips `X-API-Key`, `Authorization`, `X-OpenViking-Account`, and
`X-OpenViking-User` from incoming requests so a malicious client can't pass
alternative credentials downstream.

## Docker

A multi-stage `Dockerfile` lives one level up at `web-studio/Dockerfile`.
Build context is the `web-studio` directory.

```bash
cd web-studio
docker build -t openviking-studio-proxy .
docker run --rm -p 3000:3000 \
-e OV_STUDIO_UPSTREAM=https://ov-api.example.com \
-e OV_STUDIO_API_KEY=$ROOT_API_KEY \
openviking-studio-proxy
```

The runtime image is `node:22-alpine` + the built SPA + `server/proxy.mjs`.
No production `node_modules` — the proxy is zero-dep — so the image stays
around 150 MB.

## Railway

`web-studio/railway.toml` wires the Dockerfile build + healthcheck.

1. On Railway, create a new service from the repo containing this branch.
2. **Service → Settings → Source** → set **Root Directory** to `web-studio`.
Railway then auto-detects the Dockerfile.
3. **Variables** → add:
- `OV_STUDIO_UPSTREAM` — your OV server origin, e.g. `https://ov.example.com`.
- `OV_STUDIO_API_KEY` — the root API key the proxy injects upstream.
- (optional) `OV_STUDIO_ACCOUNT_ID`, `OV_STUDIO_USER_ID` to pin identity.
4. Deploy. Railway injects `$PORT`; the proxy honors it automatically (no
`OV_STUDIO_PORT` needed). The generated public URL is the studio entry —
anyone who can open it acts with the configured server identity.

## Fly.io / Render / generic Docker host

Anything that runs Docker works the same way. Pass the env vars above,
expose port 3000 (or pass `PORT` / `OV_STUDIO_PORT`), and route HTTPS to the
container.

## Why not nginx?

The nginx layout in the main README is still the right answer when you already
have nginx in front. This Node script is for deployments that want the
"static frontend + thin proxy" pattern as a single self-contained process —
for example Render / Railway / Fly app instances or local demos.
Loading