今天早上发布了 CopilotX v1.0,一个把 GitHub Copilot 变成本地 API 的工具。中午吃饭时我想了一个问题:
如果我出门了,笔记本不在身边,还能用这些模型吗?
答案是——放一台云服务器上不就行了?
但 v1.0 完全没有安全防护。它默认绑定 127.0.0.1,只能本机访问。如果直接改成 0.0.0.0 暴露到公网,任何人都能白嫖你的 Copilot 订阅。
于是我花了一个下午,把 CopilotX 从"本地工具"进化成了"可远程部署的服务"。
需求分析:三分钟想清楚
我的需求其实很简单:
- 本地用户零影响 —— 之前怎么用,现在还怎么用
- 远程需要密钥 —— 没有 API Key 的请求直接拒绝
- localhost 免认证 —— 在服务器本机调试不用带 Key
- HTTPS —— API Key 不能明文传输
写成表格就一目了然:
| 场景 | Host | API 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:
| SDK | Header 格式 |
|---|---|
| OpenAI SDK | Authorization: Bearer sk-xxx |
| Anthropic SDK | x-api-key: sk-xxx |
| Azure OpenAI SDK | api-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 完胜。
| 维度 | Caddy | Nginx |
|---|---|---|
| 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.0 | Local | OAuth、双 API 格式、流式响应、模型发现 | ✅ 已发布 |
| v2.0.0 | Remote | API Key 认证、远程部署、Caddy/systemd 模板 | ✅ 今天发布 |
| v3.0.0 | Multi-User | Token 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 行代码的距离。
链接
- PyPI: pypi.org/project/copilotx (v2.0.0)
- GitHub: github.com/Polly2014/CopilotX
- 前篇: CopilotX v1.0:一个晚上,把 GitHub Copilot 变成本地 API 服务器
升级只需一行:
pip install --upgrade copilotx
从本地到远程,三行命令的距离又近了一步。🌐

