Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 753c1eec23 | |||
| 62fbea0936 | |||
| 871688550e |
+3
-3
@@ -215,7 +215,7 @@ H5_PUBLIC_BASE_URL=http://127.0.0.1:5173
|
|||||||
# 登录仍在主域完成;后台凭 H5_COOKIE_DOMAIN=.tkmind.cn 共享的会话 Cookie 鉴权(生产务必设置)。
|
# 登录仍在主域完成;后台凭 H5_COOKIE_DOMAIN=.tkmind.cn 共享的会话 Cookie 鉴权(生产务必设置)。
|
||||||
ADMIN_PORT=8082
|
ADMIN_PORT=8082
|
||||||
ADMIN_HOST=127.0.0.1
|
ADMIN_HOST=127.0.0.1
|
||||||
ADMIN_PUBLIC_HOST=gadm.tkmind.cn
|
ADMIN_PUBLIC_HOST=md.tkmind.cn
|
||||||
|
|
||||||
# 本进程挂载哪些控制台(可拆分架构):admin=平台超管, ops=广场运营。
|
# 本进程挂载哪些控制台(可拆分架构):admin=平台超管, ops=广场运营。
|
||||||
# 默认两者合一;将来拆成两个进程时,A 实例设 admin、B 实例设 ops,路由代码无需改动。
|
# 默认两者合一;将来拆成两个进程时,A 实例设 admin、B 实例设 ops,路由代码无需改动。
|
||||||
@@ -223,9 +223,9 @@ ADMIN_PUBLIC_HOST=gadm.tkmind.cn
|
|||||||
|
|
||||||
# 每控制台独立网络护栏(逗号分隔;留空=放开。支持精确 IP 与 CIDR)。
|
# 每控制台独立网络护栏(逗号分隔;留空=放开。支持精确 IP 与 CIDR)。
|
||||||
# 超管面建议收得最紧(仅内网/VPN),广场运营可放宽给审核团队网段。
|
# 超管面建议收得最紧(仅内网/VPN),广场运营可放宽给审核团队网段。
|
||||||
# ADMIN_API_ALLOWED_HOSTS=gadm.tkmind.cn
|
# ADMIN_API_ALLOWED_HOSTS=md.tkmind.cn
|
||||||
# ADMIN_API_IP_ALLOWLIST=10.0.0.0/8,127.0.0.1
|
# ADMIN_API_IP_ALLOWLIST=10.0.0.0/8,127.0.0.1
|
||||||
# OPS_API_ALLOWED_HOSTS=gadm.tkmind.cn
|
# OPS_API_ALLOWED_HOSTS=md.tkmind.cn
|
||||||
# OPS_API_IP_ALLOWLIST=
|
# OPS_API_IP_ALLOWLIST=
|
||||||
|
|
||||||
# 多用户系统(MySQL 配置后启用;未配置时回退到 H5_ACCESS_PASSWORD)
|
# 多用户系统(MySQL 配置后启用;未配置时回退到 H5_ACCESS_PASSWORD)
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ Cursor 额外加载:`.cursor/rules/mindspace-publish-chat-finish-guards.mdc`
|
|||||||
|
|
||||||
## 必读:管理后台 UI 边界(memind_adm 5174)
|
## 必读:管理后台 UI 边界(memind_adm 5174)
|
||||||
|
|
||||||
**平台管理后台的前端 UI 只能在独立仓库 `memind_adm`(本地 5174 / 生产 gadm)开发,禁止在 Memind 本仓库的 `ops/`(约 3002)新增管理功能。**
|
**平台管理后台的前端 UI 只能在独立仓库 `memind_adm`(本地 5174 / 生产 [md.tkmind.cn](https://md.tkmind.cn))开发,禁止在 Memind 本仓库的 `ops/`(约 3002)新增管理功能。**
|
||||||
|
|
||||||
| 层级 | 位置 | 说明 |
|
| 层级 | 位置 | 说明 |
|
||||||
|------|------|------|
|
|------|------|------|
|
||||||
|
|||||||
@@ -15,10 +15,10 @@ test('parseList splits, trims, and drops empties', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('hostAllowed: empty list allows all, otherwise matches hostname ignoring port/case', () => {
|
test('hostAllowed: empty list allows all, otherwise matches hostname ignoring port/case', () => {
|
||||||
assert.equal(hostAllowed('gadm.tkmind.cn:8082', []), true);
|
assert.equal(hostAllowed('md.tkmind.cn:8082', []), true);
|
||||||
assert.equal(hostAllowed('GADM.tkmind.cn', ['gadm.tkmind.cn']), true);
|
assert.equal(hostAllowed('MD.tkmind.cn', ['md.tkmind.cn']), true);
|
||||||
assert.equal(hostAllowed('evil.example.com', ['gadm.tkmind.cn']), false);
|
assert.equal(hostAllowed('evil.example.com', ['md.tkmind.cn']), false);
|
||||||
assert.equal(hostAllowed('gadm.tkmind.cn:443', ['gadm.tkmind.cn', 'localhost']), true);
|
assert.equal(hostAllowed('md.tkmind.cn:443', ['md.tkmind.cn', 'localhost']), true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('ipMatches: exact and IPv4-mapped IPv6', () => {
|
test('ipMatches: exact and IPv4-mapped IPv6', () => {
|
||||||
@@ -48,7 +48,7 @@ test('buildNetworkGuard returns null when nothing configured', () => {
|
|||||||
|
|
||||||
test('buildNetworkGuard denies on host then ip, allows when both pass', () => {
|
test('buildNetworkGuard denies on host then ip, allows when both pass', () => {
|
||||||
const guard = buildNetworkGuard({
|
const guard = buildNetworkGuard({
|
||||||
allowedHosts: ['gadm.tkmind.cn'],
|
allowedHosts: ['md.tkmind.cn'],
|
||||||
ipAllowlist: ['10.0.0.0/8'],
|
ipAllowlist: ['10.0.0.0/8'],
|
||||||
onDeny: (_res, _req, reason) => `deny:${reason}`,
|
onDeny: (_res, _req, reason) => `deny:${reason}`,
|
||||||
});
|
});
|
||||||
@@ -60,8 +60,8 @@ test('buildNetworkGuard denies on host then ip, allows when both pass', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
assert.equal(guard(mk('evil.com', '10.1.1.1'), {}, next), 'deny:host');
|
assert.equal(guard(mk('evil.com', '10.1.1.1'), {}, next), 'deny:host');
|
||||||
assert.equal(guard(mk('gadm.tkmind.cn', '8.8.8.8'), {}, next), 'deny:ip');
|
assert.equal(guard(mk('md.tkmind.cn', '8.8.8.8'), {}, next), 'deny:ip');
|
||||||
assert.equal(nexted, false);
|
assert.equal(nexted, false);
|
||||||
guard(mk('gadm.tkmind.cn', '10.1.1.1'), {}, next);
|
guard(mk('md.tkmind.cn', '10.1.1.1'), {}, next);
|
||||||
assert.equal(nexted, true);
|
assert.equal(nexted, true);
|
||||||
});
|
});
|
||||||
|
|||||||
+2
-2
@@ -5,7 +5,7 @@
|
|||||||
// Serves the platform super-admin API (/admin-api/*) and the plaza operations
|
// Serves the platform super-admin API (/admin-api/*) and the plaza operations
|
||||||
// console API (/api/ops/v1/*) on its own port, isolated from the public
|
// console API (/api/ops/v1/*) on its own port, isolated from the public
|
||||||
// user-facing server (server.mjs). In production this is fronted by
|
// user-facing server (server.mjs). In production this is fronted by
|
||||||
// gadm.tkmind.cn; in dev the ops SPA (:3002) proxies to it directly.
|
// md.tkmind.cn; in dev the ops SPA (:3002) proxies to it directly.
|
||||||
//
|
//
|
||||||
// It trusts the shared session cookie (H5_COOKIE_DOMAIN=.tkmind.cn), so login
|
// It trusts the shared session cookie (H5_COOKIE_DOMAIN=.tkmind.cn), so login
|
||||||
// continues to happen on the main app domain — this process only reads the
|
// continues to happen on the main app domain — this process only reads the
|
||||||
@@ -43,7 +43,7 @@ loadEnvFile(path.join(__dirname, '.env'));
|
|||||||
|
|
||||||
const PORT = Number(process.env.ADMIN_PORT ?? 8082);
|
const PORT = Number(process.env.ADMIN_PORT ?? 8082);
|
||||||
const HOST = process.env.ADMIN_HOST ?? '127.0.0.1';
|
const HOST = process.env.ADMIN_HOST ?? '127.0.0.1';
|
||||||
const PUBLIC_HOST = process.env.ADMIN_PUBLIC_HOST ?? 'gadm.tkmind.cn';
|
const PUBLIC_HOST = process.env.ADMIN_PUBLIC_HOST ?? 'md.tkmind.cn';
|
||||||
|
|
||||||
const getToken = (req) => parseCookies(req.get('cookie'))[USER_COOKIE];
|
const getToken = (req) => parseCookies(req.get('cookie'))[USER_COOKIE];
|
||||||
|
|
||||||
|
|||||||
@@ -606,6 +606,14 @@ function sandboxMcpEnvs(sandboxMcp, mcpTools) {
|
|||||||
envs[key] = String(value).trim();
|
envs[key] = String(value).trim();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// goosed stdio MCPs do not inherit Portal env; forward worker flags so
|
||||||
|
// scheduled_task_create can report the same enablement as server.mjs.
|
||||||
|
for (const key of ['H5_REMINDER_WORKER_ENABLED', 'H5_SCHEDULED_TASK_WORKER_ENABLED']) {
|
||||||
|
const value = process.env[key];
|
||||||
|
if (value != null && String(value).trim() !== '') {
|
||||||
|
envs[key] = String(value).trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
if (mcpTools.includes('generate_image')) {
|
if (mcpTools.includes('generate_image')) {
|
||||||
if (sandboxMcp.agentApiBaseUrl) {
|
if (sandboxMcp.agentApiBaseUrl) {
|
||||||
envs.MINDSPACE_AGENT_API_BASE_URL = sandboxMcp.agentApiBaseUrl;
|
envs.MINDSPACE_AGENT_API_BASE_URL = sandboxMcp.agentApiBaseUrl;
|
||||||
|
|||||||
@@ -507,6 +507,31 @@ test('static_publish with sandboxMcp uses stdio sandbox-fs extension instead of
|
|||||||
assert.ok(!allTools.includes('shell'), 'shell should not be exposed');
|
assert.ok(!allTools.includes('shell'), 'shell should not be exposed');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('sandboxMcpEnvs forwards scheduled task worker flags from portal env', () => {
|
||||||
|
const previousReminder = process.env.H5_REMINDER_WORKER_ENABLED;
|
||||||
|
const previousScheduled = process.env.H5_SCHEDULED_TASK_WORKER_ENABLED;
|
||||||
|
process.env.H5_REMINDER_WORKER_ENABLED = '1';
|
||||||
|
delete process.env.H5_SCHEDULED_TASK_WORKER_ENABLED;
|
||||||
|
try {
|
||||||
|
const caps = { ...DEFAULT_USER_CAPABILITIES, static_publish: true };
|
||||||
|
const policy = buildAgentExtensionPolicy(caps, {
|
||||||
|
sandboxMcp: {
|
||||||
|
serverPath: '/opt/portal/mindspace-sandbox-mcp.mjs',
|
||||||
|
sandboxRoot: '/opt/h5/MindSpace/abc123',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const sandboxExt = policy.extensionOverrides.find((ext) => ext.name === 'sandbox-fs');
|
||||||
|
assert.ok(sandboxExt);
|
||||||
|
assert.equal(sandboxExt.envs.H5_REMINDER_WORKER_ENABLED, '1');
|
||||||
|
assert.equal(sandboxExt.envs.H5_SCHEDULED_TASK_WORKER_ENABLED, undefined);
|
||||||
|
} finally {
|
||||||
|
if (previousReminder === undefined) delete process.env.H5_REMINDER_WORKER_ENABLED;
|
||||||
|
else process.env.H5_REMINDER_WORKER_ENABLED = previousReminder;
|
||||||
|
if (previousScheduled === undefined) delete process.env.H5_SCHEDULED_TASK_WORKER_ENABLED;
|
||||||
|
else process.env.H5_SCHEDULED_TASK_WORKER_ENABLED = previousScheduled;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
test('sandboxMcp emits a scoped logical workspace token only for a bound session', () => {
|
test('sandboxMcp emits a scoped logical workspace token only for a bound session', () => {
|
||||||
const secret =
|
const secret =
|
||||||
'mindspace-mcp-policy-secret-1234';
|
'mindspace-mcp-policy-secret-1234';
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ Memind 公开页与生成页的真实用户访问统计 **仅使用 Umami**(`m
|
|||||||
|------|------|------|
|
|------|------|------|
|
||||||
| 1 | memind-analytics | `bash scripts/release-umami-105.sh --yes` |
|
| 1 | memind-analytics | `bash scripts/release-umami-105.sh --yes` |
|
||||||
| 2 | Memind(103 Portal) | `bash scripts/release-portal-runtime-prod.sh --yes` |
|
| 2 | Memind(103 Portal) | `bash scripts/release-portal-runtime-prod.sh --yes` |
|
||||||
| 3 | memind_adm(103 gadm) | `bash scripts/release-prod.sh --yes` |
|
| 3 | memind_adm(103 · md.tkmind.cn) | `bash scripts/release-prod.sh --yes` |
|
||||||
|
|
||||||
105 Umami 细节见 memind-analytics 仓库 `deploy/105-runbook.md`。
|
105 Umami 细节见 memind-analytics 仓库 `deploy/105-runbook.md`。
|
||||||
gadm Analytics 环境变量见 memind_adm `docs/analytics-deploy.md`。
|
md.tkmind.cn Analytics 环境变量见 memind_adm `docs/analytics-deploy.md`。
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ memind_adm SEO/GEO 看板
|
|||||||
| 2 | **Memind** | 改了 discovery 埋点、Umami 代理、公开页 delivery | `bash scripts/release-portal-runtime-prod.sh` |
|
| 2 | **Memind** | 改了 discovery 埋点、Umami 代理、公开页 delivery | `bash scripts/release-portal-runtime-prod.sh` |
|
||||||
| 3 | **memind_adm** | 改了 SEO/GEO 看板、Analytics 配置页、Umami 代理 | `bash scripts/release-prod.sh --yes` |
|
| 3 | **memind_adm** | 改了 SEO/GEO 看板、Analytics 配置页、Umami 代理 | `bash scripts/release-prod.sh --yes` |
|
||||||
|
|
||||||
只改 overlay / API 时,发 **105** 即可;只改 adm 看板 UI 时,发 **103 gadm** 即可。
|
只改 overlay / API 时,发 **105** 即可;只改 adm 看板 UI 时,发 **103 md.tkmind.cn(memind_adm)** 即可。
|
||||||
Memind discovery 字段(`discovery_channel` 等)未上线前,adm 看板可能暂无 SEO/GEO 分列数据。
|
Memind discovery 字段(`discovery_channel` 等)未上线前,adm 看板可能暂无 SEO/GEO 分列数据。
|
||||||
|
|
||||||
## 1. 105 Umami(memind-analytics)
|
## 1. 105 Umami(memind-analytics)
|
||||||
@@ -92,22 +92,20 @@ bash scripts/release-prod.sh --yes
|
|||||||
### 生产 `.env`(Analytics 相关)
|
### 生产 `.env`(Analytics 相关)
|
||||||
|
|
||||||
```env
|
```env
|
||||||
# SEO/GEO 看板拉 Umami memind-pages API 所需
|
# SEO/GEO 看板拉 Umami memind-pages API 所需(105 SSO-only 时用 MEMIND_UMAMI_SSO_SECRET)
|
||||||
UMAMI_ADMIN_PASSWORD=<与 Umami admin 账号一致>
|
|
||||||
UMAMI_URL=https://fxi.tkmind.cn # 可选,默认 http://127.0.0.1:3100
|
|
||||||
UMAMI_WEBSITE_ID=<Memind Generated Pages 的 Website ID>
|
|
||||||
|
|
||||||
# Umami SSO(Analytics 配置页「打开 Umami」)
|
|
||||||
MEMIND_UMAMI_SSO_SECRET=<与 105 .env 一致>
|
MEMIND_UMAMI_SSO_SECRET=<与 105 .env 一致>
|
||||||
UMAMI_SSO_USERNAME=admin
|
UMAMI_SSO_USERNAME=admin
|
||||||
|
UMAMI_URL=https://fxi.tkmind.cn # 可选,默认 http://127.0.0.1:3100
|
||||||
|
UMAMI_WEBSITE_ID=<Memind 公共页面 Website ID>
|
||||||
|
H5_PUBLIC_BASE_URL=https://md.tkmind.cn
|
||||||
```
|
```
|
||||||
|
|
||||||
103 需能访问 105 Umami(内网或配置的 `UMAMI_URL`)。
|
103 需能访问 105 Umami(内网或配置的 `UMAMI_URL`)。
|
||||||
|
|
||||||
### 验收
|
### 验收
|
||||||
|
|
||||||
1. gadm → **Analytics 配置** → 「打开 Umami 分析后台」可 SSO 登录
|
1. [md.tkmind.cn/analytics](https://md.tkmind.cn/analytics) → **Analytics 配置** → 「打开 Umami 分析后台」可 SSO 登录
|
||||||
2. gadm → **SEO / GEO 流量** → 表格加载无 `未配置 UMAMI_ADMIN_PASSWORD`
|
2. [md.tkmind.cn/analytics/seo-geo](https://md.tkmind.cn/analytics/seo-geo) → **SEO / GEO 流量** → 表格加载无 SSO/凭证错误
|
||||||
3. 抽样对比 Umami「所有生成页面明细」与 adm 看板列口径一致
|
3. 抽样对比 Umami「所有生成页面明细」与 adm 看板列口径一致
|
||||||
|
|
||||||
详见 memind_adm [`docs/analytics-deploy.md`](../memind_adm/docs/analytics-deploy.md)。
|
详见 memind_adm [`docs/analytics-deploy.md`](../memind_adm/docs/analytics-deploy.md)。
|
||||||
@@ -130,4 +128,4 @@ UMAMI_SSO_USERNAME=admin
|
|||||||
| [local-analytics.md](local-analytics.md) | 本地 Umami 与 Memind env |
|
| [local-analytics.md](local-analytics.md) | 本地 Umami 与 Memind env |
|
||||||
| [105-server-operations.md](105-server-operations.md) | 105 运维边界(含 `/opt/memind-analytics`) |
|
| [105-server-operations.md](105-server-operations.md) | 105 运维边界(含 `/opt/memind-analytics`) |
|
||||||
| memind-analytics `README.md` | Umami 仓库运维与 overlay |
|
| memind-analytics `README.md` | Umami 仓库运维与 overlay |
|
||||||
| memind_adm `docs/analytics-deploy.md` | gadm Analytics / SEO·GEO 配置 |
|
| memind_adm `docs/analytics-deploy.md` | md.tkmind.cn Analytics / SEO·GEO 配置 |
|
||||||
|
|||||||
@@ -13,6 +13,33 @@
|
|||||||
- 远端功能分支默认在闭环后删除;发布和审计依据使用 `main` commit、tag、CI 记录或 runtime manifest,不使用功能分支名。
|
- 远端功能分支默认在闭环后删除;发布和审计依据使用 `main` commit、tag、CI 记录或 runtime manifest,不使用功能分支名。
|
||||||
- 这类保留分支必须在本文件登记清楚:分支名、最终 HEAD、进入 `origin/main` 的提交、验证摘要,以及“禁止再次引用”的状态。
|
- 这类保留分支必须在本文件登记清楚:分支名、最终 HEAD、进入 `origin/main` 的提交、验证摘要,以及“禁止再次引用”的状态。
|
||||||
|
|
||||||
|
## `feature/md-admin-domain`(Memind + memind_adm)
|
||||||
|
|
||||||
|
**状态:禁止再次引用。改动已提交并进入 `origin/main`,该分支保留仅用于只读追溯,不是待合并开发分支。**
|
||||||
|
|
||||||
|
审计日期:2026-08-11
|
||||||
|
分支 HEAD:`8716885`(Memind)、`0da7f04`(memind_adm)
|
||||||
|
`origin/main` 对应提交:同上
|
||||||
|
|
||||||
|
### 原始用途
|
||||||
|
|
||||||
|
- 管理后台公网域名从 `gadm.tkmind.cn` 更正为 `md.tkmind.cn`
|
||||||
|
- 文档与 nginx 示例明确 **105 反代 → 103 memind_adm**(5174/8085)
|
||||||
|
- memind_adm 默认 `VITE_BASE_PATH=/`,使 `/analytics/seo-geo` 在根路径可访问
|
||||||
|
|
||||||
|
### 生产发布
|
||||||
|
|
||||||
|
| 仓库 | release_id |
|
||||||
|
|------|------------|
|
||||||
|
| memind_adm | `20260810-173922-0da7f04` |
|
||||||
|
| 105 nginx | `md.tkmind.cn.conf` 已 reload |
|
||||||
|
|
||||||
|
### 验证摘要
|
||||||
|
|
||||||
|
- `https://md.tkmind.cn/analytics/seo-geo` → 200
|
||||||
|
- 103 `8085/health` → ok;5174 `base=/`
|
||||||
|
- `/ops/analytics/seo-geo` → 301 到 `/analytics/seo-geo`
|
||||||
|
|
||||||
## `feature/fix-rybbit-publication-tracking`
|
## `feature/fix-rybbit-publication-tracking`
|
||||||
|
|
||||||
**状态:禁止再次引用。改动已提交并进入 `origin/main`,该分支保留仅用于只读追溯,不是待合并开发分支。**
|
**状态:禁止再次引用。改动已提交并进入 `origin/main`,该分支保留仅用于只读追溯,不是待合并开发分支。**
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
> **重要:本目录不再接受新的管理后台功能开发。**
|
> **重要:本目录不再接受新的管理后台功能开发。**
|
||||||
>
|
>
|
||||||
> 所有平台管理后台 UI(用户、计费、图片额度、策略、模型中心等)必须在独立仓库 **`memind_adm`** 中实现,本地开发端口 **5174**(生产 gadm),API 端口 **8085**。
|
> 所有平台管理后台 UI(用户、计费、图片额度、策略、模型中心等)必须在独立仓库 **`memind_adm`** 中实现,本地开发端口 **5174**(生产 [md.tkmind.cn](https://md.tkmind.cn)),API 端口 **8085**。
|
||||||
>
|
>
|
||||||
> 本目录(Memind `ops/`,本地约 **3002**)仅保留 **Plaza 运营**相关页面(审核、精选、创作者等)及历史遗留的超级管理路由;**不得**在此新增页面、导航项或 API 客户端。
|
> 本目录(Memind `ops/`,本地约 **3002**)仅保留 **Plaza 运营**相关页面(审核、精选、创作者等)及历史遗留的超级管理路由;**不得**在此新增页面、导航项或 API 客户端。
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user