🌐 CopilotX v2.0:从本地工具到远程服务,一个下午的进化

🌐 CopilotX v2.0:从本地工具到远程服务,一个下午的进化

今天早上发布了 CopilotX v1.0,一个把 GitHub Copilot 变成本地 API 的工具。中午吃饭时我想了一个问题:

如果我出门了,笔记本不在身边,还能用这些模型吗?

答案是——放一台云服务器上不就行了?

但 v1.0 完全没有安全防护。它默认绑定 127.0.0.1,只能本机访问。如果直接改成 0.0.0.0 暴露到公网,任何人都能白嫖你的 Copilot 订阅。

于是我花了一个下午,把 CopilotX 从"本地工具"进化成了"可远程部署的服务"。


需求分析:三分钟想清楚

我的需求其实很简单:

  1. 本地用户零影响 —— 之前怎么用,现在还怎么用
  2. 远程需要密钥 —— 没有 API Key 的请求直接拒绝
  3. localhost 免认证 —— 在服务器本机调试不用带 Key
  4. HTTPS —— API Key 不能明文传输

写成表格就一目了然:

场景HostAPI Key行为
本地开发(v1 用户)127.0.0.1不设全开,向后兼容
远程部署(有保护)0.0.0.0设了localhost 免认证,远程需要 Bearer Token
远程部署(裸奔)0.0.0.0没设⚠️ 启动时警告,但允许运行

核心设计原则:"Local Trust, Remote Verify" —— 本地信任,远程验证。


实现:225 行代码的精准手术

整个 v2.0 只改了 5 个文件,新增 3 个部署模板,总共 +225 行。

1. 安全常量(config.py,+8 行)

# ── Security ───────────────────────────────────────────────────────
COPILOTX_API_KEY = os.environ.get("COPILOTX_API_KEY", "")
LOCALHOST_ADDRS = {"127.0.0.1", "::1", "localhost"}
PUBLIC_PATHS = {"/health", "/"}

三个变量,三个决策:

  • COPILOTX_API_KEY:环境变量驱动,不是配置文件。因为密钥不该提交到代码里
  • LOCALHOST_ADDRS:同时覆盖 IPv4、IPv6 和主机名
  • PUBLIC_PATHS:健康检查和根路径永远可访问,方便监控探活

2. API Key 中间件(app.py,核心 50 行)

这是整个 v2 的灵魂——一个 Starlette BaseHTTPMiddleware:

class ApiKeyMiddleware(BaseHTTPMiddleware):
    async def dispatch(self, request: Request, call_next) -> Response:
        # 没设 Key → 全放行(向后兼容 v1)
        if not COPILOTX_API_KEY:
            return await call_next(request)

        # 公开路径 → 放行
        if request.url.path in PUBLIC_PATHS:
            return await call_next(request)

        # localhost → 放行(本地信任)
        client_host = request.client.host if request.client else ""
        if client_host in LOCALHOST_ADDRS:
            return await call_next(request)

        # 远程请求 → 验证 Bearer Token
        token = extract_token(request)  # 支持 3 种 header 格式
        if token != COPILOTX_API_KEY:
            return JSONResponse(status_code=401, content={...})

        return await call_next(request)

决策链很清晰:没 Key → 公开路径 → localhost → 验证 Token。每一步都是短路判断,命中就放行,到最后一步才真正验证。

为什么支持三种 header 格式?因为不同的 SDK 用不同的方式传 API Key:

SDKHeader 格式
OpenAI SDKAuthorization: Bearer sk-xxx
Anthropic SDKx-api-key: sk-xxx
Azure OpenAI SDKapi-key: xxx

一个中间件兼容三大生态,省得用户查文档。

3. CLI 远程模式感知(cli.py,+15 行)

启动时自动检测模式并给出提醒:

🚀 CopilotX v2.0.0
✅ Copilot Token valid (28m remaining, auto-refresh)
🔐 API Key protection: ON (localhost exempt)     ← 远程+有保护
📋 Models: gpt-4o, claude-sonnet-4, gemini-2.0-flash

或者当你 0.0.0.0 启动但忘了设 Key:

⚠️  WARNING: Remote mode without API key!
   Anyone can access your Copilot subscription.
   Set COPILOTX_API_KEY env var to enable protection.

这个黄色警告够醒目,但不阻止启动——也许你确实在内网环境不需要 Key。提醒但不强制。

4. 部署模板(deploy/,3 个新文件)

Caddyfile(3 行核心配置)

api.polly.wang {
    reverse_proxy localhost:24680
}

就这么多。Caddy 看到域名就会自动:

  • 向 Let's Encrypt 申请 TLS 证书
  • 配置 HTTPS(443)
  • HTTP → HTTPS 重定向
  • 证书到期前自动续期

对比 Nginx 需要 25 行配置 + 手动跑 certbot + 配 cron 续期。3 行 vs 25 行 + 运维。

systemd 服务文件

[Service]
Type=simple
User=copilotx
EnvironmentFile=/home/copilotx/.env
ExecStart=/home/copilotx/.local/bin/copilotx serve --host 0.0.0.0
Restart=always
NoNewPrivileges=true
ProtectSystem=strict

几个安全加固细节:

  • NoNewPrivileges=true:进程不能提权
  • ProtectSystem=strict:只读挂载系统目录
  • Restart=always:崩了自动重启
  • EnvironmentFile:密钥从 .env 文件加载,不写在 service 里

架构演进:从单机到两层

v1.0 的架构是一条直线:

你的工具 → CopilotX (localhost:24680) → api.githubcopilot.com

v2.0 变成两层:

你的笔记本 / 手机 / 任意设备
    │
    │  HTTPS (443)
    ▼
┌─────────────────────────────┐
│  Caddy (api.polly.wang)     │  ← 自动 HTTPS,零运维
│  Let's Encrypt 证书          │
└────────────┬────────────────┘
             │  HTTP (24680)     ← 内网通信,不出网
             ▼
┌─────────────────────────────┐
│  CopilotX (0.0.0.0:24680)  │
│  API Key 验证 + Token 管理   │
└────────────┬────────────────┘
             │  OpenAI 格式 + Copilot JWT
             ▼
    api.githubcopilot.com
    → GPT-4o, Claude, Gemini...

CopilotX 本身不管 HTTPS —— 交给专业的 Caddy 处理。这是 Unix 哲学:每个程序做好一件事。


为什么选 Caddy 而不是 Nginx?

这个问题我认真想了一下。结论是:在个人项目的场景下,Caddy 完胜。

维度CaddyNginx
HTTPS 配置零配置,自动 Let's Encrypt手动 certbot + cron 续期
配置量3 行~25 行
证书续期自动(内置)需要 cron 或 certbot timer
性能万级 QPS(够了)十万级 QPS(用不上)

CopilotX 是个人用的 API 代理,又不是服务百万用户的网关。Caddy 的自动 HTTPS 才是杀手锏 —— 忘了续期证书导致服务挂掉?Caddy 不存在这个问题。


部署流程:5 分钟搞定

实际在 Azure VM 上部署的完整流程:

# 1. 安装
pip install copilotx

# 2. 认证
copilotx auth login
# → 浏览器授权,和本地用一模一样

# 3. 设置 API Key
export COPILOTX_API_KEY=$(openssl rand -hex 32)
echo "记住这个 Key: $COPILOTX_API_KEY"

# 4. 启动
copilotx serve --host 0.0.0.0

# 5. 安装 Caddy(一次性)
sudo apt install caddy
echo 'api.polly.wang { reverse_proxy localhost:24680 }' | sudo tee /etc/caddy/Caddyfile
sudo systemctl reload caddy

然后从任何地方:

from openai import OpenAI

client = OpenAI(
    base_url="https://api.polly.wang/v1",
    api_key="你的COPILOTX_API_KEY",
)

response = client.chat.completions.create(
    model="gpt-4o",
    messages=[{"role": "user", "content": "Hello from remote!"}],
)
print(response.choices[0].message.content)

和调 OpenAI 官方 API 完全一样的体验,只是 base_url 换成了你自己的域名。


版本路线图

版本代号特性状态
v1.0.0LocalOAuth、双 API 格式、流式响应、模型发现✅ 已发布
v2.0.0RemoteAPI Key 认证、远程部署、Caddy/systemd 模板✅ 今天发布
v3.0.0Multi-UserToken Pool、用户数据库、OpenRouter 模式🔜 规划中

v3.0.0 的愿景是把 CopilotX 变成一个多用户的 AI API 网关——多个 GitHub 账号的 Token 组成池子,像 OpenRouter 一样按需分配。但那是更远的事了。


反思:225 行的信噪比

回顾这个下午的工作,我最满意的一点是克制。

远程部署这个需求,一般人可能会想加:

  • 用户注册系统
  • JWT 签发和验证
  • 速率限制中间件
  • 数据库存 API Key
  • 管理面板

这些都是"合理"的功能,但对于"个人远程访问自己的 Copilot"这个场景,全是过度设计。

一个环境变量(COPILOTX_API_KEY)+ 一个中间件(50 行)+ 一个 Caddy 配置(3 行),就解决了所有问题。

这让我想起今天早上写的 GateX v2 博文里的那句话:Less is More。GateX 是删掉 2000 行变强的,CopilotX v2 是只加 225 行就升级的。两个方向,同一个道理 —— 代码的价值不在于行数,在于每一行都解决真实问题。

v1.0 → v2.0,从 localhost 到 api.polly.wang,225 行代码的距离。


链接

升级只需一行:

pip install --upgrade copilotx

从本地到远程,三行命令的距离又近了一步。🌐

Comments