From fd0f80b226b859daec14353223451498fb04140c Mon Sep 17 00:00:00 2001 From: lili Date: Sat, 27 Jun 2026 11:22:53 -0700 Subject: [PATCH] =?UTF-8?q?docs(release):=20=E8=AE=B0=E5=BD=95mini?= =?UTF-8?q?=E9=83=A8=E7=BD=B2=E8=AF=81=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- docs/mvp/1.0.0-RC发布说明.md | 17 +++- docs/mvp/进度总账.md | 2 +- scripts/deploy/mini-desktop/start-backend.sh | 42 ++++++++++ scripts/deploy/mini-desktop/start-redis.sh | 35 ++++++++ scripts/deploy/mini-desktop/static-proxy.mjs | 88 ++++++++++++++++++++ 5 files changed, 180 insertions(+), 4 deletions(-) create mode 100644 scripts/deploy/mini-desktop/start-backend.sh create mode 100644 scripts/deploy/mini-desktop/start-redis.sh create mode 100644 scripts/deploy/mini-desktop/static-proxy.mjs diff --git a/docs/mvp/1.0.0-RC发布说明.md b/docs/mvp/1.0.0-RC发布说明.md index 029b8fcb..d676d718 100644 --- a/docs/mvp/1.0.0-RC发布说明.md +++ b/docs/mvp/1.0.0-RC发布说明.md @@ -35,7 +35,18 @@ Muse 1.0.0 线 A 已具备最小可用闭环:作品写作、AI 真生成、候 - Studio:`tsc -b --force` 通过;ESLint 0 errors / 1 既有 warning;Vitest 27 files / 119 tests passed;Playwright MSW-off 全量 61 tests = 60 passed / 1 skipped。 - Admin:`@vben/web-antd typecheck` 通过;Vitest 50 files / 347 tests passed。 -## 4. 已知边界 +## 4. mini-desktop 部署 + +- 部署时间:2026-06-27 11:20-11:28 PDT。 +- 入口:`mini-desktop` 实际 SSH 入口为 `root@100.64.0.7`(hostname `minione-ubuntu-desktop`)。 +- RC 目录:`/opt/muse-rc1`。 +- 端口:后端 `48081`,Studio `4175`,Admin `4176`;保留既有 `48080/4173/4174` 上其它项目进程。 +- 运行依赖:已安装 OpenJDK 21;Redis 使用 mini 本机 `127.0.0.1:6379` 受控实例,密码来自 `/root/.config/muse-repo/infra.env`,只监听 loopback;PostgreSQL 仍连 `100.64.0.8:5433/muse_slice_live`。 +- 前端反代:`scripts/deploy/mini-desktop/static-proxy.mjs` 负责静态文件与 `/app-api`、`/admin-api` 反代到 `127.0.0.1:48081`。 +- Smoke:后端 `GET /app-api/muse/agents`、`/works`、`/knowledge-bases` 均返回 `code=0`;`http://100.64.0.7:4175/` 与 `http://100.64.0.7:4176/` 均 HTTP 200;经 Studio 端口反代 `/app-api/muse/works` 返回 `code=0`;经 Admin 端口反代 `/admin-api/muse/ai/agents` 返回 `code=0`。 +- 部署修正:远端默认缺 Java 21 与 Redis;`apt update` 受既有 Docker 源 GPG key 缺失影响,但直接安装 Ubuntu `openjdk-21-jre-headless`、`redis-server` 成功。`/actuator/health` 在当前应用被业务 404 包装成 HTTP 200,不作为健康证据。 + +## 5. 已知边界 - GraphRAG attribution live IT 未配置独立 opt-in,当前 honest skipped,不计为线 A passed。 - `market-install-kb-retrieval.spec.ts` D0-fork UI 物化用例仍 `test.fixme`,后端 D0-fork/KB handoff/召回已有真 PG 与 e2e 切片证据。 @@ -45,7 +56,7 @@ Muse 1.0.0 线 A 已具备最小可用闭环:作品写作、AI 真生成、候 - Meta impact preview 的生产 owner usage contributor 仍要求 all-real;本轮 Meta lifecycle IT 的 `VERIFIED_ZERO` 只证明状态机和装配门。 - 当前 Node v26.3.0 超出 Admin package engines 推荐范围,但本轮 typecheck/Vitest 已通过;发布流水建议使用 package engines 声明的 Node 版本。 -## 5. 复跑命令 +## 6. 复跑命令 ```bash cd muse-cloud @@ -70,7 +81,7 @@ pnpm -F @vben/web-antd run typecheck pnpm test:unit ``` -## 6. 回滚策略 +## 7. 回滚策略 - 后端按 Epic/模块回滚,优先回滚最近变更;涉及 Flyway 的已应用迁移不做 destructive rollback,采用前向修复。 - 前端回滚按 Studio/Admin 独立包回滚;Playwright global setup 夹具变更若导致环境污染,先禁用对应 e2e 并复位 fixture,再决定代码回退。 diff --git a/docs/mvp/进度总账.md b/docs/mvp/进度总账.md index b0d248bc..a1763724 100644 --- a/docs/mvp/进度总账.md +++ b/docs/mvp/进度总账.md @@ -39,7 +39,7 @@ **E8 发布候选冻结与总验(2026-06-27)**:E0-E7 完成后进入 RC 总验。后端三层 fresh 复跑已过:`local` 61/0F/0E/0S;`real-pg` 使用真实 PostgreSQL `_test` 库 `muse_p1r_100_release_test`,47 份 fresh surefire 报告、194/0F/0E/0S;`external-live` 真实调用 New-API/RAGFlow,server live 8/0F/0E/0S + module live 4/0F/0E/1S,`BUILD SUCCESS`。唯一 skip 为 GraphRAG attribution 独立 opt-in 未配置,不计为线 A passed;New-API completion、AI runtime usage/quota/attribution、RAGFlow upload/parse/retrieval、Knowledge runtime、Market KB D0-fork 与 after-commit 均真跑通过。本轮 real-PG 先暴露并修正 1F/2E:market 召回断言从旧 fail-closed 语义对齐为当前来源状态事件发布语义,两个 content IT 补齐测试上下文 bean;另确认手工直跑 server IT 时必须让 surefire forked JVM 收到 `p1r.flyway.*` 和代理清理 `argLine`,改 module 后必须 `-am` 或 install 防 stale jar 假红。CI/package 兜底已补跑:首次 `mvn -B package --file pom.xml` 暴露 `MuseApiContractSupport` 缺 8 个 OpenAPI 操作登记(233/241),补齐后定向契约门 `MuseApiContractSupportTest` 4/0F/0E/0S,根 package 61 个 reactor 模块全 SUCCESS、`BUILD SUCCESS`。前端总验已过:Studio `tsc -b --force` 通过、ESLint 0 errors/1 既有 MSW warning、Vitest 27 files/119 tests passed、MSW-off Playwright 全量 61 tests=60 passed/1 skipped/0 failed;Admin `@vben/web-antd typecheck` 通过、Vitest 50 files/347 tests passed。首次 Studio 全量红暴露额度夹具缺失、work1 槽位被 agent-slot-bind 污染、知识库定位器过宽,已分别用 global-setup 额度复位/槽位隔离/卡片级定位修复;React key 重复 warning 已用 `source/installId/kbId` 复合 key 消除。RC 发布说明与已知边界已落 [1.0.0-RC发布说明](1.0.0-RC发布说明.md);T8.4 完成。边界:GraphRAG attribution skip、Studio D0-fork UI 物化 `test.fixme`、Admin Playwright 未跑、pay/report/bpm/mp 与线 B 后置项均不计为 1.0.0 passed。 -**RC 基线记录(2026-06-27 11:05 PDT)**:`v1.0.0-rc.1` 指向 `3585219 feat(mvp): 收束1.0.0线A交付闭环`,分支 `dev/1.0.0` 已推送到 `origin/dev/1.0.0`。部署到 `mini-desktop` 需另行 smoke 证明,不能仅凭 tag 视为已部署。 +**RC 基线记录(2026-06-27 11:05 PDT)**:`v1.0.0-rc.1` 指向 `3585219 feat(mvp): 收束1.0.0线A交付闭环`,分支 `dev/1.0.0` 已推送到 `origin/dev/1.0.0`。**mini-desktop 部署已完成(2026-06-27 11:28 PDT)**:实际入口 `root@100.64.0.7`,部署目录 `/opt/muse-rc1`,端口后端 `48081` / Studio `4175` / Admin `4176`,保留既有 `48080/4173/4174` 其它项目进程。远端补齐 OpenJDK 21 与本机 loopback Redis;后端业务 smoke `GET /app-api/muse/agents|works|knowledge-bases` 均 `code=0`,Studio/Admin HTML 均 HTTP 200,经 `4175/app-api/muse/works` 与 `4176/admin-api/muse/ai/agents` 反代均 `code=0`。部署细节与边界见 [1.0.0-RC发布说明 §4](1.0.0-RC发布说明.md#4-mini-desktop-部署)。 **market 写路径整改(2026-06-14,ultracode)**:member 暴露写端口 `MuseAccountRecordProjectionApi` + DTO(member-server 实现读写自有 DAL、tenantId 由实现侧从上下文注入防伪造、事务沿用调用方),market 5 类改消费端口、移除 member.dal 依赖 → `KNOWN_VIOLATION_EXEMPTIONS` 清空、BC 门全绿。**附带修复**:round-2 重构 `ContentKnowledgeWorkOwnerFacade` 时遗留的旧测试 `KnowledgeWorkOwnerFacadeTest`(仍断言旧 WorkMapper 行为)已删除,其装配守卫/兜底两用例并入 `ContentKnowledgeWorkOwnerFacadeTest`(5/0F)——此为 round-2 一处假绿(当时构建在平台时区用例处中止、未真正跑到 knowledge),现已补正。 diff --git a/scripts/deploy/mini-desktop/start-backend.sh b/scripts/deploy/mini-desktop/start-backend.sh new file mode 100644 index 00000000..08ee5d29 --- /dev/null +++ b/scripts/deploy/mini-desktop/start-backend.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash +set -euo pipefail + +# mini-desktop 只承载 Muse RC 进程,基础设施继续复用 mini-ubuntu-infra。 +# 凭据文件只在运行时 source,脚本本身绝不保存密码、Token 或 API Key。 +ROOT="${MUSE_RC_ROOT:-/opt/muse-rc1}" +ENV_FILE="${MUSE_INFRA_ENV:-/root/.config/muse-repo/infra.env}" +AI_ENV_FILE="${MUSE_AI_ENV:-/root/.config/muse-repo/p1r-external-acceptance.env}" + +if [[ -f "${ENV_FILE}" ]]; then + set -a + # shellcheck disable=SC1090 + source "${ENV_FILE}" + set +a +fi + +if [[ -f "${AI_ENV_FILE}" ]]; then + set -a + # shellcheck disable=SC1090 + source "${AI_ENV_FILE}" + set +a +fi + +# Redis 由本部署目录的 start-redis.sh 在 mini-desktop 本机拉起;这里保留 env 默认值。 +export MUSE_POSTGRES_HOST="${MUSE_POSTGRES_HOST:-100.64.0.8}" +export MUSE_POSTGRES_PORT="${MUSE_POSTGRES_PORT:-5433}" +export MUSE_POSTGRES_DATABASE="${MUSE_POSTGRES_DATABASE:-muse_slice_live}" +export MUSE_POSTGRES_USERNAME="${MUSE_POSTGRES_USERNAME:-root}" +export MUSE_REDIS_HOST="${MUSE_REDIS_HOST:-127.0.0.1}" +export MUSE_REDIS_PORT="${MUSE_REDIS_PORT:-6379}" + +exec /usr/lib/jvm/java-21-openjdk-amd64/bin/java \ + -Xms512m -Xmx2g \ + -Dhttp.proxyHost= \ + -Dhttps.proxyHost= \ + -Djava.net.useSystemProxies=false \ + -DsocksProxyHost= \ + -DsocksProxyPort= \ + -jar "${ROOT}/backend/muse-server.jar" \ + --server.port=48081 \ + --spring.profiles.active=local,infra \ + --muse.codegen.import-enable=false diff --git a/scripts/deploy/mini-desktop/start-redis.sh b/scripts/deploy/mini-desktop/start-redis.sh new file mode 100644 index 00000000..d90b9d0d --- /dev/null +++ b/scripts/deploy/mini-desktop/start-redis.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +set -euo pipefail + +# RC 部署仅需要一个本机 Redis 缓存/会话实例;密码从私有 env 读取,配置文件仅 root 可读。 +ROOT="${MUSE_RC_ROOT:-/opt/muse-rc1}" +ENV_FILE="${MUSE_INFRA_ENV:-/root/.config/muse-repo/infra.env}" +REDIS_PORT="${MUSE_REDIS_PORT:-6379}" + +if [[ -f "${ENV_FILE}" ]]; then + set -a + # shellcheck disable=SC1090 + source "${ENV_FILE}" + set +a +fi + +if [[ -z "${MUSE_REDIS_PASSWORD:-}" ]]; then + echo "缺少 MUSE_REDIS_PASSWORD,拒绝启动 Redis。" >&2 + exit 1 +fi + +install -d -m 700 "${ROOT}/redis" "${ROOT}/logs" "${ROOT}/run" +umask 077 +cat > "${ROOT}/redis/redis.conf" < { + const [key, ...rest] = arg.replace(/^--/, '').split('='); + return [key, rest.join('=') || '']; + }), +); + +const root = resolve(args.root || '.'); +const port = Number(args.port || 4175); +const backend = new URL(args.backend || 'http://127.0.0.1:48081'); +const proxyPrefixes = (args.proxy || '/app-api,/admin-api,/sse').split(',').filter(Boolean); + +const contentTypes = new Map([ + ['.html', 'text/html; charset=utf-8'], + ['.js', 'text/javascript; charset=utf-8'], + ['.mjs', 'text/javascript; charset=utf-8'], + ['.css', 'text/css; charset=utf-8'], + ['.json', 'application/json; charset=utf-8'], + ['.png', 'image/png'], + ['.jpg', 'image/jpeg'], + ['.jpeg', 'image/jpeg'], + ['.svg', 'image/svg+xml'], + ['.ico', 'image/x-icon'], + ['.woff', 'font/woff'], + ['.woff2', 'font/woff2'], + ['.ttf', 'font/ttf'], + ['.eot', 'application/vnd.ms-fontobject'], +]); + +function sendNotFound(res) { + res.writeHead(404, { 'content-type': 'text/plain; charset=utf-8' }); + res.end('Not Found'); +} + +function proxy(req, res) { + const target = new URL(req.url || '/', backend); + const headers = { ...req.headers, host: backend.host }; + const upstream = http.request(target, { method: req.method, headers }, (proxyRes) => { + res.writeHead(proxyRes.statusCode || 502, proxyRes.headers); + proxyRes.pipe(res); + }); + + upstream.on('error', (error) => { + res.writeHead(502, { 'content-type': 'application/json; charset=utf-8' }); + res.end(JSON.stringify({ code: 502, msg: `backend unavailable: ${error.code || error.message}` })); + }); + + req.pipe(upstream); +} + +function serve(req, res) { + const url = new URL(req.url || '/', `http://${req.headers.host || 'localhost'}`); + if (proxyPrefixes.some((prefix) => url.pathname === prefix || url.pathname.startsWith(`${prefix}/`))) { + proxy(req, res); + return; + } + + const decodedPath = decodeURIComponent(url.pathname); + const relative = normalize(decodedPath).replace(/^\/+/, ''); + let file = resolve(join(root, relative)); + if (!file.startsWith(root)) { + sendNotFound(res); + return; + } + + // SPA 深链统一回落到 index.html,避免刷新路由时 404。 + if (!existsSync(file) || statSync(file).isDirectory()) { + file = join(root, 'index.html'); + } + if (!existsSync(file) || statSync(file).isDirectory()) { + sendNotFound(res); + return; + } + + const type = contentTypes.get(extname(file).toLowerCase()) || 'application/octet-stream'; + res.writeHead(200, { 'content-type': type }); + createReadStream(file).pipe(res); +} + +http.createServer(serve).listen(port, '0.0.0.0', () => { + console.log(`static proxy listening on ${port}, root=${root}, backend=${backend.origin}`); +});