本分支(
server)的目标:把main的 RelayAB 从 Vercel Serverless 移植到 Debian 服务器。停更的真正原因原本是 Vercel Pro 每月 20 美元的固定月费叠加 Hobby 额度上限。 自托管直接消掉了这两项成本:SQLite + systemd + nginx,没有平台月费,也没有请求数上限。
与
main的关系:两者目前视为相互独立的项目。main是原 Vercel 版本 (停在 v178),保留待后续恢复;本分支只负责 Debian 自托管,不依赖main,也暂不与它合并。 本分支尚在真机验收阶段,克隆本仓库默认拿到的是本分支。
一个自托管的 AI API 网关(API 中转站),用于将上游 AI 服务的 API Key 安全、可控地分享给少数人,并实现精细的权限和用量管理。 部署到自己的 Debian 服务器(SQLite + systemd + nginx),无平台月费。
语言 / Language:中文(本页)· English ——切换只在此处,正文内部不会自动跳语言
原先的一键部署到 Vercel 的路径已移除。RelayAB 现在可以直接跑在自己的机器上, 没有平台月费,也没有请求数上限。
完整分步手册:deploy/README.md
快速概览:
# 1. 数据库目录(SQLite 不需要装任何服务,只要一个可写的目录)
sudo useradd -r -m -d /opt/relayab -s /usr/sbin/nologin relayab
sudo mkdir -p /var/lib/relayab
sudo chown relayab:relayab /var/lib/relayab
# 2. 代码与构建
sudo -u relayab git clone <repo> /opt/relayab
cd /opt/relayab && corepack pnpm install --frozen-lockfile && corepack pnpm build
# 3. 环境变量
cp deploy/env.production.example .env.production # 填 RELAY_AUTH / RELAY_DB_PATH / RELAY_BUILD_ID
chmod 600 .env.production
# 4. 常驻服务
sudo cp deploy/relayab.service /etc/systemd/system/
sudo systemctl enable --now relayab
# 5. 反向代理与 TLS
sudo cp deploy/nginx.conf /etc/nginx/sites-available/relayab
sudo ln -s /etc/nginx/sites-available/relayab /etc/nginx/sites-enabled/relayab
sudo nginx -t && sudo systemctl reload nginx
sudo certbot --nginx -d api.your-domain.comdeploy/ 目录里有全部现成配置:systemd unit、nginx 站点、环境变量模板。
数据库本身不用装、不用起、不用配密码。
根目录的 app.json 声明了主密钥由平台自动生成:
"RELAY_AUTH": { "generator": "secret" }Dokku 会在首次部署时生成一个 64 字符的加密安全随机串。所以这个密钥 不需要你写下来、不进 shell 历史、不进 GitHub Secrets、不需要复制到任何地方 —— 只有 Dokku 知道。
原生部署则用 openssl rand -hex 32 自己生成一次,存进 chmod 600 的
.env.production 即可。
完整的 Dokku 路线见 deploy/dokku.md。
- nginx 必须关掉响应缓冲(
proxy_buffering off+X-Accel-Buffering no), 否则 SSE 流式输出会变成「卡住然后一次性全吐」。 - systemd 的
WorkingDirectory必须在仓库根。管理台有两个页面在运行时读 仓库里的文件(docs/模型适配协议/README.md、scripts/spec-check.ts), 只拷.next会让它们显示「读取失败」。 - 数据库文件必须放在发布目录之外(
RELAY_DB_PATH指到/var/lib/relayab/relayab.db)。 否则git pull和重新构建会碰到它。而且目录与文件的属主必须是relayab, 否则第一次查询会报SQLITE_CANTOPEN/SQLITE_READONLY。
curl -s https://<your-domain>/healthz期望:
{"ok":true,"data":{"status":"ok","storage":"sqlite","env":{"required":1,"configured":1},"revision":"v179"}}storage 字段用来确认走的是哪条数据库通道(sqlite = 本地文件,默认)。
revision 来自你设置的 RELAY_BUILD_ID——自托管没有 Vercel 自动注入的
commit SHA,不设置它就无法判断线上跑的是哪一版。
数据存在一个 SQLite 文件里,由 Node 24 内置的 node:sqlite 管理 —— 没有
Redis/Valkey,没有端口,没有密码,没有内存淘汰导致数据消失的可能。备份就是复制文件。
唯一的必填环境变量是 RELAY_AUTH(主密钥)。数据库路径可选,不设就用
<工作目录>/data/relayab.db;生产环境建议显式指定到发布目录之外。
原先的 Upstash REST 通道和本机 TCP 通道都已随存储迁移一并移除, 现在只有 SQLite 一条路。
配置细节(架构、冒烟测试清单、故障排查、主密钥轮换)见 docs/zh/deployment.md。
- 多用户 + 角色(admin / user),密码 bcrypt 散列存储(单向不可逆)
- 欢迎页:
/是公开的介绍页——说明服务是什么、给出可复制的接口地址、一个按钮进入控制台。未登录访客不再被直接弹到登录页 - 积分属于账号(admin 控制):管理员给账号分配积分总量与可访问模型; 该账号下的所有 Key 共用这一份积分,用完即全部停止。 多建 Key 不会多拿额度
- 普通用户自助管理:在
/dashboard自己创建 / 重命名 / 启用停用 / 删除 Key,无需管理员介入 - 普通用户自助改密:
/dashboard/settings,需先输入原密码,再输入两次新密码进行验证 - 用户友好的文档页:
/dashboard/docs,把公网 URL 与 OpenAI / Anthropic 兼容示例以可复制代码块形式呈现 - 每用户多把 API Key,每把可独立配置:
- 过期时间(绝对时间戳)
- 启用/禁用(随时切换)
- 模型收窄(可选,只能在账号白名单基础上收窄,不能放宽)
- 客户 Key 格式 兼容 OpenAI:
sk-relay-...,HTTPAuthorization: Bearer sk-relay-... - 上游 Provider Key 用 AES-256-GCM 加密后存数据库,主密钥丢失 = 数据永久不可用
- 兼容协议:
- OpenAI Chat Completions(
/v1/chat/completions) - OpenAI Responses API(
/v1/responses,含 Chat / Anthropic 协议自动转换) - Anthropic Messages(
/anthropic/v1/messages) - 模型映射(客户端模型 → 上游真实模型,推荐恒等映射)
- OpenAI Chat Completions(
- 管理后台 + 用户面板 + 用量统计
- 界面多语言:中文(默认)/ English,页脚一键切换,偏好记在
relayab_localecookie - 本地开发闭环:嵌入式 Vercel REST API emulator(无需独立进程、无需网络)
| 层 | 技术 |
|---|---|
| 框架 | Next.js 15 App Router + React 19 + TypeScript 5 |
| 数据 | SQLite(Node 24 内置 node:sqlite,一个文件,无外部服务) |
| 认证 | iron-session 8 + bcryptjs(work factor 12) |
| 加密 | AES-256-GCM(Node crypto)+ bcryptjs |
| 上游 AI | 自写协议转换层(src/lib/proxy/*,基于 fetch) |
| 部署 | systemd + nginx(配置见 deploy/) |
| 测试 | Vitest + Playwright |
| UI | Tailwind CSS 3 + 自写组件 |
pnpm installcp .env.example .env.local只有 1 个必填,而且不需要任何数据库服务:
# 必填:主密码(也是管理员登录密码)
RELAY_AUTH="<openssl rand -hex 32>"
# 可选:数据库文件位置,不设则默认 ./data/relayab.db
RELAY_DB_PATH="./data/relayab.db"SQLite 由 Node 24 内置的 node:sqlite 驱动,没有 npm 依赖,也没有服务要装。
不设 RELAY_DB_PATH 直接 pnpm dev 就会在仓库下建 data/relayab.db;
想每次重启从零开始,设 RELAY_DB_PATH=":memory:" 即可。
公网地址建议显式设置。 /dashboard/docs 和欢迎页里展示的接口地址会依次尝试
数据库设置、RELAY_PUBLIC_URL、请求头里的 x-forwarded-proto / x-forwarded-host。
自托管在 nginx 后面时最后那一档能工作,但那是隐式依赖——nginx 一旦漏转发,
页面就会显示 http://127.0.0.1:3000。装好代理后直接把 RELAY_PUBLIC_URL
设成你的域名更稳妥。
可选:自动 bootstrap 上游 Provider(首次启动时生效)
# 多个 key 用逗号分隔 → 自动创建 OpenAI provider(启用 key rotation)
OPENAI_KEYS="sk-xxx,sk-yyy"
OPENAI_BASE_URL="https://api.openai.com/v1" # 覆盖 Azure/代理
ANTHROPIC_KEYS="sk-ant-xxx"
ANTHROPIC_BASE_URL="https://api.anthropic.com"SESSION_PASSWORD 和 RELAY_MASTER_KEY_HEX 都从 RELAY_AUTH 自动派生(HMAC-SHA256),无需手动生成。
默认就用仓库下的 SQLite 文件 ./data/relayab.db,不设任何环境变量也能跑:
pnpm dev # → http://localhost:3000想每次重启从零开始,加 RELAY_DB_PATH=":memory:"(测试就是这么做的)。
首次启动时,RelayAB 自动用 RELAY_AUTH 作为密码创建 admin 用户。
登录:
- 访问
/login - 用户名:
admin(或自定义RELAY_ADMIN_USERNAME) - 密码:你的
RELAY_AUTH值
首次登录后建议立即在 /admin/users 给自己改密码,或重置一个新的强密码。
在 /admin/providers 添加 Provider。三个字段职责不同,别互相替代:
| 字段 | 作用 |
|---|---|
| kind | 协议家族(模板决定)。anthropic = 这条 Provider 说 Anthropic Messages 协议 |
| API 请求地址(baseUrl) | 上游根地址,代理在其后拼端点路径 |
| 上游格式(upstreamFormat) | 上游原生协议:responses / chat / anthropic |
baseUrl 必须和端点配对,这是最常见的配置错误:
| 端点 | baseUrl 必须是 |
|---|---|
/v1/chat/completions、/v1/responses |
上游的 OpenAI 兼容基址,如 https://api.minimaxi.com/v1 |
/anthropic/v1/messages |
上游的 Anthropic 兼容基址,如 https://api.minimaxi.com/anthropic |
同一家上游的两套基址不通用。 想让三个端点都能用,就建两条 Provider:
MiniMax kind=openai baseUrl=https://api.minimaxi.com/v1 上游格式=Responses(原生)
MiniMaxAnthropic kind=anthropic baseUrl=https://api.minimaxi.com/anthropic 上游格式=Anthropic Messages
用恒等映射:客户端名和上游名填一样的,例如 MiniMax-M3 → MiniMax-M3。
左列会原样出现在 GET /v1/models 并被写进用量日志,所以不要为了迁就某个客户端
而写成 claude-sonnet-4-6 指向 MiniMax-M3 —— 这对使用者是误导。如果客户端
支持指定模型(例如 Claude Code 的 ANTHROPIC_MODEL),改客户端配置即可。
完整规则(含 upstreamFormat 与协议转换、Anthropic Provider 的分步配置)见
docs/zh/architecture.md §7。
pnpm test # 跑所有单元 + 集成测试(无需网络)
pnpm test:unit # 仅单元测试
pnpm test:integration # 仅集成测试
pnpm smoke # 端到端冒烟:真实 dev server + mock 上游(本地 SQLite)
pnpm type-check # TypeScript 编译检查
pnpm build # Next.js 生产构建RelayAB/
├── deploy/ 自托管部署配置(systemd / nginx / env 模板)
├── docs/ 技术文档
├── scripts/ 运维脚本(bootstrap / reset / rotate)
├── src/
│ ├── app/ Next.js App Router 页面与路由
│ │ ├── (auth)/login 登录页
│ │ ├── (user)/dashboard 用户面板
│ │ ├── (admin)/admin 管理员后台
│ │ └── api/ 后端 API
│ ├── components/ UI 组件
│ └── lib/ 核心库
│ ├── auth/ 认证(session / api key)
│ ├── crypto/ 加密(secrets / hashing / password)
│ ├── db/ 持久化(users / keys / providers / usage)
│ ├── proxy/ 上游代理(openai / anthropic)
│ ├── quota/ 配额(credits / rates / calculator)
│ └── config.ts 环境变量
├── tests/ unit / integration / e2e
└── 配置文件
pnpm bootstrap-admin --username <name> [--password <pw>]
pnpm reset-password --username <name> # 生成新密码并打印
pnpm rotate-key # 主密钥轮换(需 OLD_/RELAY_MASTER_KEY_HEX)
pnpm list-usage [--user <name>] [--days N] # 用量摘要入口:docs/zh/README.md(中文文档索引)。
| 文档 | 说明 |
|---|---|
| docs/zh/architecture.md | 完整架构 + 数据模型 + 业务决策 |
| docs/zh/data-model.md | 表结构 + 列名 + 索引 + 字段定义 |
| docs/zh/api-routes.md | API 路由完整规范 |
| docs/zh/testing.md | 测试金字塔 + 工具 |
| docs/zh/admin.md | 管理员操作指南(用户/Key/Provider 管理) |
| docs/zh/deployment.md | 自托管架构 + 数据库选型 + 冒烟测试清单 + 故障排查 |
| deploy/README.md | Debian 分步部署手册 |
注意:媒体供应商协议(图片 / 视频 / 语音 / 音乐供应商的声明式 JSON spec 格式, 及其离线校验器「判官」)仅有中文版: docs/模型适配协议/README.md。
- 用户密码:bcryptjs 单向散列(work factor 12),不可能反推原文
- 客户 API Key:仅存
sha256(明文)+ 前后缀展示,明文仅创建时返回一次 - 上游 Provider Key:AES-256-GCM 加密后入库,主密钥 =
RELAY_MASTER_KEY_HEX,丢失即不可恢复 - Session Cookie:iron-session 加密(HttpOnly + Secure + SameSite=Lax)
- 数据库文件单独泄漏不致命:拿到
.db但拿不到 env var → 无法解密上游 Key - 但数据库文件本身要收紧权限:
chmod 600+ 属主relayab。文件里存着 用户名、sha256(key)、明文keyPrefix、以及加密后的上游 Key。
分步手册见 deploy/README.md,设计与配置说明见 docs/zh/deployment.md。最关键的环境变量:
| 名称 | 来源 |
|---|---|
RELAY_AUTH |
openssl rand -hex 32(管理员登录密码 + 密钥派生种子)← 唯一必填 |
RELAY_DB_PATH |
建议显式指定,如 /var/lib/relayab/relayab.db。不设则默认 ./data/relayab.db |
RELAY_BUILD_ID |
版本号或 commit sha,显示在 /healthz 的 revision |
RELAY_PUBLIC_URL |
建议设为 https://你的域名 |
NODE_ENV |
production |
RELAY_ADMIN_USERNAME |
可选,默认 admin |
RELAY_DEFAULT_LOCALE |
可选,界面默认语言 zh-CN(默认)或 en |
RELAY_MASTER_KEY_HEX |
可选,默认从 RELAY_AUTH 派生 |
SESSION_PASSWORD 与(默认情况下的)RELAY_MASTER_KEY_HEX 都由 RELAY_AUTH
派生,无需手动设置。
数据默认落在 <工作目录>/data/relayab.db。生产环境建议显式设 RELAY_DB_PATH
把它放到发布目录之外,这样 git pull 和重新构建都碰不到它。
MIT