Skip to content

Repository files navigation

somanyapikeys

GitHub Go License

OpenAI 兼容 API 中转 / Key 池网关:多上游端点、优先级调度、失败自动换 Key、探测与模型映射、请求/调度日志,以及中英文 Web 管理台。

仓库:https://github.com/azoway/somanyapikeys

预置上游示例:https://integrate.api.nvidia.com/v1。也可添加任意 OpenAI 兼容 base_url


目录


功能

能力 说明
OpenAI 兼容代理 /v1/chat/completions/v1/responses、聚合 /v1/models 等,支持流式 SSE
多 API 端点 系统预置 + 自定义 base URL;创建时 SSRF 校验
Key 池调度 优先级 0–1000;成功率排序 + 可选 per-key 并发上限;额度用尽 / 鉴权失败 / 限流时自动换 Key
探测 定时或手动;用最小 chat/completions 真鉴权(不用不可靠的 GET /models 判活)
自动禁用 可选:失效 / 耗尽 Key 设为 enabled=false;可 Webhook 告警
模型探测 + 映射 缓存上游模型;客户端 model → 上游 id(全局或按端点);支持批量导入
日志与用量 请求 / 调度日志;按天用量 rollup 与仪表盘趋势
访问密钥 客户端调 /v1 的凭证;支持过期、Token 配额、模型白名单、日志归属
备份恢复 配置包导出/导入 + 在线数据库快照
安全默认 change-me-* 弱口令;首次随机生成 Admin / 访问密钥 / 落盘加密密钥
落盘加密 AES-GCM;密钥自动生成或由环境变量指定
可观测 GET /healthz、可选 GET /metrics(Prometheus)、/v1 可选 per-IP 限流
Web 管理台 中 / 英;嵌入二进制,无需单独前端部署

概念:三种密钥

请勿混用:

名称 用途 获取位置
ADMIN_TOKENsk-admin-… 登录管理台、调用 /admin/api/* 首次生成 → data/credentials.txt
访问密钥sk-gw-… 客户端 api_key,请求本站 /v1 总览页,或 credentials 中的 GATEWAY_ACCESS_KEY
上游 Key(如 nvapi-… 网关出站调用厂商 API Key列表 / 导入

另外:

名称 用途
KEY_ENCRYPTION_SECRETsk-enc-… 加密 SQLite 内的密钥材料;不是 API 调用凭证

可选遗留变量 GATEWAY_TOKEN:若设置且库中尚无访问密钥,会作为首条访问密钥;仅当库中没有同值行时,运行时才作为 /v1 鉴权回退。若该密钥在库中被禁用或过期,不会被 env 回退复活。推荐只使用总览页管理的访问密钥。


快速开始

本地运行

要求:Go 1.25+(见 go.mod)。

git clone https://github.com/azoway/somanyapikeys.git
cd somanyapikeys

# 推荐:不设任何 Token / 加密密钥,首次启动自动生成
go run ./cmd/server
#
make run

首次启动:

  1. 日志只打印 prefix(不会把完整密钥打进 journald / Docker 日志)。
  2. 完整密钥写入 data/credentials.txt(权限 0600),例如:
ADMIN_TOKEN=sk-admin-…
GATEWAY_ACCESS_KEY=sk-gw-…
KEY_ENCRYPTION_SECRET=sk-enc-…
  1. 落盘加密密钥保存在 data/key_encryption.secret0600);同目录还会生成 data/key_encryption.salt0600enc:v2: 记录的 PBKDF2 盐)。两个文件重启时自动加载,缺一不可
  2. 打开管理台:http://localhost:8080/ ,用 ADMIN_TOKEN 登录。
  3. Key列表 / 导入 粘贴上游厂商 Key,绑定端点与优先级。
  4. 客户端使用 GATEWAY_ACCESS_KEY(或总览里新建的访问密钥)请求 /v1

也可自行指定强随机密钥(禁止 change-me-admin / change-me-gateway,启动会拒绝):

export ADMIN_TOKEN="$(openssl rand -hex 24)"
# export KEY_ENCRYPTION_SECRET=...   # 可选;不设则自动生成
go run ./cmd/server

常用入口

入口 地址
管理台 http://localhost:8080/
代理 base http://localhost:8080/v1
健康检查 GET /healthz(公开、无密钥,适合 LB 探针)
指标 GET /metrics(默认开启;默认需 Admin 鉴权METRICS_AUTH=true

Web 管理台

侧边栏(中文):

  1. 总览 — 池统计、Tokens 使用(24h/7d/Top models)、访问密钥(创建 / 查看 / 复制 / RPM / 启停 / 删除)
  2. API 端点 — 上游 base URL
  3. Key列表 — 上游 Key 池、筛选、单 Key 对话探测
  4. 导入 — 批量粘贴上游 Key
  5. 模型 — 已探测模型 + 模型映射
  6. 探测 — 全量 / 策略
  7. 日志 — 请求日志 / 调度日志(页内切换)
  8. 设置 — 探测与日志清理等
  9. 说明 — 简要帮助

支持界面语言中 / 英切换。管理 Token 仅保存在本机浏览器 localStorage


客户端用法

data/credentials.txt 取出 GATEWAY_ACCESS_KEY,或在总览页创建访问密钥:

export ACCESS_KEY=sk-gw-…   # 不是上游 nvapi Key

curl http://localhost:8080/v1/chat/completions \
  -H "Authorization: Bearer $ACCESS_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "meta/llama-3.1-8b-instruct",
    "messages": [{"role":"user","content":"hello"}]
  }'

Python(OpenAI SDK):

from openai import OpenAI

client = OpenAI(
    base_url="http://localhost:8080/v1",
    api_key="sk-gw-…",  # 访问密钥
)
print(client.chat.completions.create(
    model="meta/llama-3.1-8b-instruct",
    messages=[{"role": "user", "content": "hello"}],
))

Docker

docker compose up -d --build
  • Token / 加密密钥均可留空:首次启动写入卷内 data/credentials.txtdata/key_encryption.secretdata/key_encryption.salt
  • 查看:docker compose logs -f(日志仅 prefix)或进入数据卷读 credentials。
  • 需要固定密钥时再设置环境变量 ADMIN_TOKENKEY_ENCRYPTION_SECRET 等(强随机,勿用模板字面量)。
  • 盐没有对应的环境变量,只以文件形式存在于数据卷——数据卷必须持久化且可写,否则每次重建都会生成新盐,已有 enc:v2: 记录随之报废。
docker compose exec gateway cat /app/data/credentials.txt

反代与 TLS(Caddy 示例)

进程默认本地 HTTP 监听(如 :8080)。TLS、域名、公网入口请用 Caddy / Nginx 等反代完成。

场景 A:管理台 + API 一并公网(你当前用法)

整站反代到本机进程即可。保护依赖:

  • 强随机 ADMIN_TOKEN(管理台登录 / /admin/api / 默认 /metrics
  • 客户端只用 访问密钥/v1,不要把 Admin Token 发给调用方
  • HTTPS(Caddy 自动证书)
  • 浏览器侧勿在不可信设备保存 Token;定期轮换访问密钥
# 管理台 + OpenAI 兼容 API 同一域名(HTTPS)
gateway.example.com {
    # 可选:压缩、安全头
    encode gzip
    header {
        Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
        X-Content-Type-Options nosniff
        X-Frame-Options DENY
        Referrer-Policy no-referrer
        -Server
    }

    reverse_proxy 127.0.0.1:8080
}

说明:

路径 公网 鉴权
/ 管理台 UI 可开 浏览器输入 Admin Token
/admin/api/* 可开 Authorization: Bearer <ADMIN_TOKEN>
/v1/* 可开 访问密钥 sk-gw-…
/healthz 可开 (仅 {"ok":true},适合探活)
/metrics 可开 默认需 AdminMETRICS_AUTH=true

Prometheus 抓取示例(带 Admin Token):

# scrape_configs 片段
- job_name: somanyapikeys
  scheme: https
  metrics_path: /metrics
  static_configs:
    - targets: ["gateway.example.com"]
  authorization:
    type: Bearer
    credentials: "sk-admin-…"   # 与管理台同一 ADMIN_TOKEN;建议专用只读网络或内网抓取

若 Prometheus 在内网、不想带 Token:本机进程设 METRICS_AUTH=false,并在 Caddy 不要/metrics 暴露到公网,例如:

gateway.example.com {
    # 公网拒绝 metrics(由内网直连 :8080 或另一条内网反代抓取)
    @metrics path /metrics
    respond @metrics 404

    reverse_proxy 127.0.0.1:8080
}

场景 B:公网只暴露 API(管理台仅本机/内网)

api.example.com {
    reverse_proxy /v1/* 127.0.0.1:8080
    reverse_proxy /healthz 127.0.0.1:8080
    # 不转发 /、/admin、/metrics
}

管理台:ssh -L 8080:127.0.0.1:8080 user@host 后访问 http://127.0.0.1:8080/

healthz 与 metrics 说明

端点 建议 原因
GET /healthz 保持公开、体积极小 负载均衡 / Caddy / k8s 探活需要无鉴权;当前仅返回 {"ok":true},不含密钥与业务细节,无需再加密。进程日志默认不打印 healthz 访问,避免刷屏。
GET /metrics 默认 METRICS_AUTH=true 指标会暴露请求量、上游成败、换 Key 次数等运维面信息;管理台已公网时,无鉴权 metrics 等于白给侦察。抓取端带 Admin Bearer,或内网关闭鉴权且公网屏蔽该路径。
关闭指标 METRICS_ENABLED=false 完全不注册路由

本机开发可直接访问 http://127.0.0.1:8080/


环境变量

变量 默认 说明
LISTEN :8080 监听地址
UPSTREAM https://integrate.api.nvidia.com/v1 默认上游(系统端点)
ADMIN_TOKEN (空 → 首次随机生成) 管理台登录;生成 sk-admin-… → credentials
GATEWAY_TOKEN (空) 可选:首条访问密钥 / 遗留 /v1 回退;空则生成 sk-gw-…
KEY_ENCRYPTION_SECRET (空 → 首次随机生成) 落盘 AES-GCM;生成 sk-enc-…key_encryption.secret + credentials。启用时还会在同目录建立 key_encryption.saltenc:v2: 的 PBKDF2 盐,无对应环境变量,仅文件)
DB_PATH ./data/gateway.db SQLite 路径
RETRY_MAX_KEY_SWITCHES 5 单请求最多换 Key 次数(账号/Key 级失败;容量类错误不按此扫同端点 Key
RATE_LIMIT_COOLDOWN 60s 上游 429 冷却
SERVER_ERROR_COOLDOWN 10s 上游 5xx 冷却
INVALID_COOLDOWN 24h 判定失效后的冷却
CAPACITY_COOLDOWN 0 worker/容量饱和后 Key 冷却(默认 0:容量是端点共享,勿停车全部 Key)
CAPACITY_SAME_KEY_RETRIES 6 容量错误时同 Key 额外退避重试次数
CAPACITY_RETRY_BASE 1s 同 Key 退避基数(× 第几次:1s、2s…,单次上限 8s)
ENDPOINT_MODEL_MAX_INFLIGHT 32 每端点+模型网关侧并发上限,0=关闭(防打爆上游 worker)
ENDPOINT_MODEL_INFLIGHT_WAIT 45s 槽位满时排队等待;超时返回 503 + Retry-After
UPSTREAM_RESPONSE_HEADER_TIMEOUT 120s 等待上游响应头上限
UPSTREAM_NON_STREAM_TIMEOUT 300s 单次非流式请求总时长上限(含读完 body)
UPSTREAM_STREAM_IDLE_TIMEOUT 120s 流式请求空闲超时:无字节到达即中止(防半开连接钉死并发槽位)
PROBE_ENABLED false 启动时开启定时探测
PROBE_INTERVAL 30m 探测间隔(支持 7d / 365d 等 day 单位)
PROBE_SMART true 定时探测跳过近期 valid Key,优先问题 Key
STREAM_INCLUDE_USAGE true 流式请求自动注入 stream_options.include_usage 以便统计 tokens
PROBE_CONCURRENCY 10 探测并发
PROBE_AUTO_DISABLE false 失效自动禁用
PROBE_TIMEOUT 15s 单 Key 探测超时
PROBE_MODEL meta/llama-3.1-8b-instruct 探测用模型
LOG_CLEANUP_ENABLED true 日志自动清理
LOG_CLEANUP_INTERVAL 1h 清理间隔
LOG_KEEP_REQUEST 5000 请求日志保留条数
LOG_KEEP_SCHEDULE 15000 调度日志保留条数
CORS_ORIGINS (空) 空=不发 CORS;*=全部;逗号白名单
ALLOW_PRIVATE_ENDPOINTS false 允许私有/本地域名端点(SSRF 放宽)
GATEWAY_RATE_LIMIT_PER_MIN 0 /v1 每 IP 每分钟上限,0=关闭
TRUSTED_PROXIES 127.0.0.0/8,::1/128 仅这些来源的 X-Forwarded-For / X-Real-IP 被采信;其余用直连对端地址(防 IP 伪造)
ADMIN_MAX_FAIL_PER_MIN 10 Admin Token 错误次数上限,超出返回 429,0=关闭
ADMIN_FAIL_BLOCK 5m Admin 失败计数窗口 / 封禁时长
METRICS_ENABLED true 暴露 GET /metrics
METRICS_AUTH true /metrics 是否要求 Admin Token(公网管理台时请保持 true)
ASYNC_LOGS true 异步缓冲写入请求/调度日志
GLOBAL_UPSTREAM_MAX_CONNS 200 上游 HTTP 连接池相关上限

完整示例见 .env.example


数据文件与备份

DB_PATH 所在目录(默认 ./data/,已在 .gitignore):

文件 权限 说明
gateway.db(及 -wal / -shm 0600 业务库;密钥材料为 enc:v2:… 密文(当前写入与启动迁移目标格式)。若仍有未迁移的 enc:v1:… 行,仅依赖 secret 可读,启动成功后会重加密为 v2
credentials.txt 0600 首次生成的 Admin / 访问密钥 / 加密密钥(勿提交 Git
key_encryption.secret 0600 落盘加密密钥;与 DB 分开存放,重启必需
key_encryption.salt 0600 enc:v2: 的 PBKDF2 盐(16 字节,hex 编码);同样与 DB 分开存放,重启必需,重要性与 secret 等同

启动时会将目录收紧为 0700、上述文件为 0600

关于 key_encryption.salt

  • 全新数据目录首次启动会自动创建:没有 DB、或 DB 里尚无任何 enc: 密文时,启动会随机生成盐并写入 DB_PATH 所在目录——因此该目录必须可写,否则启动失败。
  • 启动时会把可读的 enc:v1: 重加密为 enc:v2:api_keysaccess_keysmeta.admin_token)。成功迁移后,这些记录同时需要 secret 与当时生成的盐才能解密。请勿再假设「历史 v1 永久只靠 secret 恢复」。
  • 库内已有密文时,缺少 salt/secret 会直接拒绝启动(fail-closed):不会静默生成新盐或新 secret 继续跑(那等于报废全部 enc:v2:)。请从备份恢复 sidecar 文件。空/损坏的 salt 或 secret 文件同样 fatal,不会覆盖重生。
  • 仅恢复 gateway.db、不带 salt/secret 会启动失败——这是有意的,避免“服务起来了但密钥全读不出来”。请始终同时备份/恢复三个文件。
  • 仍无法解密的 enc:v1: 行会跳过并告警,不阻塞启动。

备份:请同时备份 SQLite、key_encryption.secretkey_encryption.salt(以及你保存的 credentials.txt)。管理台「设置 → 备份与恢复」可导出配置包 / 在线库快照;DB 快照不含 secret/salt。丢失加密密钥或盐中的任意一个,都将无法解密库内 enc:v2: 字段。


管理 API 摘要

均需 Header:Authorization: Bearer <ADMIN_TOKEN>X-Admin-Token: <ADMIN_TOKEN>

方法 路径 说明
GET /admin/api/stats 池统计
GET /admin/api/config 公开运行配置摘要
GET/POST /admin/api/access-keys 客户端访问密钥列表 / 创建
GET /admin/api/access-keys/{id} 查看完整访问密钥(可复制)
PATCH/DELETE /admin/api/access-keys/{id} 启停 / 删除访问密钥
GET/POST /admin/api/endpoints 端点列表 / 添加
PATCH/DELETE /admin/api/endpoints/{id} 更新 / 删除端点
GET /admin/api/keys 上游 Key 列表
POST /admin/api/keys/import 导入上游 Key
PATCH /admin/api/keys/{id} 更新
GET /admin/api/keys/export 导出明文上游 Key
POST /admin/api/keys/batch 批量启停 / 删除
POST /admin/api/probe 批量状态探测
POST /admin/api/keys/{id}/test 单 Key 对话探测
GET/PUT /admin/api/settings 运行时设置
GET /admin/api/backup/export 导出配置包 JSON(含明文密钥)
POST /admin/api/backup/db 在线 SQLite 快照(VACUUM INTO)
POST /admin/api/backup/import 合并导入配置包
GET /admin/api/usage/series 按天用量趋势(仪表盘)
POST /admin/api/logs/cleanup 立即清理日志
GET /admin/api/models 已缓存上游模型
POST /admin/api/models/discover 探测上游 /models
GET/POST /admin/api/model-maps 映射列表 / 创建
POST /admin/api/model-maps/import 批量导入映射(JSON items 或 CSV text
PATCH/DELETE /admin/api/model-maps/{id} 更新 / 删除映射
GET /admin/api/logs/requests 请求日志(含 mapped_model
GET /admin/api/logs/schedule 调度日志

模型映射

解析顺序(命中即停):

  1. 端点级精确映射(client_model 完全相等 + 当前 Key 所属端点)
  2. 全局精确endpoint_id = 0
  3. 端点级通配符 client_model = *(该端点任意未精确命中的客户端 model)
  4. 全局通配符 client_model = * + endpoint_id = 0
  5. 无映射则 原样转发 客户端 model

*整条兜底(不是 gpt-* 这类前缀通配)。同一范围下精确规则永远优先于 *

GET /v1/models 聚合:启用中的映射客户端 model(不含 *)+ 已探测到的上游 model。

示例:把所有未单独配置的客户端 model 统一改写到某上游:

client_model target_model endpoint_id
gpt-4o meta/llama-3.1-70b-instruct 0(精确)
* meta/llama-3.1-8b-instruct 0(兜底)

安全说明

  • 无默认弱口令change-me-* 字面量会启动失败;推荐 Token / 加密密钥留空,首次随机生成。
  • 日志脱敏:FIRST RUN 只打 prefix;完整密钥仅在 credentials.txt0600)。
  • 文件权限:数据目录 0700,库与凭证 0600(启动自动收紧)。
  • 落盘加密默认开启:首次生成 KEY_ENCRYPTION_SECRET;新写入采用 enc:v2:(PBKDF2-HMAC-SHA256 派生密钥 + 持久化盐),历史 enc:v1: 记录永久可读。备份务必同时包含 key_encryption.secretkey_encryption.salt——丢失任一,enc:v2: 密钥材料都无法恢复。
  • 部署模型:默认本地 HTTP;TLS / 域名由 Caddy/Nginx 等处理。支持整站公网(管理台 + /v1)或仅暴露 API(见上文 Caddy 场景 A / B)。
  • Admin 公网可用但失陷代价高:可 export 全部上游 Key、配置自定义端点(出站带 Key)。务必使用强随机 ADMIN_TOKEN、HTTPS,并避免在公共电脑保存 Token。
  • /healthz 公开无鉴权:仅存活探测,可安全给负载均衡使用。
  • /metrics 默认需 Admin 鉴权METRICS_AUTH=true);内网抓取可关鉴权并在反代屏蔽公网路径。
  • CORS 默认关闭;浏览器跨域时再配置 CORS_ORIGINS
  • 自定义端点 默认禁止私有/本地域名;仅内网调试时打开 ALLOW_PRIVATE_ENDPOINTS
  • 列表与日志只展示 Key 前缀keys/export 仍返回完整上游 Key。

开发

go test ./...
go build -ldflags="-s -w" -o bin/somanyapikeys ./cmd/server
#
make test
make build

主要目录:

cmd/server/          入口
internal/admin/      管理 API
internal/auth/       鉴权
internal/config/     环境变量
internal/keypool/    调度
internal/proxy/      /v1 代理
internal/probe/      Key 探测
internal/store/      SQLite、bootstrap、加密、访问密钥
web/                 嵌入式管理台静态资源

发布与二进制

推送符合 v* 的 tag 后,GitHub Actions 会自动交叉编译常用平台并创建 Release

平台 产物
Linux amd64 / arm64 .tar.gz
macOS amd64 / arm64 .tar.gz
Windows amd64 / arm64 .zip

同时附带 checksums.txt(SHA-256)。

# 打 tag 并推送(触发 .github/workflows/release.yml)
git tag v0.1.0
git push origin v0.1.0

# 本地同样产物(写入 dist/)
make release-local
# 或指定版本号
make release-local VERSION=v0.1.0

二进制为 CGO_ENABLED=0 静态构建(modernc.org/sqlite),无需系统 SQLite。启动时日志会打印 version / commit / date


License

LICENSE

About

No description or website provided.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages