Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
c155f30
feat: 第一个可以运行的 remote-control 服务器
claude-code-best Apr 8, 2026
e4e1e9c
chore: docker 部署脚本完毕
claude-code-best Apr 8, 2026
e020f39
feat: 样式和 /rc 恢复
claude-code-best Apr 8, 2026
0afac98
feat: 修整后端代码
claude-code-best Apr 8, 2026
495ce88
feat: 完成鉴权, 列表逻辑
claude-code-best Apr 8, 2026
855cc4b
fix: 修复 ask user question 的工具调用弹窗
claude-code-best Apr 8, 2026
e508941
test: add comprehensive server-side tests for remote-control-server
claude-code-best Apr 9, 2026
333ea0b
test: add route integration tests and disconnect-monitor tests
claude-code-best Apr 9, 2026
3304558
test: add session-ingress and worker-events route tests
claude-code-best Apr 9, 2026
38d5e16
test: add SSE writer, WS handler message conversion, and route edge-c…
claude-code-best Apr 9, 2026
1446742
fix: 修复历史数据回显
claude-code-best Apr 9, 2026
324103e
Potential fix for pull request finding 'CodeQL / Clear-text logging o…
claude-code-best Apr 9, 2026
b1aaefa
Potential fix for pull request finding 'CodeQL / Clear-text logging o…
claude-code-best Apr 9, 2026
d407d39
build: 调整构建过程
claude-code-best Apr 9, 2026
8c1fb25
build: 修复 alpine 权限问题
claude-code-best Apr 9, 2026
ab67e36
build: 删除权限添加
claude-code-best Apr 9, 2026
442f639
build: 修复 user 的定义问题
claude-code-best Apr 9, 2026
0f86c66
fix: 修复 crypto.randomUUID 的 https 需要
claude-code-best Apr 9, 2026
821ad5d
fix: 修复服务器的路径问题
claude-code-best Apr 9, 2026
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
11 changes: 11 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
node_modules
dist
.git
.githooks
.github
docs
*.md
packages/remote-control-server/data/*.db
packages/remote-control-server/data/*.db-wal
packages/remote-control-server/data/*.db-shm
.claude
75 changes: 75 additions & 0 deletions .github/workflows/release-rcs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
name: Release RCS Docker Image

on:
push:
tags:
- 'rcs-v*'

env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository_owner }}/remote-control-server

jobs:
build-and-push:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write

steps:
- uses: actions/checkout@v4

- name: Login to GHCR
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Extract version
id: version
run: echo "VERSION=${GITHUB_REF_NAME#rcs-v}" >> "$GITHUB_OUTPUT"

- name: Generate tags
id: tags
run: |
VERSION="${{ steps.version.outputs.VERSION }}"
IMAGE="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}"
TAGS="${IMAGE}:${VERSION}"
IFS='.' read -r MAJOR MINOR PATCH <<< "$VERSION"
if [ -n "$MAJOR" ] && [ -n "$MINOR" ]; then
TAGS="${TAGS},${IMAGE}:${MAJOR}.${MINOR}"
fi
TAGS="${TAGS},${IMAGE}:latest"
echo "tags=$TAGS" >> "$GITHUB_OUTPUT"

- name: Build Docker image
uses: docker/build-push-action@v5
with:
context: .
file: packages/remote-control-server/Dockerfile
push: false
load: true
tags: ${{ steps.tags.outputs.tags }}
build-args: VERSION=${{ steps.version.outputs.VERSION }}
cache-from: type=gha
cache-to: type=gha,mode=max

- name: Verify image
run: |
IMAGE_TAG=$(echo "${{ steps.tags.outputs.tags }}" | cut -d',' -f1)
docker run -d --name rcs-test -p 3000:3000 "$IMAGE_TAG"
sleep 5
curl -sf http://localhost:3000/health || { docker logs rcs-test; exit 1; }
docker stop rcs-test
docker rm rcs-test

- name: Push Docker image
run: |
IFS=',' read -ra TAGS <<< "${{ steps.tags.outputs.tags }}"
for TAG in "${TAGS[@]}"; do
docker push "$TAG"
done
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,5 @@ src/utils/vendor/
__pycache__/
*.pyc
logs

data
322 changes: 321 additions & 1 deletion bun.lock

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@
"check:unused": "knip-bun",
"health": "bun run scripts/health-check.ts",
"postinstall": "node scripts/postinstall.cjs",
"docs:dev": "npx mintlify dev"
"docs:dev": "npx mintlify dev",
"rcs": "bun run scripts/rcs.ts"
},
"dependencies": {},
"devDependencies": {
Expand Down
1 change: 1 addition & 0 deletions packages/remote-control-server/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
data
32 changes: 32 additions & 0 deletions packages/remote-control-server/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# ---- Stage 1: Install deps + build ----
FROM oven/bun:1 AS builder
WORKDIR /app

ARG VERSION=0.1.0

COPY packages/remote-control-server/package.json ./package.json
RUN bun install

COPY packages/remote-control-server/src ./src
RUN bun build src/index.ts --outfile=dist/server.js --target=bun \
--define "process.env.RCS_VERSION=\"${VERSION}\""

# ---- Stage 2: Runtime ----
FROM oven/bun:1-slim AS runtime

ARG VERSION=0.1.0
ENV RCS_VERSION=${VERSION}

WORKDIR /app

COPY --from=builder /app/dist/server.js ./dist/server.js
COPY packages/remote-control-server/web ./web

VOLUME /app/data

EXPOSE 3000

HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD bun run -e "fetch('http://localhost:3000/health').then(r => r.ok ? process.exit(0) : process.exit(1)).catch(() => process.exit(1))"

CMD ["bun", "run", "dist/server.js"]
Comment on lines +15 to +32
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

In the official oven/bun:1-slimimage, isbun a valid non-root user, and what is the recommended least-privilege pattern for production containers?

💡 Result:

Yes. In the oven/bun slim images, bun is a real non-root account created in the image (e.g., groupadd bun --gid 1000 && useradd bun --uid 1000 ... --create-home), with the working directory set under /home/bun/app. [1] Bun’s own Docker guide also uses USER bun in the final (“release”) stage, implying this user is intended to be used for runtime. [2]

Recommended least-privilege pattern for production containers

  • Use a multi-stage build: install/build in intermediate stages, and copy only the built artifacts + production dependencies into the final image. [2]
  • In the final runtime stage, run as non-root (e.g., USER bun, or your own dedicated runtime user) and ensure files are owned appropriately (typically via COPY --chown=...). [2]
  • Prefer a smaller, more locked-down runtime base where possible (Docker also points to Bun “Docker Hardened Images” as a production-ready option). [3]

Sources: [1] [2] [3]


Run runtime stage as non-root user

The runtime image does not set a non-root USER, which is a container hardening gap matching the static finding (DS-0002). The oven/bun:1-slim base image includes the bun non-root user, and using USER bun is the recommended least-privilege pattern for production.

Proposed fix
 FROM oven/bun:1-slim AS runtime
 
 WORKDIR /app
 
 COPY --from=builder /app/dist/server.js ./dist/server.js
 COPY packages/remote-control-server/web ./web
+RUN mkdir -p /app/data && chown -R bun:bun /app
 
 VOLUME /app/data
+USER bun
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/remote-control-server/Dockerfile` around lines 15 - 32, The runtime
Dockerfile currently runs as root; update the runtime stage to drop privileges
by switching to the existing non-root bun user (add USER bun in the runtime
stage after files are copied) and ensure the copied artifacts (dist/server.js
and web) and the /app directory are readable/executable by that user (adjust
ownership or use chown during COPY or an explicit chown RUN before switching
users); keep the HEALTHCHECK and CMD as-is but ensure they run under USER bun so
the container runs with least privilege.

167 changes: 167 additions & 0 deletions packages/remote-control-server/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
# Remote Control Server (RCS)

Remote Control Server 是 Claude Code 的远程控制后端,允许你通过浏览器 Web UI 远程监控和操作 Claude Code 会话。

## 功能

- **会话管理** — 创建、监控、归档 Claude Code 会话
- **实时消息流** — WebSocket / SSE 双向传输,实时查看对话和工具调用
- **权限审批** — 在 Web UI 中审批 Claude Code 的工具权限请求
- **多环境管理** — 注册多个运行环境,支持心跳和断线重连
- **认证安全** — API Key + JWT 双层认证

## 快速开始

### Docker 部署(推荐)

```bash
docker run -d \
--name rcs \
-p 3000:3000 \
-e RCS_API_KEYS=your-api-key-here \
-v rcs-data:/app/data \
ghcr.io/claude-code-best/remote-control-server:latest
Comment on lines +22 to +23
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Clarify/remove persistent volume examples for an in-memory server.

Line [22]-[23] and Line [95]-[99] suggest persistent storage, but Line [149] says data is in-memory and cleared on restart. This is confusing for deployment expectations.

Also applies to: 95-99, 149-149

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/remote-control-server/README.md` around lines 22 - 23, The README
currently contradicts itself by showing a persistent volume mount "-v
rcs-data:/app/data" in the Docker run examples while later stating the server is
in-memory and data is cleared on restart; update the sections referencing the
volume examples (the Docker run snippet containing "-v rcs-data:/app/data" and
the block at lines ~95-99) to either remove the volume examples or explicitly
state that mounting a host volume will persist data across restarts and is
optional, and modify the later note (around the in-memory data statement at
~149) to clarify that without a volume mount data is ephemeral; ensure the same
terminology is used and the examples reference the exact flag "-v
rcs-data:/app/data" so readers can locate and reconcile the two behaviors.

```

## 环境变量

### 服务器配置

| 变量 | 默认值 | 说明 |
|------|--------|------|
| `RCS_PORT` | `3000` | 监听端口 |
| `RCS_HOST` | `0.0.0.0` | 监听地址 |
| `RCS_API_KEYS` | _(空)_ | API 密钥列表,逗号分隔。客户端和 Worker 连接时需要提供 |
| `RCS_BASE_URL` | _(自动)_ | 外部访问地址,例如 `https://rcs.example.com`。用于生成 WebSocket 连接 URL |
| `RCS_VERSION` | `0.1.0` | 服务版本号,显示在 `/health` 响应中 |

### 超时与心跳

| 变量 | 默认值 | 说明 |
|------|--------|------|
| `RCS_POLL_TIMEOUT` | `8` | V1 轮询超时(秒) |
| `RCS_HEARTBEAT_INTERVAL` | `20` | 心跳间隔(秒) |
| `RCS_JWT_EXPIRES_IN` | `3600` | JWT 令牌有效期(秒) |
| `RCS_DISCONNECT_TIMEOUT` | `300` | 断线判定超时(秒) |

## Claude Code 客户端配置

### 连接到自托管服务器

在 Claude Code 所在环境设置以下变量:

```bash
# 指向你的 RCS 服务器地址
export CLAUDE_BRIDGE_BASE_URL="https://rcs.example.com"

# 认证令牌(与 RCS_API_KEYS 中的值对应)
export CLAUDE_BRIDGE_OAUTH_TOKEN="your-api-key-here"
```

然后启动远程控制模式:

```bash
ccb --remote-control
```

> **注意**:远程控制功能需要启用 `BRIDGE_MODE` feature flag。开发模式下默认启用。

### 环境变量参考

| 变量 | 说明 |
|------|------|
| `CLAUDE_BRIDGE_BASE_URL` | RCS 服务器地址,覆盖默认的 Anthropic 云端地址 |
| `CLAUDE_BRIDGE_OAUTH_TOKEN` | 认证令牌,用于连接 RCS 服务器 |
| `CLAUDE_BRIDGE_SESSION_INGRESS_URL` | WebSocket 入口地址(默认与 BASE_URL 相同) |
| `CLAUDE_CODE_REMOTE` | 设为 `1` 时标记为远程执行模式 |

## Docker Compose 示例

```yaml
version: "3.8"
services:
rcs:
build:
context: .
dockerfile: packages/remote-control-server/Dockerfile
args:
VERSION: "0.1.0"
ports:
- "3000:3000"
environment:
- RCS_API_KEYS=sk-rcs-change-me
- RCS_BASE_URL=https://rcs.example.com
volumes:
- rcs-data:/app/data
restart: unless-stopped

volumes:
rcs-data:
```

## 反向代理配置

使用 Nginx 或 Caddy 反向代理时,需要支持 WebSocket 升级:

```nginx
server {
listen 443 ssl;
server_name rcs.example.com;

location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_read_timeout 86400s;
}
}
```

Caddy 配置更简单,自动处理 WebSocket:

```
rcs.example.com {
reverse_proxy localhost:3000
}
```
Comment on lines +125 to +129
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add language identifiers to fenced code blocks (MD040).

Line [125] and Line [133] use unlabeled fences; markdownlint flags this and it hurts renderer/tooling behavior.

Suggested patch
-```
+```caddyfile
 rcs.example.com {
     reverse_proxy localhost:3000
 }

@@
- +text
┌─────────────┐ WebSocket/SSE ┌──────────────────┐
│ Claude Code │ ◄──────────────────► │ Remote Control │
│ (Bridge CLI)│ HTTP API │ Server │
...
└──────────────────┘

Also applies to: 133-146

🧰 Tools
🪛 markdownlint-cli2 (0.22.0)

[warning] 125-125: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/remote-control-server/README.md` around lines 125 - 129, Add
explicit language identifiers to the unlabeled fenced code blocks in README.md:
annotate the Caddy snippet fence (the block containing "rcs.example.com {
reverse_proxy localhost:3000 }") with ```caddyfile and annotate the ASCII
diagram block (the multi-line box/diagram) with a neutral label such as ```text
(also apply the same change to the similar fences mentioned for lines 133-146)
so markdownlint MD040 warnings are resolved.


## 架构概览

```
┌─────────────┐ WebSocket/SSE ┌──────────────────┐
│ Claude Code │ ◄──────────────────► │ Remote Control │
│ (Bridge CLI)│ HTTP API │ Server │
└─────────────┘ │ │
│ ┌────────────┐ │
┌─────────────┐ HTTP/SSE │ │ Event Bus │ │
│ Web UI │ ◄────────────────── │ └────────────┘ │
│ (/code/*) │ │ ┌────────────┐ │
└─────────────┘ │ │ In-Memory │ │
│ │ Store │ │
│ └────────────┘ │
└──────────────────┘
```

- **传输层**:WebSocket(V1)和 SSE + HTTP POST(V2)
- **存储**:纯内存存储(Map),重启后数据清除
- **认证**:API Key(客户端)+ JWT(Worker)
- **前端**:原生 JS SPA,通过 `/code/*` 路径访问

## 开发

```bash
# 安装依赖
bun install

# 开发模式(热重载)
bun run dev

# 类型检查
bun run typecheck

# 运行测试
bun test packages/remote-control-server/
```
27 changes: 27 additions & 0 deletions packages/remote-control-server/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"name": "@anthropic/remote-control-server",
"version": "0.1.0",
"type": "module",
"scripts": {
"dev": "bun run --watch src/index.ts",
"start": "bun run src/index.ts",
"build:web": "cd web && bun run build",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"hono": "^4.7.0",
"uuid": "^11.0.0"
},
"devDependencies": {
"@types/uuid": "^10.0.0",
"typescript": "^5.7.0",
"vite": "^6.0.0",
"@vitejs/plugin-react": "^4.0.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"@types/react": "^19.0.0",
"@types/react-dom": "^19.0.0",
"tailwindcss": "^4.0.0",
"@tailwindcss/vite": "^4.0.0"
}
}
Loading
Loading