Compare commits
3
Commits
fdc5942a72
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
29019fc5d5 | ||
|
|
8d4a5c4f94 | ||
|
|
ddb1b56459 |
@@ -35,8 +35,7 @@ Bun 工具链(选型见 `docs/design/技术选型.md`)。**Windows 上 `bun`
|
||||
| `bun run build:web` | 前端类型检查 + 产物构建 |
|
||||
| `bun run build:server` | 编译后端单文件 → `bin/naku-server`(当前平台) |
|
||||
| `bun run build:engine` | 编译同步引擎单文件 → `bin/naku-engine`(当前平台) |
|
||||
| `bun run naku` | 打包/构建 CLI(citty + clack):`naku pack` 交互打部署包 → `release/`;`naku build` 本地构建 → `bin/`;支持 `--yes` / `--target` / `--only` |
|
||||
| `bun run pack:release` | 非交互打部署包(= `naku pack --yes`,目标默认 linux-x64) |
|
||||
| `bun run pack` | 打包/构建 CLI(citty + clack):`pack release` 交互打部署包 → `release/`;`pack build` 本地构建 → `bin/`;支持 `--yes` / `--target` / `--only`;`pack release --yes` 非交互打部署包(目标默认 linux-x64) |
|
||||
|
||||
环境变量见 `.env.example`(复制为 `.env` 后填写)。
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 skycurtain
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -6,30 +6,32 @@
|
||||
|
||||
| 项目 | 内容 |
|
||||
|------|------|
|
||||
| 当前状态 | **实现推进中**——设计文档主体定稿;后端 + 同步引擎 + 前端(管理面与成员视图)已落地;部署链已定(见 [`docs/design/部署方案.md`](docs/design/部署方案.md));CFW 衍生形态待做 |
|
||||
| 当前状态 | **实现推进中**——设计文档主体定稿;后端 + 同步引擎 + 前端(管理面与成员视图)已落地;部署链已定(见 [`docs/design/部署方案.md`](docs/design/部署方案.md));Cloudflare Workers 抽取版待做 |
|
||||
| 技术栈 | 全栈 TypeScript(选型见 [`docs/design/技术选型.md`](docs/design/技术选型.md)):Bun + Hono + Drizzle + PostgreSQL · 前端 Vue 3 + antdv-next |
|
||||
|
||||
## 这是什么
|
||||
|
||||
面向 [NewAPI](https://github.com/QuantumNous/new-api) 实例的**独立计费辅助平台**:作者购买订阅额度后,创建多个 sk- API key 分发给多名使用者分摊成本。平台只提供**事实服务**——用量、来源标注、支付参数、多维统计;**不算「谁该付谁多少钱」**(分摊是人和人之间的事)。
|
||||
面向 [NewAPI](https://github.com/QuantumNous/new-api) 实例的**独立计费辅助平台**:订阅持有人创建多个 sk- API key 分发给多名使用者,平台把「谁用了多少」的事实记录清楚——用量、来源标注、支付参数、多维统计;**不算「谁该付谁多少钱」**(分摊是人和人之间的事)。
|
||||
|
||||
**适用场景**:同一份订阅额度由多人共享的小团队 / 成本分担群体。
|
||||
|
||||
- 名字直述用途:NewAPI 网关下按 key 查询用量(key usage);产品展示名 NAKU
|
||||
- 全场景统一小写 kebab-case:仓库名 / 包名一致
|
||||
- 仓库名 / 包名统一小写 kebab-case
|
||||
|
||||
## 背景与约束
|
||||
|
||||
- 真实场景:NewAPI(Go + GORM 开源 LLM 网关)实例提供订阅服务,多使用者通过分发 API key 共享订阅额度——**非商业,纯成本分担**
|
||||
- 项目作者是该实例的**普通用户**:只有 sk-key,**没有实例管理员账号**
|
||||
- 一切数据获取只能走 key 级接口(账号级接口用 sk-key 访问会 401)
|
||||
- 交付形态为**双形态并行**:完整前后端部署在实例运营方提供的服务器上(作者不接触服务器)+ Cloudflare Workers 抽取版部署在作者自控账号(见 [`docs/design/需求清单.md`](docs/design/需求清单.md) R13)
|
||||
- 典型场景:NewAPI(Go + GORM 开源 LLM 网关)实例提供订阅服务,多名使用者通过分发 API key 共享订阅额度——**非商业,纯成本分担**
|
||||
- 平台持有方通常只是实例的**普通用户**:只有 sk-key,**没有实例管理员账号**
|
||||
- 一切数据获取只能走 key 级接口(账号级接口用 sk-key 访问会 401)——这是整个数据链路的设计前提
|
||||
- 部署形态为**双形态并行**:完整前后端跑在自有或托管服务器上(见 [`docs/design/部署方案.md`](docs/design/部署方案.md));Cloudflare Workers 抽取版部署在自己掌控的账号里(见 [`docs/design/需求清单.md`](docs/design/需求清单.md) R13)
|
||||
|
||||
**双形态并行的动因**:服务器部署形态下,每次迭代都需实例运营方手工升级;部署到作者自控的 Cloudflare 账号后,迭代部署权回到作者手里。母体完整前后端先行,Workers 抽取版延后至功能全集实现后。
|
||||
**双形态并行的动因**:服务器部署形态下,每次迭代都要经服务器管理员手工升级;Workers 形态把迭代部署权收回自己手里。完整前后端先行,抽取版延后至功能全集实现后。
|
||||
|
||||
## 核心能力
|
||||
|
||||
| # | 能力 | 说明 |
|
||||
|---|------|------|
|
||||
| 1 | key 用量查询 | 按时间范围查模型请求日志与用量;作者全员视角在管理面总览,成员视角在成员视图 |
|
||||
| 1 | key 用量查询 | 按时间范围查模型请求日志与用量;管理端全员视角,成员端各自视角 |
|
||||
| 2 | key 登记 | 管理通道手动登记:入库并加入轮询;生命周期为手动启停(引擎只读 `status`) |
|
||||
| 3 | 支付事实登记 | 订阅权益与充值入账分表登记;`billing_domain` 标账本域,成员同域可见 |
|
||||
| 4 | 多维统计 | 按成员 / key / 计费来源 / 模型的任意时段聚合 |
|
||||
@@ -86,13 +88,13 @@ bun run dev:web # 前端 Vite(5175)
|
||||
### 本地打包
|
||||
|
||||
```bash
|
||||
bun run naku pack
|
||||
bun run pack release
|
||||
# 交互选:目标平台 · 打包内容 · 是否 tar.gz
|
||||
# 产出 release/:dist · naku-server · naku-engine · naku-migrate · drizzle/
|
||||
|
||||
# 非交互(CI / 一键):
|
||||
bun run pack:release
|
||||
# 或 bun run naku pack --yes --target=bun-linux-arm64 --only=dist,naku-server
|
||||
bun run pack release --yes
|
||||
# 或 bun run pack release --yes --target=bun-linux-arm64 --only=dist,naku-server
|
||||
```
|
||||
|
||||
### 服务器
|
||||
@@ -150,6 +152,7 @@ docker compose ps # migrate 应为 exited(0),其余 running/healthy
|
||||
|
||||
- 命名与分区:**目录用英文、文档名用中文**;`docs/design/`(方案与决策)· `docs/references/`(平台可用接口参考——实证事实,不做设计决策)
|
||||
- 生命周期:状态流转(草案 → 讨论中 → 待审阅 → 定稿(日期) → 已实现);定稿后不搬家、只改状态;被取代时删除(git 历史即考古)
|
||||
- AI 协作物料:[`AGENTS.md`](AGENTS.md)(协作约定)与 `.agents/skills/`(技能包)随仓库分发——参与开发的 Agent 从这里读取项目约定,人工贡献者亦可参考
|
||||
|
||||
## 关键事实清单(实测 / 源码确认,设计时不得违反)
|
||||
|
||||
@@ -161,3 +164,9 @@ docker compose ps # migrate 应为 exited(0),其余 running/healthy
|
||||
4. **1000 条硬上限**:窗口只保留最近 1000 条——当前日志速率下约等于 3 天;同步停摆超过窗口会永久丢数据
|
||||
5. **`TokenAuthReadOnly`**:用量/日志接口用宽松鉴权,key 过期 / 额度耗尽仍可查询;**显式禁用会被 401 拒绝**
|
||||
6. **`DisplayTokenStatEnabled` 已开启**:billing 接口返回 token 级额度
|
||||
|
||||
## License
|
||||
|
||||
[MIT](LICENSE) © 2026 skycurtain
|
||||
|
||||
NAKU 是 NewAPI 的**独立客户端**,仅通过 HTTP API 交互,不包含其源码;与上游许可证相互独立。上游 NewAPI 为 AGPL-3.0,本项目不受其传染,亦不衍生自它。
|
||||
|
||||
+1
-1
@@ -54,7 +54,7 @@
|
||||
├─ packages/
|
||||
│ ├─ core/ # 业务核心:轮询与 request_id 去重 · 折算链 · 加密体系 · API 语义与 DTO 类型
|
||||
│ └─ db/ # 数据访问层:Drizzle schema(pg 方言)+ 仓储 + 迁移 CLI
|
||||
├─ scripts/ # 打包/构建 CLI(naku pack / build)
|
||||
├─ scripts/ # 打包/构建 CLI(pack release / build)
|
||||
```
|
||||
|
||||
**依赖规则(单向)**:`apps/*` → `packages/db` → `packages/core`;core 导出的 DTO 类型可被 web 以纯类型依赖引用(前后端共用一份契约类型)。
|
||||
|
||||
+12
-12
@@ -28,7 +28,7 @@
|
||||
```text
|
||||
本地(打包) 服务器(运营方)
|
||||
────────── ────────────────
|
||||
bun run pack:release /opt/naku/ ← 约定目录
|
||||
bun run pack release --yes /opt/naku/ ← 约定目录
|
||||
├─ 前端 dist/ ├─ docker-compose.yml
|
||||
├─ naku-server(linux 单文件) ├─ Dockerfile
|
||||
├─ naku-engine(linux 单文件) ├─ .dockerignore
|
||||
@@ -53,7 +53,7 @@ bun run pack:release /opt/naku/ ← 约定目录
|
||||
|
||||
## 3. 产物包 `release/`
|
||||
|
||||
本地 `bun run pack:release` 产出,内容固定为:
|
||||
本地 `bun run pack release --yes` 产出,内容固定为:
|
||||
|
||||
```text
|
||||
release/
|
||||
@@ -68,13 +68,13 @@ release/
|
||||
|
||||
| 项 | 约定 |
|
||||
|----|------|
|
||||
| 命令入口 | 优先 `bun run naku pack`(交互:平台 / 内容 / tar.gz);一键等价 `bun run pack:release` 或 `naku pack --yes` |
|
||||
| 脚本落点 | `scripts/cli.ts`(citty + clack)· `scripts/pack-core.ts`(纯打包逻辑)· `scripts/pack-release.ts`(非交互兼容入口) |
|
||||
| 命令入口 | 优先 `bun run pack release`(交互:平台 / 内容 / tar.gz);一键等价 `bun run pack release --yes` |
|
||||
| 脚本落点 | `scripts/cli.ts`(citty + clack)· `scripts/pack-core.ts`(纯打包逻辑) |
|
||||
| 目标平台 | 默认 `bun-linux-x64`;`--target=` 可切 `bun-linux-arm64` / `bun-darwin-*` / `bun-windows-*`(含 arm64) |
|
||||
| 构建顺序 | 清空输出目录 → `build:web` → 三次 `--compile` → 拷 `drizzle/` →(可选)tar.gz |
|
||||
| 禁止入包 | `.env` · 源码 · `node_modules` · 任何密钥 |
|
||||
| 校验 | 打包后检查五条目都在且非空(Windows 本机编译允许 `.exe` 后缀);缺一即失败退出 |
|
||||
| 本地试跑 | `naku build` / `build:server` / `build:engine` → `bin/`(当前平台),与部署包 `release/` 分离 |
|
||||
| 本地试跑 | `pack build` / `build:server` / `build:engine` → `bin/`(当前平台),与部署包 `release/` 分离 |
|
||||
|
||||
### 3.2 为何是单文件而不是「服务器上构建」或「上传源码」
|
||||
|
||||
@@ -211,7 +211,7 @@ loadEnv → createDb(DATABASE_URL) → runMigrations(db, MIGRATIONS_FOLDER) →
|
||||
|
||||
```bash
|
||||
# 本地
|
||||
bun run pack:release
|
||||
bun run pack release --yes
|
||||
|
||||
# 服务器
|
||||
# 将 docker-compose.yml · Dockerfile · .dockerignore · Caddyfile · entrypoint-*.sh 放入 /opt/naku/
|
||||
@@ -228,7 +228,7 @@ docker compose ps
|
||||
|
||||
```bash
|
||||
# 本地
|
||||
bun run pack:release
|
||||
bun run pack release --yes
|
||||
# 上传,整目录覆盖 /opt/naku/release/
|
||||
|
||||
# 服务器(固定命令)
|
||||
@@ -299,13 +299,13 @@ docker compose ps
|
||||
|
||||
| 文件 | 动作 |
|
||||
|------|------|
|
||||
| `scripts/cli.ts` · `scripts/pack-core.ts` · `scripts/pack-release.ts` | 打包/构建 CLI 与核心逻辑 |
|
||||
| `scripts/cli.ts` · `scripts/pack-core.ts` | 打包/构建 CLI 与核心逻辑 |
|
||||
| `packages/db/src/migrate-cli.ts` | 迁移 CLI 入口 |
|
||||
| `package.json` | `naku` · `pack:release` · `build:server` · `build:engine` |
|
||||
| `package.json` | `pack` · `build:server` · `build:engine` |
|
||||
| `Dockerfile` · `docker-compose.yml` · `.dockerignore` | 两阶段镜像 · 五服务(含 `container_name`) |
|
||||
| `Caddyfile` · `entrypoint-pg.sh` · `entrypoint-app.sh` | 与 compose 同级平铺 |
|
||||
| `README.md` | 「部署」章与文档地图 |
|
||||
| `AGENTS.md` | 命令表(pack / build / naku) |
|
||||
| `AGENTS.md` | 命令表(pack / build) |
|
||||
| `.env.example` | 部署语义注释 |
|
||||
|
||||
---
|
||||
@@ -315,13 +315,13 @@ docker compose ps
|
||||
1. **`migrate` 独立单文件**:多一个产物,换来「先迁后跑、失败不启」的清晰门闩。若嫌多,可改为 server 启动时迁(仍幂等),但失去 migrate 失败阻断 engine 的不对称控制。
|
||||
2. **目标平台写死 linux-x64**:匹配常见服务器;ARM 用打包参数切换,不为此引入交叉编译矩阵。
|
||||
3. **`DATABASE_URL` 单源 + 两个 entrypoint**:少一处密码不一致;多两个常驻小 shell。若偏好 compose 原生 `POSTGRES_*`,需接受密码在 `.env` 写两处(或 `DATABASE_URL` 仅应用用)。
|
||||
4. **产物包而非源码包**:服务器零构建;代价是发版必须在本地(或 CI)跑 `pack:release`。
|
||||
4. **产物包而非源码包**:服务器零构建;代价是发版必须在本地(或 CI)跑 `pack release --yes`。
|
||||
|
||||
---
|
||||
|
||||
## 12. 验收清单(实现后)
|
||||
|
||||
- [ ] `bun run naku pack --yes`(或 `pack:release`)在 Windows 产出完整 `release/`(五条目齐全)
|
||||
- [ ] `bun run pack release --yes` 在 Windows 产出完整 `release/`(五条目齐全)
|
||||
- [ ] 首次安装按 §6.1 一次命令链可起,`docker compose ps` 形态符合 §6.4
|
||||
- [ ] 仅替换 `release/` 后重跑固定命令,版本切换成功且数据保留(卷不重建)
|
||||
- [ ] 连续两次固定命令:第二次 `migrate` 不重复应用已存在迁移(日志可证)
|
||||
|
||||
+2
-2
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"name": "new-api-key-usage",
|
||||
"private": true,
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"workspaces": [
|
||||
"apps/*",
|
||||
@@ -17,8 +18,7 @@
|
||||
"build:web": "bun run --cwd apps/web build",
|
||||
"build:server": "bun build --compile --outfile=bin/naku-server apps/server/src/index.ts",
|
||||
"build:engine": "bun build --compile --outfile=bin/naku-engine apps/engine/src/index.ts",
|
||||
"pack:release": "bun run scripts/pack-release.ts",
|
||||
"naku": "bun run scripts/cli.ts"
|
||||
"pack": "bun run scripts/cli.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@clack/prompts": "^1.8.1",
|
||||
|
||||
+8
-8
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* naku 打包/构建 CLI:citty 定义命令,@clack/prompts 负责交互。
|
||||
* pack 打包/构建 CLI:citty 定义命令,@clack/prompts 负责交互。
|
||||
* 有 TTY 且未给全 flag 时进交互;--yes / 无 TTY 则纯 flag。
|
||||
*/
|
||||
import { defineCommand, runMain } from "citty";
|
||||
@@ -34,9 +34,9 @@ const targetOption = {
|
||||
},
|
||||
} as const;
|
||||
|
||||
const pack = defineCommand({
|
||||
const release = defineCommand({
|
||||
meta: {
|
||||
name: "pack",
|
||||
name: "release",
|
||||
description: "打部署产物包(dist + 单文件 + drizzle)",
|
||||
},
|
||||
args: {
|
||||
@@ -62,7 +62,7 @@ const pack = defineCommand({
|
||||
},
|
||||
async run({ args }) {
|
||||
const interactive = isInteractive(args.yes as boolean | undefined);
|
||||
p.intro("NAKU 打包");
|
||||
p.intro("PACK 打包");
|
||||
|
||||
// --- target ---
|
||||
let target = args.target as string | undefined;
|
||||
@@ -205,7 +205,7 @@ const build = defineCommand({
|
||||
}
|
||||
only = [positional as PackItem];
|
||||
} else if (interactive) {
|
||||
p.intro("NAKU 本地构建");
|
||||
p.intro("PACK 本地构建");
|
||||
only = assertNotCancel(
|
||||
await p.multiselect({
|
||||
message: "构建什么",
|
||||
@@ -245,11 +245,11 @@ const build = defineCommand({
|
||||
|
||||
const main = defineCommand({
|
||||
meta: {
|
||||
name: "naku",
|
||||
name: "pack",
|
||||
version: "0.0.0",
|
||||
description: "NAKU 打包 / 本地构建",
|
||||
description: "PACK 打包 / 本地构建",
|
||||
},
|
||||
subCommands: { pack, build },
|
||||
subCommands: { release, build },
|
||||
});
|
||||
|
||||
runMain(main);
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
/**
|
||||
* 兼容入口:等价于 `naku pack --yes --target=bun-linux-x64`。
|
||||
* 交互请用 `bun run naku pack`。
|
||||
*/
|
||||
import { packRelease } from "./pack-core.ts";
|
||||
|
||||
function readTarget(): string {
|
||||
const arg = process.argv.find((a) => a.startsWith("--target="));
|
||||
return arg?.slice("--target=".length) || process.env.PACK_TARGET || "bun-linux-x64";
|
||||
}
|
||||
|
||||
const target = readTarget();
|
||||
console.log(`[pack-release] non-interactive pack (target=${target})`);
|
||||
|
||||
try {
|
||||
const out = await packRelease({
|
||||
target,
|
||||
only: ["dist", "naku-server", "naku-engine", "naku-migrate", "drizzle"],
|
||||
outDir: "release",
|
||||
tarball: false,
|
||||
onLog: (m) => console.log("[pack-release]", m),
|
||||
});
|
||||
console.log("[pack-release] done →", out);
|
||||
} catch (err) {
|
||||
console.error("[pack-release]", err instanceof Error ? err.message : err);
|
||||
process.exit(1);
|
||||
}
|
||||
Reference in New Issue
Block a user