diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index 749c471..0000000 --- a/.dockerignore +++ /dev/null @@ -1,17 +0,0 @@ -.git -.env -.env.* -!.env.example -node_modules -**/node_modules -**/dist -**/.tmp -**/.vite -*.log -api*.json -session-archive -coverage -*.tsbuildinfo -.claude -.agents -agent diff --git a/.env.example b/.env.example index 386e913..b7106af 100644 --- a/.env.example +++ b/.env.example @@ -1,14 +1,12 @@ # ── new-api-key-usage · 环境变量示例 ────────────────────────── # 复制为 .env 后填写(.env 已被 .gitignore,勿提交) # 开发:任意可用的 PostgreSQL,DATABASE_URL 指向即可 -# 部署:docker compose up -d --build(见 README) -# 本服务端口(避开 3000/3001 等常见占用;容器内固定 18456 即可) +# 本服务端口(避开 3000/3001 等常见占用) PORT=18456 # PostgreSQL 连接串——数据库配置的唯一来源 -# 开发:指向你本机/已有的库(主机 127.0.0.1 或 localhost) -# 部署:同一份即可;compose 会把主机改写为容器服务名 postgres +# 指向你本机/已有的库(主机 127.0.0.1 或 localhost) # 密码含 @ : / 等字符时请 URL 编码 DATABASE_URL=postgres://postgres:postgres@127.0.0.1:5432/new_api_key_usage diff --git a/AGENTS.md b/AGENTS.md index 7c96f7a..8c547d9 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -8,7 +8,6 @@ - **命名**:需求 / 设计 / 交互 / 技术 / 计划等**文档名**用中文(`README` / `AGENTS` / `LICENSE` 等约定俗成除外)、**目录**用英文;新文档按性质放入 `docs/design/`(我们定的方案与决策)或 `docs/references/`(平台可用接口参考——实证事实,不做设计决策),并同步更新 README 的「文档地图」;`docs/deprecated/` 只归档已废弃文档——**勿引用、勿作依据**(需要时再建) - **代码文件命名**:所有**目录名与文件名**一律 kebab-case(如 `time-range-picker.vue` · `admin-session.ts`);**组件在模板中引用时用大驼峰**(``)——文件名 kebab、模板标签 PascalCase - **文档状态**:头部表格标「状态」(草案 → 讨论中 → 待审阅 → 定稿(日期) → 已实现);设计文档定稿后不搬家、只改状态;被取代时删除(git 历史即考古) -- **部署**:交付物必须保证「一条命令链」可部署;**双形态并行已裁决**——完整前后端(运营方服务器)先行,Cloudflare Workers 抽取版(作者自部署)后续(见 `docs/design/需求清单.md` R13) - **不引入版本叙事**:不使用 v1/v2/v3 之类版本编号,不以「MVP」等标签或指称显式描述任何历史阶段;文档内容必须**自立**——不得引用、描述任何当前仓库之外、可能被删除的分支或目录内容 - **本地不入库也不 ignore 的物料**(AI 提交时务必避开):`api*.json`(上游实测快照)、`session-archive/`(作者主动备份的会话记录)——不加入 git、不写 `.gitignore`,靠提交时人工/执行者目视把关 @@ -34,7 +33,6 @@ Bun 工具链(选型见 `docs/design/技术选型.md`)。**Windows 上 `bun` | `bun run dev:engine` | 开发跑同步引擎(独立进程,读 `SYNC_*` env) | | `bun run dev:web` | 开发跑前端 Vite | | `bun run build:web` | 前端类型检查 + 产物构建 | -| `docker compose up -d --build` | 部署一键起(server / engine / caddy / pg) | 环境变量见 `.env.example`(复制为 `.env` 后填写)。 diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index cbc8bf6..0000000 --- a/Dockerfile +++ /dev/null @@ -1,37 +0,0 @@ -# 语法:docker/dockerfile:1 -# 构建产物:/app/naku-server · /app/naku-engine · /app/drizzle · /srv(前端) - -# ---------- 构建:依赖 + 前端 + 两个单文件 ---------- -FROM oven/bun:1.4-alpine AS build -WORKDIR /src - -COPY package.json bun.lock ./ -COPY apps ./apps -COPY packages ./packages - -RUN bun install --frozen-lockfile - -# 前端静态资源 -RUN bun run --cwd apps/web build-only - -# 单文件(linux-x64;构建发生在 Linux 容器内,无需交叉编译) -RUN mkdir -p /out \ - && bun build --compile --outfile /out/naku-server apps/server/src/index.ts \ - && bun build --compile --outfile /out/naku-engine apps/engine/src/index.ts - -# ---------- 应用运行时:两个二进制 + 迁移 SQL ---------- -FROM alpine:3.20 AS app -WORKDIR /app -# Bun 单文件仍需 C++ 运行时与 CA;从构建镜像拷贝,避免 apk 联网 -COPY --from=build /usr/lib/libstdc++.so.6 /usr/lib/libgcc_s.so.1 /usr/lib/ -COPY --from=build /etc/ssl/certs /etc/ssl/certs -COPY --from=build /out/naku-server /out/naku-engine /app/ -COPY --from=build /src/packages/db/drizzle /app/drizzle -ENV MIGRATIONS_FOLDER=/app/drizzle -# 默认跑 server;compose 里 engine 覆盖 command -CMD ["/app/naku-server"] - -# ---------- 网关:Caddy + 前端静态资源 ---------- -FROM caddy:2-alpine AS proxy -COPY deploy/Caddyfile /etc/caddy/Caddyfile -COPY --from=build /src/apps/web/dist /srv diff --git a/README.md b/README.md index 473af8c..906da87 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ | 项目 | 内容 | |------|------| -| 当前状态 | **实现推进中**——设计文档主体定稿;后端 + 同步引擎 + 前端(管理面与成员视图)已落地;部署链与衍生形态待收尾 | +| 当前状态 | **实现推进中**——设计文档主体定稿;后端 + 同步引擎 + 前端(管理面与成员视图)已落地;部署方案与衍生形态待定 | | 技术栈 | 全栈 TypeScript(选型见 [`docs/design/技术选型.md`](docs/design/技术选型.md)):Bun + Hono + Drizzle + PostgreSQL · 前端 Vue 3 + antdv-next | ## 这是什么 @@ -79,59 +79,6 @@ bun run dev:web # 前端 Vite(5175) 更多命令与协作约定见 [`AGENTS.md`](AGENTS.md)。 -## 部署 - -生产形态:**Caddy**(前端静态 + `/api` 反代)+ **server / engine 单文件**(Bun `--compile`)+ PostgreSQL。**迁移不随服务启动**——改表后先用 `db:push` / 手动应用 SQL,再起服务。开发不依赖本 Compose——把 `DATABASE_URL` 指到任意可用 PostgreSQL 即可。 - -### 起服务 - -```bash -cp .env.example .env -# 必改:DATABASE_URL、SK_KEY_ENCRYPTION_SECRET、NEWAPI_BASE、COOKIE_SECURE -docker compose up -d --build -``` - -- 页面:`http://<服务器>/`;接口文档:`http://<服务器>/api/docs` -- 升级:`git pull` → 有表变更时先应用 schema → 再 `docker compose up -d --build` - -### 环境变量要点 - -| 变量 | 说明 | -|------|------| -| `DATABASE_URL` | 数据库配置的**唯一来源**(用户 / 密码 / 库名都在串里)。开发指向本机库;部署同一份即可,compose 会把主机改写为容器服务名 `postgres` | -| `SK_KEY_ENCRYPTION_SECRET` | ≥32 字节随机数(base64url)。**生成一次、入库后不可更换**——丢失则已登记 key 无法解密,与库同等备份 | -| `NEWAPI_BASE` | 上游 NewAPI 实例根地址 | -| `COOKIE_SECURE` | HTTPS / 域名用 `true`;纯 IP + HTTP 用 `false`(否则登录后立刻 401) | -| `ADMIN_INIT_USERNAME` / `ADMIN_INIT_PASSWORD` | 可选;同时存在时空库首次静默建管理员 | - -### 服务分工 - -| 服务 | 职责 | 对外 | -|------|------|------| -| `caddy` | 前端静态 + `/api` 反代 | 仅 80 / 443 | -| `server` | 管理面 / 成员视图 API | 内网 | -| `engine` | 轮询上游、去重入库 | 内网 | -| `postgres` | 数据(卷 `pgdata`) | 内网 | - -### HTTPS - -把 `deploy/Caddyfile` 站点行从 `:80` 改成域名(如 `your.domain.example`),重建后 Caddy 自动签证书;同时设 `COOKIE_SECURE=true`。 - -### 常用命令 - -```bash -docker compose ps # 运行状态 -docker compose logs -f engine # 看同步 -docker compose restart server # 只重启后端 -docker compose down # 全停(数据在卷里,不丢) -``` - -### 注意 - -1. `COOKIE_SECURE` 必须与访问方式匹配:HTTP + `true` 会「登录后立刻 401」 -2. `SK_KEY_ENCRYPTION_SECRET` 更换等于已登记 key 全部作废 -3. `DATABASE_URL` 密码含 `@` `:` `/` 等字符时做 URL 编码 - ## 文档地图 | 文档 | 什么时候读 | 状态 | diff --git a/deploy/Caddyfile b/deploy/Caddyfile deleted file mode 100644 index 4c46baa..0000000 --- a/deploy/Caddyfile +++ /dev/null @@ -1,17 +0,0 @@ -# 站点地址按部署环境改: -# 本地/内网 HTTP → :80 -# 公网 HTTPS → your.domain.example(Caddy 自动签证书) -:80 { - root * /srv - - # 接口反代到 server 容器 - handle /api/* { - reverse_proxy server:18456 - } - - # SPA:非 /api 的路径回退 index.html - handle { - try_files {path} /index.html - file_server - } -} diff --git a/deploy/entrypoint-app.sh b/deploy/entrypoint-app.sh deleted file mode 100644 index 57cd077..0000000 --- a/deploy/entrypoint-app.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/sh -# 把 DATABASE_URL 的主机改写为 compose 服务名 postgres,再启动应用 -set -e - -if [ -z "$DATABASE_URL" ]; then - echo "DATABASE_URL is required" >&2 - exit 1 -fi - -export DATABASE_URL="$(echo "$DATABASE_URL" | sed -E 's#(@)[^:/]+(:[0-9]+)?(/)#\1postgres\2\3#')" - -exec "$@" diff --git a/deploy/entrypoint-pg.sh b/deploy/entrypoint-pg.sh deleted file mode 100644 index a1fcb27..0000000 --- a/deploy/entrypoint-pg.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/sh -# 从 DATABASE_URL 拆出官方镜像建库所需的 POSTGRES_*,再交回原入口 -set -e - -if [ -z "$DATABASE_URL" ]; then - echo "DATABASE_URL is required" >&2 - exit 1 -fi - -u="${DATABASE_URL#*://}" -auth="${u%%@*}" -rest="${u#*@}" - -export POSTGRES_USER="${auth%%:*}" -export POSTGRES_PASSWORD="${auth#*:}" -export POSTGRES_DB="${rest#*/}" -export POSTGRES_DB="${POSTGRES_DB%%\?*}" - -exec docker-entrypoint.sh postgres diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index 776d4d3..0000000 --- a/docker-compose.yml +++ /dev/null @@ -1,76 +0,0 @@ -# 部署一键起:docker compose up -d --build -# 配置只认 DATABASE_URL(见 .env.example);开发不依赖本文件 -# 注意:PASSWORD 含 @ : / 等字符时需 URL 编码 - -services: - postgres: - image: postgres:17-alpine - entrypoint: ["/bin/sh", "/entrypoint-pg.sh"] - environment: - DATABASE_URL: ${DATABASE_URL:?set DATABASE_URL in .env} - volumes: - - ./deploy/entrypoint-pg.sh:/entrypoint-pg.sh:ro - - pgdata:/var/lib/postgresql/data - healthcheck: - test: ["CMD-SHELL", "pg_isready -U $$(echo $$DATABASE_URL | sed -E 's#^[^:]+://([^:]+):.*#\\1#')"] - interval: 5s - timeout: 3s - retries: 12 - restart: unless-stopped - - server: - build: - context: . - target: app - entrypoint: ["/bin/sh", "/entrypoint-app.sh"] - command: ["/app/naku-server"] - environment: - PORT: "18456" - DATABASE_URL: ${DATABASE_URL} - SK_KEY_ENCRYPTION_SECRET: ${SK_KEY_ENCRYPTION_SECRET:?set SK_KEY_ENCRYPTION_SECRET in .env} - NEWAPI_BASE: ${NEWAPI_BASE:?set NEWAPI_BASE in .env} - COOKIE_SECURE: ${COOKIE_SECURE:-true} - LOG_LEVEL: ${LOG_LEVEL:-info} - ADMIN_INIT_USERNAME: ${ADMIN_INIT_USERNAME:-} - ADMIN_INIT_PASSWORD: ${ADMIN_INIT_PASSWORD:-} - MIGRATIONS_FOLDER: /app/drizzle - volumes: - - ./deploy/entrypoint-app.sh:/entrypoint-app.sh:ro - depends_on: - postgres: - condition: service_healthy - restart: unless-stopped - - engine: - build: - context: . - target: app - entrypoint: ["/bin/sh", "/entrypoint-app.sh"] - command: ["/app/naku-engine"] - environment: - DATABASE_URL: ${DATABASE_URL} - SK_KEY_ENCRYPTION_SECRET: ${SK_KEY_ENCRYPTION_SECRET} - NEWAPI_BASE: ${NEWAPI_BASE} - SYNC_INTERVAL_MS: ${SYNC_INTERVAL_MS:-300000} - LOG_LEVEL: ${LOG_LEVEL:-info} - MIGRATIONS_FOLDER: /app/drizzle - volumes: - - ./deploy/entrypoint-app.sh:/entrypoint-app.sh:ro - depends_on: - postgres: - condition: service_healthy - restart: unless-stopped - - caddy: - build: - context: . - target: proxy - ports: - - "80:80" - - "443:443" - depends_on: - - server - restart: unless-stopped - -volumes: - pgdata: diff --git a/docs/design/API契约-公共约定.md b/docs/design/API契约-公共约定.md index acdd183..f2936f3 100644 --- a/docs/design/API契约-公共约定.md +++ b/docs/design/API契约-公共约定.md @@ -27,7 +27,7 @@ - **会话 cookie**:`HttpOnly + Secure + SameSite=Lax`,`Path=/api/admin`(「路径限管理面」裁决的落地);随请求自动携带、登出即删行 - **成员令牌**:`Authorization: Bearer `;明文令牌只存在于登录壳输入与 sessionStorage(关标签即失效——《交互设计-登录与会话》§4) - 401 分流为前端职责:管理面任意 401 → 登录页「会话已过期」;成员视图任意 401 → 登录壳「令牌无效或已停用」——后端 401 响应不带面特化信息,前端按所在面分流(共性 §1) -- **命名空间约定:`/api/*` 专属接口**——前端页面路由永不进入 `/api` 前缀;未知 `/api/*` 路径 = 接口 `404` 错误信封,非 `/api` 请求归前端托管(SPA history 回退「非 `/api` → index.html」,细则归骨架与部署设计) +- **命名空间约定:`/api/*` 专属接口**——前端页面路由永不进入 `/api` 前缀;未知 `/api/*` 路径 = 接口 `404` 错误信封,非 `/api` 请求归前端托管(SPA history 回退「非 `/api` → index.html」,细则归骨架设计) ## 3. 错误信封 diff --git a/docs/design/api_keys表字段设计.md b/docs/design/api_keys表字段设计.md index 74717c4..3b92e9f 100644 --- a/docs/design/api_keys表字段设计.md +++ b/docs/design/api_keys表字段设计.md @@ -55,7 +55,7 @@ - `key_hash`(HMAC-SHA256,不可逆):只服务「命中判定」——没有它,每次查询都要把全部 key 送去上游验证 - `key_ciphertext`(AES-256-GCM,可解密):唯一用途是调上游 -- 密钥 `SK_KEY_ENCRYPTION_SECRET` 经 env 注入(HKDF 派生 hmac / aes 子密钥);**密钥丢失 = 全部 key 需重新登记**(记入部署文档) +- 密钥 `SK_KEY_ENCRYPTION_SECRET` 经 env 注入(HKDF 派生 hmac / aes 子密钥);**密钥丢失 = 全部 key 需重新登记** > **注解:为什么是两个字段(给非密码学背景的读者)** > diff --git a/docs/design/同步引擎设计.md b/docs/design/同步引擎设计.md index 40735a9..0877220 100644 --- a/docs/design/同步引擎设计.md +++ b/docs/design/同步引擎设计.md @@ -26,7 +26,7 @@ apps/engine ──► 定时 syncRound() ──► packages/db | 共享 | `packages/core`(上游客户端、dedupe、折算)· `packages/db`(仓储)——**业务能力仍一份** | | 不共享 | 进程内存、事件循环、调度器;HTTP 与引擎**互不调用** | | 不调自家 API | 引擎只打上游 NewAPI + 写库;不走 `/api/admin/*` | -| 部署 | 开发:两个命令(`dev:server` / `dev:engine`);生产 compose 两个 service(均连同一 PostgreSQL) | +| 运行形态 | 开发:两个命令(`dev:server` / `dev:engine`);生产两个进程(均连同一 PostgreSQL) | **为何分进程(2026-10-05 复审)**:一轮可能解析上千条日志并做指纹/加密/批量写,若与 API 同进程会争事件循环,查询延迟与「引擎是否在跑」绑死;定时器漂移本身对本业务无害,**互扰才是拆分动因**。 @@ -174,7 +174,7 @@ apps/ ## 9. 验收清单(实现后) -- [ ] 只启 `dev:engine`(或 compose 仅 engine+db):日志入库、`log_synced_at` 前进 +- [ ] 只启 `dev:engine`(或只跑 engine + db):日志入库、`log_synced_at` 前进 - [ ] 只启 `dev:server`:HTTP 正常,无轮询日志 - [ ] 同一数据重复轮次不产生重复行 - [ ] 某 key 401:其余 key 正常;`engine_status.last_round.failed_api_key_ids` 含之 diff --git a/docs/design/技术选型.md b/docs/design/技术选型.md index 8a0e017..f38fd71 100644 --- a/docs/design/技术选型.md +++ b/docs/design/技术选型.md @@ -22,7 +22,6 @@ | 调度 | **独立进程 `apps/engine`**:手写 `setTimeout` 循环(细则见《同步引擎设计》) | | 加密 | WebCrypto(Bun 原生) | | 前端 | **Vue 3 + antdv-next + Vite** | -| 部署 | **Docker**(倾向,§6) | ## 2. 语言:TypeScript 全栈 @@ -39,7 +38,7 @@ | 数据访问 | **Drizzle ORM** | TS 优先、查询保持 SQL 形态(不藏 SQL,利于学与审计);schema 即代码,drizzle-kit 迁移配套齐全 | Kysely / 裸驱动 + 手写仓储 | | pg 驱动 | **postgres.js** | Drizzle 文档主推;查询构建器式 API;内置连接池;Bun 兼容好 | node-postgres | | 前端框架 | **Vue 3** | 作者近年常用栈;实现期自由度(共性 §3.5「作者即前端」) | — | -| 组件库 | **antdv-next**(作者指定尝试项) | Vue 3 企业组件库、对齐 ant-design(React 上游)设计体系;**已核验通过**(§7.1):活跃周更、§3.7 清单逐项有对应组件、Agent 文档齐备 | Element Plus / ant-design-vue(§3.7 全覆盖) | +| 组件库 | **antdv-next**(作者指定尝试项) | Vue 3 企业组件库、对齐 ant-design(React 上游)设计体系;**已核验通过**(§6.1):活跃周更、§3.7 清单逐项有对应组件、Agent 文档齐备 | Element Plus / ant-design-vue(§3.7 全覆盖) | | 加密 | **WebCrypto** | HKDF / AES-256-GCM / HMAC-SHA256 / PBKDF2 与裁决一致(密钥体系见各表设计);Bun 原生 | HKDF 若 Bun 覆盖滞后:基于 HMAC 的纯 TS 垫片(同原语) | | 构建 / 测试 | **Vite**(web);质量门 = `typecheck` + `build` + 人工验收 | 前端构建走 Vite 生态默认;自动化测试不在当前范围 | 需要时再引入测试框架 | | API 文档与校验 | **zod** + **@hono/zod-openapi** + **Scalar**(作者确认) | 一源三用:TS 类型 / 请求校验(`422` 与 `fields` 的机器来源)/ OpenAPI 生成;Scalar 为 Hono 官方搭档,规范与渲染器解耦 | Swagger UI(规范不动、换皮即可) | @@ -69,19 +68,15 @@ R13 的 CFW 抽取版仍是长期裁决,功能全集实现后启动,届时 Hono(双运行时可跑)与 WebCrypto 本身恰好与 CFW 兼容,属顺手之得,不构成现阶段约束。 -## 6. 部署通道(倾向,细则归后续部署设计) - -母体以 **Docker 镜像**交付(server 容器 + PostgreSQL,compose 编排):满足仓库「一条命令链可部署」约定;运营方服务器零环境依赖(Bun 运行时随镜像内置),升级 = 换镜像重启;作者不接触服务器,镜像化把运行时选择与运营方环境解耦。 - -## 7. 风险与回退 +## 6. 风险与回退 | 风险 | 评估 | 对策 | |------|------|------| -| antdv-next 项目年轻、维护者 3 人 | 桌面核验通过(§7.1);未核验项:社区采用规模 | 实现期第一步做 **§3.7 能力 spike**(实测);锁版本;实测不满足即触发回退(Element Plus / ant-design-vue) | +| antdv-next 项目年轻、维护者 3 人 | 桌面核验通过(§6.1);未核验项:社区采用规模 | 实现期第一步做 **§3.7 能力 spike**(实测);锁版本;实测不满足即触发回退(Element Plus / ant-design-vue) | | Bun 生态资料少于 Node、边角兼容性 | 本项目规模小、依赖面窄(Hono / Drizzle / postgres.js 均成熟) | **Bun → Node 回退不伤业务代码**:换 `@hono/node-server` 装配即可,packages/* 原样 | | Bun 的 WebCrypto 原语覆盖(HKDF 等) | 未逐项实测 | 实现期首轮加密任务实测;缺失则 HMAC 纯 TS 垫片 | -### 7.1 antdv-next 核验记录(2026-10-02,一手源直查) +### 6.1 antdv-next 核验记录(2026-10-02,一手源直查) 口径:npm registry / GitHub API / 官网 llms.txt 直查(当轮无浏览器检索通道——社区口碑与采用规模未核验)。 @@ -96,7 +91,6 @@ Hono(双运行时可跑)与 WebCrypto 本身恰好与 CFW 兼容,属顺手 结论:**通过核验,维持选定**;§3.7 spike 与回退预案照旧——桌面核验不替代实测。 -## 8. 待裁决项 +## 7. 待裁决项 -1. Docker 细则:基础镜像、compose 形态、密钥注入方式——部署设计时定; -2. 前端测试设施是否引入 vitest。 +1. 前端测试设施是否引入 vitest。