# 部署文档 > 交付给实例运营方的安装与运行说明——**一条命令链**可跑(见 §3)。前后端**独立部署**:后端单进程常驻;前端为静态产物,由反代托管。 | 项目 | 内容 | |------|------| | 状态 | 定稿(2026-09-15) | | 适用 | new-api-key-usage(针对 NewAPI 的 key 用量统计工具) | | 关联 | `README.md`(项目全景与设计依据)· 仓库根 `.env.example`(环境变量样例) | | 不含 | 反向代理软件本身的安装(假定服务器已有 Nginx 或等价物) | --- ## 0. 前置条件 | # | 项 | 要求 | |---|----|------| | 1 | 服务器 | Linux x86_64;可**出网访问 NewAPI 站点**(工具需轮询其接口) | | 2 | 运行时 | **Bun ≥ 1.4**(安装:`curl -fsSL https://bun.sh/install \| bash`) | | 3 | 数据库 | **PostgreSQL ≥ 14**(可用服务器现有实例;建议新建独立库与独立账号) | | 4 | 反向代理 | Nginx 或等价物——**必须能配置 `/api` 反代规则**(前后端连线以同域反代为主,见 README 工程注记) | ## 1. 交付物 - 源码(git 仓库或压缩包),根目录为 Bun workspaces monorepo: - `apps/server` —— 后端(Hono API + 同步引擎同进程) - `apps/web` —— 前端(构建为静态产物 `apps/web/dist`) - `packages/shared` —— 前后端共享的契约类型 - 无预编译产物要求:后端由 Bun 直接运行 TypeScript;前端产物在部署机执行一次构建即可。 ## 2. 环境变量 在仓库根创建 `.env`(**不入库**): | 变量 | 必填 | 说明 | 生成 / 填法 | |------|------|------|------------| | `PORT` | 否 | 后端监听端口(默认 3000) | 如 `3000` | | `DATABASE_URL` | **是** | PostgreSQL 连接串 | `postgres://用户:密码@主机:5432/库名` | | `MANAGE_TOKEN` | **是** | 管理面口令(`X-Manage-Token`) | `openssl rand -hex 16` | | `KEYS_MASTER_KEY` | **是** | 凭证主密钥(**丢失 = 全部 key 需重新登记**) | `openssl rand -base64 32`(32 字节 base64) | | `NEWAPI_BASE` | **是** | 上游 NewAPI 站点地址(不带尾斜杠) | 如 `https://your-instance.example.com` | > `UPSTREAM_SOURCE` / `NEWAPI_FIXTURE_DIR` 仅开发期使用(见实施计划 §6),**生产不要设置**(默认走真实站点)。 ## 3. 一条命令链 在仓库根依次执行: ```bash bun install # 1) 安装全部依赖(含前端) bun run db:push # 2) 建表(首次;四张表 + 索引) # 生产首次也可用 db:generate + db:migrate(迁移文件入库) bun run --filter '@naku/server' start # 3) 启动后端(前台;常驻见 §4) # 启动即跑一轮同步,随后每 5 分钟一轮 bun run build # 4) 构建前端产物 → apps/web/dist ``` ## 4. 常驻运行(systemd 示例) `/etc/systemd/system/naku-server.service`: ```ini [Unit] Description=new-api-key-usage server After=network-online.target [Service] Type=simple WorkingDirectory=/opt/new-api-key-usage/apps/server # .env 由应用从仓库根读取(apps/server 的上两级) ExecStart=/root/.bun/bin/bun run /opt/new-api-key-usage/apps/server/src/index.ts Restart=always RestartSec=5 # 可选:环境变量也可直接注入(优先级高于 .env) # Environment=PORT=3000 # Environment=DATABASE_URL=postgres://... [Install] WantedBy=multi-user.target ``` ```bash systemctl daemon-reload && systemctl enable --now naku-server journalctl -u naku-server -f # 观察 [sync] 日志 ``` ## 5. 反向代理(Nginx 示例) ```nginx server { listen 80; server_name usage.example.com; # 前端静态产物(同源,前端用相对路径 /api) root /opt/new-api-key-usage/apps/web/dist; index index.html; location / { try_files $uri $uri/ /index.html; # SPA 路由回退 } # 后端 API(前后端连线:同域反代) location /api/ { proxy_pass http://127.0.0.1:3000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } } ``` > 跨源直连(前端与后端不同域)亦可,但需后端 CORS 白名单与前端运行时 API base(契约 约定 24);同域反代是默认形态、无 CORS 问题。 ## 6. 首次运行检查 1. 后端日志出现两行:`[sync] 引擎启动:立即跑一轮,随后每 5 分钟一次` 与 `[sync] 轮次完成:0 key,成功 0,失败 0,新增日志 0 条…`(无 key 时正常空转) 2. 打开站点 → 管理面 `/manage/api-keys`(界面上不暴露入口,直接输入 URL)→ 输入 `MANAGE_TOKEN` 进入 3. 点「+ 登记 key」粘贴一个 sk-key → 登记成功后该 key 出现在清单(「数据截止」先显示「同步中」,一轮后变为时间) 4. 回到查看面 `/usage-log`,粘贴刚登记的 key → 查询 → 卡片与明细出现数据 ## 7. 必须知道的运行约束 | # | 约束 | 说明 | |---|------|------| | 1 | **后端必须单实例** | 同步引擎随进程启动(5 分钟一轮)。多实例会重复轮询、互相覆盖 `log_synced_at`,并可能触发上游限流。systemd 单实例 + 不做水平扩展即可 | | 2 | **3 天窗口** | 上游 `/api/log/token` 固定只返回最近 1000 条(当前速率下 ≈ 3 天)。同步停摆超过约 3 天,旧日志**永久丢失**(上游不提供回填接口)。日志落后 > 24h 时每轮打印 warning | | 3 | **`KEYS_MASTER_KEY` 不可丢** | 该密钥加密存储全部 sk-key。丢失后无法解密 → 需在管理面重新登记全部 key(历史日志数据仍在) | | 4 | **401 自动摘除** | 连续 3 轮返回 401 的 key 会被自动停用(`disabled_source = auto_401`);在管理面「启用」即可恢复(启用时会向上游验证) | | 5 | **上游同站点共享** | 工具对上游的请求是只读查询(每 key 每 5 分钟 2 个请求),量级极小;若上游有更严格的限流策略,可调低 key 数量或延长周期(`apps/server/src/sync/policy.ts` 的 `TICK_MS`) | | 6 | 真实 key 联调 | **待作者补验**:本地开发以实测响应快照验证(实施计划 §6),首次对真实站点登记时请在管理面确认「上游验证通过 + 首轮同步完成」 | ## 8. 升级流程 ```bash git pull # 或替换源码目录 bun install # 依赖有变化时 bun run db:push # schema 有变化时(会提示确认) bun run build # 重建前端产物 systemctl restart naku-server # 重启后端(引擎随启动立即补一轮) ``` ## 9. 故障排查 | 现象 | 排查方向 | |------|---------| | 查看面「数据截止」显示「数据同步中…」 | 后端日志 `[sync]` 是否有该 key 的失败行;新登记 key 等一轮即可 | | 查看面长时间数据不更新 | 是否「已失效(auto_401)」——管理面看状态徽章;或后端是否在运行 | | 登记返回「上游暂不可用」 | 服务器到 `NEWAPI_BASE` 的网络连通性(`curl -sS $NEWAPI_BASE/api/status`) | | 页面加载但查询 502 | 反代 `/api` 规则与后端端口是否一致 | | 金额列显示「—」 | 该日志所属订阅尚未登记购买记录 → 管理面「购买记录」按观测提示补录 | | 管理面全部 401 | `MANAGE_TOKEN` 与请求头是否一致(口令存 sessionStorage,关标签后需重新输入) |