chore: add 105 runtime and openhands ops tooling
This commit is contained in:
@@ -14,6 +14,8 @@ const schemaFile = path.join(root, 'schema.sql');
|
||||
const esbuildBin = path.join(root, 'node_modules', '.pnpm', 'node_modules', '.bin', 'esbuild');
|
||||
const skipBuild = process.argv.includes('--skip-build');
|
||||
const skipNodeModules = process.argv.includes('--skip-node-modules');
|
||||
const runtimeNodeTarget = process.env.PORTAL_RUNTIME_NODE_TARGET || 'node24';
|
||||
const runtimeInstallMode = process.env.PORTAL_RUNTIME_INSTALL_MODE || 'bundle-node-modules';
|
||||
|
||||
const externalPackages = [
|
||||
'@node-rs/argon2',
|
||||
@@ -84,7 +86,7 @@ async function bundleServer() {
|
||||
'--bundle',
|
||||
'--platform=node',
|
||||
'--format=esm',
|
||||
'--target=node24',
|
||||
`--target=${runtimeNodeTarget}`,
|
||||
'--outfile=.runtime/portal/server.mjs',
|
||||
'--banner:js=import { createRequire as __createRequire } from "node:module"; const require = __createRequire(import.meta.url);',
|
||||
];
|
||||
@@ -104,7 +106,7 @@ async function bundleSandboxMcp() {
|
||||
'--bundle',
|
||||
'--platform=node',
|
||||
'--format=esm',
|
||||
'--target=node24',
|
||||
`--target=${runtimeNodeTarget}`,
|
||||
'--outfile=.runtime/portal/mindspace-sandbox-mcp.mjs',
|
||||
'--banner:js=import { createRequire as __createRequire } from "node:module"; const require = __createRequire(import.meta.url);',
|
||||
];
|
||||
@@ -175,10 +177,19 @@ async function rewriteNodeModulesSymlinks(runtimeNodeModulesDir, sourceNodeModul
|
||||
|
||||
async function writeMetadata() {
|
||||
const packageJson = JSON.parse(await fs.readFile(path.join(root, 'package.json'), 'utf8'));
|
||||
const runtimeDependencies = Object.fromEntries(
|
||||
externalPackages
|
||||
.filter((pkg) => packageJson.dependencies?.[pkg])
|
||||
.map((pkg) => [pkg, packageJson.dependencies[pkg]]),
|
||||
);
|
||||
const runtimePackageJson = {
|
||||
name: `${packageJson.name}-portal-runtime`,
|
||||
private: true,
|
||||
type: 'module',
|
||||
engines: {
|
||||
node: '>=22',
|
||||
},
|
||||
dependencies: runtimeDependencies,
|
||||
};
|
||||
await writeFile(path.join(runtimeRoot, 'package.json'), `${JSON.stringify(runtimePackageJson, null, 2)}\n`);
|
||||
await writeFile(
|
||||
@@ -203,14 +214,15 @@ async function writeMetadata() {
|
||||
'export TKMIND_API_TARGETS="${TKMIND_API_TARGETS:-https://127.0.0.1:18006,https://127.0.0.1:18007,https://127.0.0.1:18008,https://127.0.0.1:18009}"',
|
||||
'export TKMIND_API_TARGET="${TKMIND_API_TARGET:-https://127.0.0.1:18006}"',
|
||||
'export TKMIND_API_TARGET_1="${TKMIND_API_TARGET_1:-https://127.0.0.1:18007}"',
|
||||
'export GOOSED_MCP_NODE_PATH="${GOOSED_MCP_NODE_PATH:-/usr/local/bin/node}"',
|
||||
'export GOOSED_MCP_SERVER_PATH="${GOOSED_MCP_SERVER_PATH:-/opt/portal/mindspace-sandbox-mcp.mjs}"',
|
||||
'',
|
||||
'NODE_BIN="${NODE_BIN:-/opt/homebrew/opt/node@24/bin/node}"',
|
||||
'if [[ ! -x "${NODE_BIN}" ]]; then',
|
||||
' NODE_BIN="$(command -v node)"',
|
||||
'fi',
|
||||
'',
|
||||
'export GOOSED_MCP_NODE_PATH="${GOOSED_MCP_NODE_PATH:-${NODE_BIN}}"',
|
||||
'export GOOSED_MCP_SERVER_PATH="${GOOSED_MCP_SERVER_PATH:-${ROOT}/mindspace-sandbox-mcp.mjs}"',
|
||||
'',
|
||||
'exec "${NODE_BIN}" "${ROOT}/server.mjs"',
|
||||
'',
|
||||
].join('\n'),
|
||||
@@ -241,6 +253,9 @@ async function writeMetadata() {
|
||||
'Bundled alongside server.mjs (required for sandbox-fs MCP):',
|
||||
' mindspace-sandbox-mcp.mjs (esbuild bundle; includes schedule-service deps)',
|
||||
'',
|
||||
`Runtime install mode: ${runtimeInstallMode}`,
|
||||
`Bundle target: ${runtimeNodeTarget}`,
|
||||
'',
|
||||
'Key runtime differences must stay in .env, not in the artifact:',
|
||||
' DATABASE_URL / MYSQL_*',
|
||||
' H5_PUBLIC_BASE_URL',
|
||||
@@ -261,7 +276,9 @@ async function main() {
|
||||
await copyRuntimeAssets();
|
||||
await bundleServer();
|
||||
await bundleSandboxMcp();
|
||||
await copyNodeModules();
|
||||
if (runtimeInstallMode === 'bundle-node-modules' && !skipNodeModules) {
|
||||
await copyNodeModules();
|
||||
}
|
||||
await writeMetadata();
|
||||
await fs.chmod(path.join(runtimeRoot, 'scripts', 'run-memind-portal-prod.sh'), 0o755);
|
||||
await fs.chmod(path.join(runtimeRoot, 'scripts', 'wechat-mp-menu.mjs'), 0o755);
|
||||
|
||||
@@ -0,0 +1,223 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* LaunchAgent / 后台常驻版 dev 栈:portal + admin + ops + vite。
|
||||
* 单个子进程异常退出时会自动重启;启动阶段失败则整体退出,由 launchd KeepAlive 重试。
|
||||
*/
|
||||
import { spawn, execSync } from 'node:child_process';
|
||||
import fs from 'node:fs';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const root = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
|
||||
const opsDir = path.join(root, 'ops');
|
||||
const nodeBin = process.execPath;
|
||||
const portalPort = Number(process.env.H5_PORT ?? 8081);
|
||||
const vitePort = Number(process.env.VITE_PORT ?? 5173);
|
||||
const plazaPort = Number(process.env.PLAZA_PORT ?? 3001);
|
||||
const opsPort = Number(process.env.OPS_PORT ?? 3002);
|
||||
const adminPort = Number(process.env.ADMIN_PORT ?? 8082);
|
||||
const portalUrl = `http://127.0.0.1:${portalPort}`;
|
||||
const adminUrl = `http://127.0.0.1:${adminPort}`;
|
||||
const plazaPublicBase = (
|
||||
process.env.PLAZA_PUBLIC_BASE ?? `http://127.0.0.1:${plazaPort}`
|
||||
).replace(/\/$/, '');
|
||||
|
||||
function loadEnvFile(filePath) {
|
||||
if (!fs.existsSync(filePath)) return;
|
||||
for (const line of fs.readFileSync(filePath, 'utf8').split('\n')) {
|
||||
const trimmed = line.trim();
|
||||
if (!trimmed || trimmed.startsWith('#')) continue;
|
||||
const eq = trimmed.indexOf('=');
|
||||
if (eq < 0) continue;
|
||||
const key = trimmed.slice(0, eq).trim();
|
||||
const value = trimmed.slice(eq + 1).trim();
|
||||
if (!process.env[key]) process.env[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
loadEnvFile(path.join(root, '../../.env.local'));
|
||||
loadEnvFile(path.join(root, '.env'));
|
||||
|
||||
const logFile =
|
||||
process.env.MEMIND_DEV_LOG ?? path.join(os.homedir(), 'Library/Logs/memind-dev.log');
|
||||
|
||||
function log(message) {
|
||||
const line = `[${new Date().toISOString()}] ${message}\n`;
|
||||
try {
|
||||
fs.appendFileSync(logFile, line);
|
||||
} catch {
|
||||
process.stdout.write(line);
|
||||
}
|
||||
}
|
||||
|
||||
function freePort(port) {
|
||||
try {
|
||||
execSync(`lsof -ti TCP:${port} -sTCP:LISTEN | xargs kill -9`, { stdio: 'ignore' });
|
||||
} catch {
|
||||
// port already free
|
||||
}
|
||||
}
|
||||
|
||||
let fullyStarted = false;
|
||||
let stopping = false;
|
||||
const children = { server: null, admin: null, ops: null, vite: null };
|
||||
|
||||
function shutdown(code = 0) {
|
||||
if (stopping) return;
|
||||
stopping = true;
|
||||
for (const child of Object.values(children)) {
|
||||
child?.kill('SIGTERM');
|
||||
}
|
||||
setTimeout(() => process.exit(code), 300);
|
||||
}
|
||||
|
||||
process.on('SIGINT', () => shutdown(0));
|
||||
process.on('SIGTERM', () => shutdown(0));
|
||||
|
||||
function attachExitHandler(label, child, respawn) {
|
||||
child.on('exit', (code, signal) => {
|
||||
if (stopping || signal === 'SIGTERM') return;
|
||||
if (!code || code === 0) return;
|
||||
if (!fullyStarted) {
|
||||
log(`[${label}] 启动阶段退出 code=${code}`);
|
||||
shutdown(code);
|
||||
return;
|
||||
}
|
||||
log(`[${label}] 异常退出 code=${code},3 秒后重启...`);
|
||||
setTimeout(() => {
|
||||
if (stopping) return;
|
||||
respawn();
|
||||
}, 3000);
|
||||
});
|
||||
}
|
||||
|
||||
function spawnPortal() {
|
||||
const child = spawn(nodeBin, [path.join(root, 'server.mjs')], {
|
||||
cwd: root,
|
||||
env: process.env,
|
||||
stdio: 'inherit',
|
||||
});
|
||||
children.server = child;
|
||||
attachExitHandler('portal', child, spawnPortal);
|
||||
return child;
|
||||
}
|
||||
|
||||
function spawnAdmin() {
|
||||
const child = spawn(nodeBin, [path.join(root, 'admin-server.mjs')], {
|
||||
cwd: root,
|
||||
env: process.env,
|
||||
stdio: 'inherit',
|
||||
});
|
||||
children.admin = child;
|
||||
attachExitHandler('admin', child, spawnAdmin);
|
||||
return child;
|
||||
}
|
||||
|
||||
function spawnOps() {
|
||||
const child = spawn('npm', ['run', 'dev'], {
|
||||
cwd: opsDir,
|
||||
env: { ...process.env, ...opsEnv },
|
||||
stdio: 'inherit',
|
||||
});
|
||||
children.ops = child;
|
||||
attachExitHandler('ops', child, spawnOps);
|
||||
return child;
|
||||
}
|
||||
|
||||
function spawnVite() {
|
||||
const child = spawn('npx', ['vite'], {
|
||||
cwd: root,
|
||||
env: { ...process.env, ...viteEnv },
|
||||
stdio: 'inherit',
|
||||
});
|
||||
children.vite = child;
|
||||
attachExitHandler('vite', child, spawnVite);
|
||||
return child;
|
||||
}
|
||||
|
||||
async function waitFor(url, check, label, retries = 60) {
|
||||
for (let i = 0; i < retries; i += 1) {
|
||||
try {
|
||||
if (await check(url)) return;
|
||||
} catch {
|
||||
// still starting
|
||||
}
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
}
|
||||
throw new Error(`${label} 未在 ${url} 启动`);
|
||||
}
|
||||
|
||||
const mindSpacePublicBase = (
|
||||
process.env.H5_PUBLIC_BASE_URL ?? `http://127.0.0.1:${vitePort}`
|
||||
).replace(/\/$/, '');
|
||||
|
||||
const viteEnv = {
|
||||
VITE_PLAZA_BASE: plazaPublicBase,
|
||||
VITE_MINDSPACE_BASE: mindSpacePublicBase,
|
||||
};
|
||||
|
||||
const opsEnv = {
|
||||
OPS_API_PROXY: portalUrl,
|
||||
OPS_ADMIN_PROXY: adminUrl,
|
||||
VITE_PLAZA_BASE: plazaPublicBase,
|
||||
VITE_MINDSPACE_BASE: mindSpacePublicBase,
|
||||
};
|
||||
|
||||
log(`清理端口 ${portalPort} / ${adminPort} / ${vitePort} / ${opsPort}...`);
|
||||
freePort(portalPort);
|
||||
freePort(adminPort);
|
||||
freePort(vitePort);
|
||||
freePort(opsPort);
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
|
||||
log('启动 portal (server.mjs)...');
|
||||
spawnPortal();
|
||||
|
||||
try {
|
||||
await waitFor(portalUrl, async (url) => (await fetch(`${url}/auth/status`)).ok, 'Portal');
|
||||
log(`Portal 就绪: ${portalUrl}`);
|
||||
|
||||
log('启动 memind_adm (admin-server.mjs)...');
|
||||
spawnAdmin();
|
||||
await waitFor(adminUrl, async (url) => (await fetch(`${url}/healthz`)).ok, 'Admin');
|
||||
log(`memind_adm 就绪: ${adminUrl}`);
|
||||
|
||||
log(`启动 Ops 后台 @ http://127.0.0.1:${opsPort}/ops/`);
|
||||
spawnOps();
|
||||
await waitFor(
|
||||
`http://127.0.0.1:${opsPort}`,
|
||||
async (url) => (await fetch(`${url}/ops/`)).ok,
|
||||
'Ops',
|
||||
40,
|
||||
);
|
||||
log(`Ops 就绪: http://127.0.0.1:${opsPort}/ops/`);
|
||||
|
||||
log(`启动 Vite @ http://127.0.0.1:${vitePort}`);
|
||||
spawnVite();
|
||||
await waitFor(
|
||||
`http://127.0.0.1:${vitePort}`,
|
||||
async (url) => {
|
||||
const [rootRes, mainRes] = await Promise.all([
|
||||
fetch(`${url}/`),
|
||||
fetch(`${url}/src/main.tsx`),
|
||||
]);
|
||||
return rootRes.ok && mainRes.ok;
|
||||
},
|
||||
'Vite',
|
||||
);
|
||||
log(`Vite 就绪: http://127.0.0.1:${vitePort}`);
|
||||
|
||||
fullyStarted = true;
|
||||
log('');
|
||||
log('本地服务(LaunchAgent 常驻):');
|
||||
log(` MindSpace UI http://127.0.0.1:${vitePort}/?preview=mindspace`);
|
||||
log(` Ops 审核后台 http://127.0.0.1:${opsPort}/ops/`);
|
||||
log(` API / Portal ${portalUrl}`);
|
||||
log(` memind_adm ${adminUrl}`);
|
||||
log('');
|
||||
log('Plaza 未包含在此 Agent 内;需要时请单独运行 pnpm dev:plaza');
|
||||
} catch (err) {
|
||||
log(err instanceof Error ? err.message : String(err));
|
||||
shutdown(1);
|
||||
}
|
||||
Executable
+74
@@ -0,0 +1,74 @@
|
||||
#!/usr/bin/env node
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { createDbPool } from '../db.mjs';
|
||||
import { createLlmProviderService } from '../llm-providers.mjs';
|
||||
|
||||
const root = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
|
||||
|
||||
function loadEnvFile(filePath) {
|
||||
if (!fs.existsSync(filePath)) return;
|
||||
for (const line of fs.readFileSync(filePath, 'utf8').split('\n')) {
|
||||
const trimmed = line.trim();
|
||||
if (!trimmed || trimmed.startsWith('#')) continue;
|
||||
const eq = trimmed.indexOf('=');
|
||||
if (eq < 0) continue;
|
||||
const key = trimmed.slice(0, eq).trim();
|
||||
const value = trimmed.slice(eq + 1).trim();
|
||||
if (!process.env[key]) process.env[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
function shellQuote(value) {
|
||||
return `'${String(value).replace(/'/g, `'\\''`)}'`;
|
||||
}
|
||||
|
||||
function parseArgs(argv) {
|
||||
const out = {
|
||||
executor: String(argv[0] ?? '').trim(),
|
||||
purpose: 'default',
|
||||
shell: false,
|
||||
};
|
||||
for (let i = 1; i < argv.length; i += 1) {
|
||||
const item = argv[i];
|
||||
if (item === '--purpose') out.purpose = String(argv[++i] ?? out.purpose);
|
||||
else if (item === '--shell') out.shell = true;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
loadEnvFile(path.join(root, '.env'));
|
||||
loadEnvFile(path.join(root, '.env.local'));
|
||||
|
||||
const parsed = parseArgs(process.argv.slice(2));
|
||||
if (!parsed.executor) {
|
||||
console.error('Usage: node scripts/executor-env.mjs <executor> [--purpose default] [--shell]');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const pool = createDbPool();
|
||||
try {
|
||||
const svc = createLlmProviderService(pool, {
|
||||
apiTarget: process.env.TKMIND_API_TARGET ?? 'https://127.0.0.1:18006',
|
||||
apiSecret: process.env.TKMIND_SERVER__SECRET_KEY ?? 'local-dev-secret',
|
||||
});
|
||||
const runtime = await svc.getExecutorRuntimeConfig(parsed.executor, {
|
||||
purpose: parsed.purpose,
|
||||
includeSecret: true,
|
||||
});
|
||||
if (!runtime?.ok) {
|
||||
console.error(runtime?.message ?? 'executor runtime unavailable');
|
||||
process.exit(2);
|
||||
}
|
||||
const env = runtime.env ?? {};
|
||||
if (parsed.shell) {
|
||||
for (const [key, value] of Object.entries(env)) {
|
||||
process.stdout.write(`export ${key}=${shellQuote(value)}\n`);
|
||||
}
|
||||
} else {
|
||||
process.stdout.write(`${JSON.stringify({ executor: parsed.executor, env })}\n`);
|
||||
}
|
||||
} finally {
|
||||
await pool.end().catch(() => {});
|
||||
}
|
||||
Executable
+159
@@ -0,0 +1,159 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
ENV_FILE="${ENV_FILE:-${ROOT}/.env}"
|
||||
LABEL="${LABEL:-cn.tkmind.memind-dev}"
|
||||
PLIST="${HOME}/Library/LaunchAgents/${LABEL}.plist"
|
||||
LOG_DIR="${HOME}/Library/Logs"
|
||||
LOG_FILE="${LOG_DIR}/memind-dev.log"
|
||||
DAEMON_SCRIPT="${ROOT}/scripts/dev-core-daemon.mjs"
|
||||
UID_NUM="$(id -u)"
|
||||
GUI="gui/${UID_NUM}"
|
||||
PATH_EXPORT="/opt/homebrew/bin:/opt/homebrew/opt/node@24/bin:/usr/local/bin:/usr/bin:/bin"
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
用法:
|
||||
bash scripts/install-memind-dev-launchagent.sh # 安装并启动
|
||||
bash scripts/install-memind-dev-launchagent.sh --uninstall
|
||||
|
||||
环境变量:
|
||||
ROOT 仓库根目录 (默认: ${ROOT})
|
||||
LABEL LaunchAgent 标识 (默认: ${LABEL})
|
||||
NODE_BIN node 可执行文件路径
|
||||
EOF
|
||||
}
|
||||
|
||||
uninstall() {
|
||||
if launchctl print "${GUI}/${LABEL}" >/dev/null 2>&1; then
|
||||
launchctl bootout "${GUI}/${LABEL}" 2>/dev/null || true
|
||||
sleep 1
|
||||
fi
|
||||
rm -f "${PLIST}"
|
||||
echo "已卸载 LaunchAgent: ${LABEL}"
|
||||
}
|
||||
|
||||
if [[ "${1:-}" == "--uninstall" ]]; then
|
||||
uninstall
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
|
||||
usage
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ ! -f "${ENV_FILE}" ]]; then
|
||||
echo "未找到环境文件: ${ENV_FILE}" >&2
|
||||
echo "请先 cp .env.example .env 并配置数据库等变量" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -f "${DAEMON_SCRIPT}" ]]; then
|
||||
echo "未找到 daemon 脚本: ${DAEMON_SCRIPT}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
set -a
|
||||
# shellcheck disable=SC1090
|
||||
source "${ENV_FILE}"
|
||||
set +a
|
||||
|
||||
NODE_BIN="${NODE_BIN:-/opt/homebrew/opt/node@24/bin/node}"
|
||||
if [[ ! -x "${NODE_BIN}" ]]; then
|
||||
NODE_BIN="$(command -v node || true)"
|
||||
fi
|
||||
if [[ ! -x "${NODE_BIN}" ]]; then
|
||||
echo "未找到 node,请安装 Node.js 或设置 NODE_BIN" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -d "${ROOT}/node_modules" ]]; then
|
||||
echo "未找到 node_modules,请先在 ${ROOT} 执行 pnpm install" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -d "${ROOT}/ops/node_modules" ]]; then
|
||||
echo "未找到 ops/node_modules,请先在 ${ROOT}/ops 执行 npm install" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "${HOME}/Library/LaunchAgents" "${LOG_DIR}"
|
||||
|
||||
cat >"${PLIST}" <<EOF
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<string>${LABEL}</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>${NODE_BIN}</string>
|
||||
<string>${DAEMON_SCRIPT}</string>
|
||||
</array>
|
||||
<key>WorkingDirectory</key>
|
||||
<string>${ROOT}</string>
|
||||
<key>EnvironmentVariables</key>
|
||||
<dict>
|
||||
<key>PATH</key>
|
||||
<string>${PATH_EXPORT}</string>
|
||||
<key>HOME</key>
|
||||
<string>${HOME}</string>
|
||||
</dict>
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
<key>KeepAlive</key>
|
||||
<true/>
|
||||
<key>ThrottleInterval</key>
|
||||
<integer>15</integer>
|
||||
<key>StandardOutPath</key>
|
||||
<string>${LOG_FILE}</string>
|
||||
<key>StandardErrorPath</key>
|
||||
<string>${LOG_FILE}</string>
|
||||
</dict>
|
||||
</plist>
|
||||
EOF
|
||||
|
||||
plutil -lint "${PLIST}"
|
||||
|
||||
if launchctl print "${GUI}/${LABEL}" >/dev/null 2>&1; then
|
||||
launchctl bootout "${GUI}/${LABEL}" 2>/dev/null || true
|
||||
sleep 1
|
||||
fi
|
||||
|
||||
launchctl bootstrap "${GUI}" "${PLIST}"
|
||||
launchctl enable "${GUI}/${LABEL}"
|
||||
launchctl kickstart -k "${GUI}/${LABEL}"
|
||||
|
||||
echo "等待 H5 开发栈就绪..."
|
||||
portal_port="${H5_PORT:-8081}"
|
||||
vite_port="${VITE_PORT:-5173}"
|
||||
ready=0
|
||||
for _ in $(seq 1 90); do
|
||||
if curl -sf "http://127.0.0.1:${portal_port}/auth/status" >/dev/null 2>&1 \
|
||||
&& curl -sf "http://127.0.0.1:${vite_port}/" >/dev/null 2>&1; then
|
||||
ready=1
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "=== Memind 本地开发栈 (LaunchAgent) ==="
|
||||
launchctl list | grep "${LABEL}" || true
|
||||
if [[ "${ready}" -eq 1 ]]; then
|
||||
echo "Portal: http://127.0.0.1:${portal_port}"
|
||||
echo "H5 UI: http://127.0.0.1:${vite_port}/?preview=mindspace"
|
||||
else
|
||||
echo "服务尚未就绪,请查看日志: tail -f ${LOG_FILE}"
|
||||
fi
|
||||
echo ""
|
||||
echo "LaunchAgent: ${PLIST}"
|
||||
echo "Log file: ${LOG_FILE}"
|
||||
echo ""
|
||||
echo "常用命令:"
|
||||
echo " launchctl kickstart -k ${GUI}/${LABEL} # 重启"
|
||||
echo " tail -f ${LOG_FILE} # 看日志"
|
||||
echo " bash scripts/install-memind-dev-launchagent.sh --uninstall"
|
||||
Executable
+76
@@ -0,0 +1,76 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
COMPOSE_FILE="${ROOT_DIR}/docker-compose.openhands-colima.yml"
|
||||
|
||||
export OPENHANDS_WORKSPACE="${OPENHANDS_WORKSPACE:-${ROOT_DIR}}"
|
||||
export OPENHANDS_DOCKER_SOCK="${OPENHANDS_DOCKER_SOCK:-/Users/john/.colima/default/docker.sock}"
|
||||
export OPENHANDS_CONFIG_DIR="${OPENHANDS_CONFIG_DIR:-/Users/john/.openhands}"
|
||||
export OPENHANDS_PORT="${OPENHANDS_PORT:-3000}"
|
||||
export OPENHANDS_SANDBOX_UID="${OPENHANDS_SANDBOX_UID:-$(id -u)}"
|
||||
export OPENHANDS_DB_NAME="${OPENHANDS_DB_NAME:-openhands}"
|
||||
export OPENHANDS_DB_USER="${OPENHANDS_DB_USER:-openhands}"
|
||||
export OPENHANDS_DB_PASS="${OPENHANDS_DB_PASS:-openhands_dev_password}"
|
||||
|
||||
if ! command -v docker >/dev/null 2>&1; then
|
||||
echo "docker not found in PATH" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -S "${OPENHANDS_DOCKER_SOCK}" ]; then
|
||||
echo "docker socket not found: ${OPENHANDS_DOCKER_SOCK}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "${OPENHANDS_CONFIG_DIR}"
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage: scripts/openhands-colima.sh <pull|up|down|restart|logs|status>
|
||||
|
||||
Environment:
|
||||
OPENHANDS_WORKSPACE Workspace mounted into /workspace
|
||||
OPENHANDS_DOCKER_SOCK Docker socket mounted into the service container
|
||||
OPENHANDS_CONFIG_DIR Legacy host config directory (kept for CLI wrappers)
|
||||
OPENHANDS_PORT Host port published for the GUI server
|
||||
OPENHANDS_DB_NAME Postgres database name for OpenHands
|
||||
OPENHANDS_DB_USER Postgres user for OpenHands
|
||||
OPENHANDS_DB_PASS Postgres password for OpenHands
|
||||
EOF
|
||||
}
|
||||
|
||||
cmd="${1:-status}"
|
||||
shift || true
|
||||
|
||||
compose() {
|
||||
docker compose -f "${COMPOSE_FILE}" "$@"
|
||||
}
|
||||
|
||||
case "${cmd}" in
|
||||
build)
|
||||
compose build openhands
|
||||
;;
|
||||
pull)
|
||||
compose pull postgres
|
||||
;;
|
||||
up)
|
||||
compose up -d postgres openhands
|
||||
;;
|
||||
down)
|
||||
compose down
|
||||
;;
|
||||
restart)
|
||||
compose up -d --force-recreate postgres openhands
|
||||
;;
|
||||
logs)
|
||||
compose logs -f --tail=200 postgres openhands
|
||||
;;
|
||||
status)
|
||||
compose ps
|
||||
;;
|
||||
*)
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
Executable
+22
@@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="/Users/john/Project/Memind"
|
||||
ENV_SCRIPT="${ROOT_DIR}/scripts/executor-env.mjs"
|
||||
OPENHANDS_BIN_DEFAULT="/Users/john/.local/bin/openhands"
|
||||
OPENHANDS_BIN="${OPENHANDS_CLI_BIN:-${OPENHANDS_BIN:-${OPENHANDS_BIN_DEFAULT}}}"
|
||||
NODE_BIN="${NODE_BIN:-$(command -v node || true)}"
|
||||
|
||||
if [[ -z "${NODE_BIN}" ]]; then
|
||||
echo "node not found in PATH" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -x "${OPENHANDS_BIN}" ]]; then
|
||||
echo "OpenHands CLI not executable: ${OPENHANDS_BIN}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
eval "$("${NODE_BIN}" "${ENV_SCRIPT}" openhands --shell)"
|
||||
|
||||
exec "${OPENHANDS_BIN}" "$@"
|
||||
@@ -0,0 +1,254 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
HOST="${H5_DEPLOY_HOST:-root@120.26.184.105}"
|
||||
REMOTE_ROOT="${REMOTE_ROOT:-/root/tkmind_go}"
|
||||
APP_DIR="${APP_DIR:-${REMOTE_ROOT}/ui/h5}"
|
||||
INCOMING_DIR="${INCOMING_DIR:-${REMOTE_ROOT}/incoming/memind-portal-runtime-105}"
|
||||
BACKUP_DIR="${BACKUP_DIR:-${REMOTE_ROOT}/backups}"
|
||||
ARCHIVE_DIR="${ARCHIVE_DIR:-${REMOTE_ROOT}/archives}"
|
||||
SERVICE_NAME="${SERVICE_NAME:-goose-h5}"
|
||||
HEALTH_URL="${HEALTH_URL:-http://127.0.0.1:8080/api/status}"
|
||||
RUNTIME_ROOT="${ROOT}/.runtime/portal"
|
||||
RELEASE_ID="${RELEASE_ID:-$(date +%Y%m%d-%H%M%S)}"
|
||||
TMP_DIR="$(mktemp -d "${TMPDIR:-/tmp}/memind-portal-runtime-105.XXXXXX")"
|
||||
BUNDLE_PATH="${TMP_DIR}/memind-portal-runtime-105-${RELEASE_ID}.tar.gz"
|
||||
MANIFEST_PATH="${TMP_DIR}/memind-portal-runtime-105-${RELEASE_ID}.manifest.txt"
|
||||
SHA_PATH="${TMP_DIR}/memind-portal-runtime-105-${RELEASE_ID}.sha256"
|
||||
DRY_RUN=0
|
||||
SKIP_BUILD=0
|
||||
SKIP_TESTS=0
|
||||
AUTO_YES=0
|
||||
|
||||
cleanup() {
|
||||
rm -rf "${TMP_DIR}"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage:
|
||||
bash scripts/release-portal-runtime-105.sh [--dry-run] [--skip-build] [--skip-tests] [--yes]
|
||||
|
||||
Behavior:
|
||||
1. Optionally runs targeted local tests
|
||||
2. Builds the local Portal runtime artifact
|
||||
3. Packages .runtime/portal into a tarball
|
||||
4. Uploads the tarball to 105
|
||||
5. Replaces the 105 live runtime while preserving config and persisted data
|
||||
|
||||
Preserved on 105:
|
||||
.env, MindSpace/, data/, users/, logs/, public/plaza-covers/
|
||||
EOF
|
||||
}
|
||||
|
||||
say() {
|
||||
printf '\n[%s] %s\n' "$(date +%H:%M:%S)" "$*"
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--dry-run)
|
||||
DRY_RUN=1
|
||||
;;
|
||||
--skip-build)
|
||||
SKIP_BUILD=1
|
||||
;;
|
||||
--skip-tests)
|
||||
SKIP_TESTS=1
|
||||
;;
|
||||
--yes)
|
||||
AUTO_YES=1
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "Unknown argument: $1" >&2
|
||||
usage >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
cd "${ROOT}"
|
||||
|
||||
say "检查 105 SSH"
|
||||
ssh -o BatchMode=yes -o ConnectTimeout=15 "${HOST}" "echo portal-runtime-105-ssh-ok" >/dev/null
|
||||
|
||||
if [[ "${SKIP_TESTS}" -ne 1 ]]; then
|
||||
say "运行微信相关本地测试"
|
||||
node --test wechat-mp.test.mjs wechat-oauth.test.mjs wechat-pay.test.mjs user-auth.test.mjs capabilities.test.mjs llm-providers.test.mjs
|
||||
fi
|
||||
|
||||
if [[ "${SKIP_BUILD}" -ne 1 ]]; then
|
||||
say "构建 Portal runtime"
|
||||
PORTAL_RUNTIME_NODE_TARGET=node22 PORTAL_RUNTIME_INSTALL_MODE=remote-npm-install \
|
||||
node scripts/build-portal-runtime.mjs --skip-node-modules
|
||||
fi
|
||||
|
||||
verify_runtime_artifact() {
|
||||
local missing=0
|
||||
for required in server.mjs mindspace-sandbox-mcp.mjs dist package.json scripts/run-memind-portal-prod.sh scripts/load-env.mjs scripts/wechat-mp-menu.mjs; do
|
||||
if [[ ! -e "${RUNTIME_ROOT}/${required}" ]]; then
|
||||
echo "runtime 产物缺失: ${RUNTIME_ROOT}/${required}" >&2
|
||||
missing=1
|
||||
fi
|
||||
done
|
||||
if [[ "${missing}" -ne 0 ]]; then
|
||||
echo "请先执行: node scripts/build-portal-runtime.mjs" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
verify_runtime_artifact
|
||||
|
||||
if [[ "${AUTO_YES}" -ne 1 && "${DRY_RUN}" -ne 1 ]]; then
|
||||
say "发布确认"
|
||||
echo "目标主机: ${HOST}"
|
||||
echo "目标目录: ${APP_DIR}"
|
||||
echo "systemd 服务: ${SERVICE_NAME}"
|
||||
echo "发布编号: ${RELEASE_ID}"
|
||||
echo "本地 HEAD: $(git -C "${ROOT}" rev-parse HEAD 2>/dev/null || echo unknown)"
|
||||
read -r -p "确认继续发布到 105? [y/N] " confirm </dev/tty
|
||||
[[ "${confirm}" =~ ^[Yy]$ ]] || exit 0
|
||||
fi
|
||||
|
||||
say "生成发布清单"
|
||||
{
|
||||
echo "release_id=${RELEASE_ID}"
|
||||
echo "created_at=$(date '+%Y-%m-%d %H:%M:%S %z')"
|
||||
echo "host=$(hostname)"
|
||||
echo "git_head=$(git -C "${ROOT}" rev-parse HEAD 2>/dev/null || echo unknown)"
|
||||
echo "git_branch=$(git -C "${ROOT}" branch --show-current 2>/dev/null || echo detached)"
|
||||
echo "artifact=.runtime/portal"
|
||||
echo "target=105"
|
||||
echo "persisted_items=.env, MindSpace, data, users, logs, public/plaza-covers"
|
||||
} > "${MANIFEST_PATH}"
|
||||
|
||||
say "打包 Portal runtime artifact"
|
||||
tar -czf "${BUNDLE_PATH}" -C "${RUNTIME_ROOT}" .
|
||||
checksum="$(shasum -a 256 "${BUNDLE_PATH}" | awk '{print $1}')"
|
||||
printf '%s %s\n' "${checksum}" "$(basename "${BUNDLE_PATH}")" > "${SHA_PATH}"
|
||||
|
||||
if [[ "${DRY_RUN}" -eq 1 ]]; then
|
||||
say "dry-run 完成"
|
||||
ls -lh "${BUNDLE_PATH}" "${MANIFEST_PATH}" "${SHA_PATH}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
say "上传 Portal runtime 到 105"
|
||||
ssh -o BatchMode=yes "${HOST}" "mkdir -p '${INCOMING_DIR}' '${BACKUP_DIR}' '${ARCHIVE_DIR}'"
|
||||
scp -q "${BUNDLE_PATH}" "${MANIFEST_PATH}" "${SHA_PATH}" "${HOST}:${INCOMING_DIR}/"
|
||||
|
||||
say "在 105 切换到新的 runtime"
|
||||
ssh -o BatchMode=yes "${HOST}" \
|
||||
"RELEASE_ID='${RELEASE_ID}' APP_DIR='${APP_DIR}' INCOMING_DIR='${INCOMING_DIR}' BACKUP_DIR='${BACKUP_DIR}' ARCHIVE_DIR='${ARCHIVE_DIR}' HEALTH_URL='${HEALTH_URL}' SERVICE_NAME='${SERVICE_NAME}' /bin/bash" <<'REMOTE_SCRIPT'
|
||||
set -euo pipefail
|
||||
|
||||
RUNTIME_DIR="${APP_DIR}.runtime-${RELEASE_ID}"
|
||||
OLD_LIVE_DIR="${ARCHIVE_DIR}/h5-before-${RELEASE_ID}"
|
||||
BUNDLE="${INCOMING_DIR}/memind-portal-runtime-105-${RELEASE_ID}.tar.gz"
|
||||
MANIFEST="${INCOMING_DIR}/memind-portal-runtime-105-${RELEASE_ID}.manifest.txt"
|
||||
SHA_FILE="${INCOMING_DIR}/memind-portal-runtime-105-${RELEASE_ID}.sha256"
|
||||
FULL_BACKUP_TAR="${BACKUP_DIR}/h5-full-${RELEASE_ID}-before.tar.gz"
|
||||
PERSIST_BACKUP_TAR="${BACKUP_DIR}/h5-persisted-${RELEASE_ID}-before.tar.gz"
|
||||
PERSISTED_ITEMS=(
|
||||
".env"
|
||||
"MindSpace"
|
||||
"data"
|
||||
"users"
|
||||
"logs"
|
||||
"public/plaza-covers"
|
||||
)
|
||||
|
||||
say() {
|
||||
printf '\n[remote %s] %s\n' "$(date +%H:%M:%S)" "$*"
|
||||
}
|
||||
|
||||
rollback() {
|
||||
if [[ -d "${OLD_LIVE_DIR}" && ! -d "${APP_DIR}" ]]; then
|
||||
mv "${OLD_LIVE_DIR}" "${APP_DIR}"
|
||||
fi
|
||||
systemctl restart "${SERVICE_NAME}" >/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
trap 'rollback' ERR
|
||||
|
||||
[[ -f "${BUNDLE}" ]] || { echo "missing bundle: ${BUNDLE}" >&2; exit 1; }
|
||||
[[ -f "${MANIFEST}" ]] || { echo "missing manifest: ${MANIFEST}" >&2; exit 1; }
|
||||
[[ -f "${SHA_FILE}" ]] || { echo "missing sha file: ${SHA_FILE}" >&2; exit 1; }
|
||||
[[ -f "${APP_DIR}/.env" ]] || { echo "missing required config: ${APP_DIR}/.env" >&2; exit 1; }
|
||||
|
||||
say "校验产物完整性"
|
||||
cd "${INCOMING_DIR}"
|
||||
shasum -a 256 -c "$(basename "${SHA_FILE}")"
|
||||
|
||||
say "备份当前 live 全目录"
|
||||
COPYFILE_DISABLE=1 tar -czf "${FULL_BACKUP_TAR}" -C "$(dirname "${APP_DIR}")" "$(basename "${APP_DIR}")"
|
||||
|
||||
say "单独备份持久目录"
|
||||
tmp_persist_dir="$(mktemp -d "${TMPDIR:-/tmp}/memind-105-persisted.XXXXXX")"
|
||||
for item in "${PERSISTED_ITEMS[@]}"; do
|
||||
if [[ -e "${APP_DIR}/${item}" ]]; then
|
||||
mkdir -p "${tmp_persist_dir}/$(dirname "${item}")"
|
||||
cp -a "${APP_DIR}/${item}" "${tmp_persist_dir}/$(dirname "${item}")/"
|
||||
fi
|
||||
done
|
||||
COPYFILE_DISABLE=1 tar -czf "${PERSIST_BACKUP_TAR}" -C "${tmp_persist_dir}" .
|
||||
rm -rf "${tmp_persist_dir}"
|
||||
|
||||
say "停止旧 Portal 服务"
|
||||
systemctl stop "${SERVICE_NAME}"
|
||||
lsof -tiTCP:8080 -sTCP:LISTEN 2>/dev/null | xargs kill 2>/dev/null || true
|
||||
sleep 2
|
||||
|
||||
say "准备新的 runtime live 目录"
|
||||
rm -rf "${RUNTIME_DIR}"
|
||||
mkdir -p "${RUNTIME_DIR}"
|
||||
tar -xzf "${BUNDLE}" -C "${RUNTIME_DIR}"
|
||||
cp "${MANIFEST}" "${RUNTIME_DIR}/.release-manifest.txt"
|
||||
|
||||
for item in "${PERSISTED_ITEMS[@]}"; do
|
||||
if [[ -e "${APP_DIR}/${item}" ]]; then
|
||||
rm -rf "${RUNTIME_DIR:?}/${item}"
|
||||
mkdir -p "$(dirname "${RUNTIME_DIR}/${item}")"
|
||||
cp -a "${APP_DIR}/${item}" "$(dirname "${RUNTIME_DIR}/${item}")/"
|
||||
fi
|
||||
done
|
||||
|
||||
say "在 105 安装 Linux 运行依赖"
|
||||
cd "${RUNTIME_DIR}"
|
||||
rm -rf node_modules package-lock.json
|
||||
npm install --omit=dev --no-package-lock
|
||||
|
||||
say "归档旧 live 并切换新 runtime"
|
||||
rm -rf "${OLD_LIVE_DIR}"
|
||||
mv "${APP_DIR}" "${OLD_LIVE_DIR}"
|
||||
mv "${RUNTIME_DIR}" "${APP_DIR}"
|
||||
|
||||
say "启动新的 Portal runtime"
|
||||
systemctl restart "${SERVICE_NAME}"
|
||||
|
||||
say "健康检查"
|
||||
for _ in $(seq 1 60); do
|
||||
portal_code="$(curl -s -o /dev/null -w '%{http_code}' "${HEALTH_URL}" || true)"
|
||||
if [[ "${portal_code}" == "200" ]]; then
|
||||
break
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
|
||||
portal_code="$(curl -s -o /dev/null -w '%{http_code}' "${HEALTH_URL}" || true)"
|
||||
if [[ "${portal_code}" != "200" ]]; then
|
||||
echo "health check failed: portal=${portal_code}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
say "runtime 切换完成"
|
||||
REMOTE_SCRIPT
|
||||
|
||||
say "105 runtime 发布完成"
|
||||
@@ -191,37 +191,22 @@ if [[ -x "${docker_bin}" ]] && "${docker_bin}" ps --format '{{.Names}}' 2>/dev/n
|
||||
fi
|
||||
fi
|
||||
else
|
||||
api_secret="$(grep -E '^TKMIND_SERVER__SECRET_KEY=' /Users/john/Project/Memind/.env 2>/dev/null | tail -1 | cut -d= -f2- || true)"
|
||||
for port in 18006 18007; do
|
||||
line="$(lsof -nP -iTCP:${port} -sTCP:LISTEN 2>/dev/null | awk 'NR==2 {print $1, $2, $9}')"
|
||||
if [[ -z "${line}" ]]; then
|
||||
echo "goosed dependency check failed: port ${port} has no listener" >&2
|
||||
status="$(curl -skS -m 5 -H "X-Secret-Key: ${api_secret}" "https://127.0.0.1:${port}/status" 2>/dev/null || true)"
|
||||
if [[ "${status}" != "ok" ]]; then
|
||||
line="$(lsof -nP -iTCP:${port} -sTCP:LISTEN 2>/dev/null | awk 'NR==2 {print $1, $2, $9}')"
|
||||
echo "goosed dependency check failed: port ${port} status check failed (${line:-no listener})" >&2
|
||||
missing=1
|
||||
continue
|
||||
fi
|
||||
command_name="${line%% *}"
|
||||
case "${command_name}" in
|
||||
goosed) ;;
|
||||
*)
|
||||
echo "goosed dependency check failed: port ${port} is listened by ${line}, not goosed" >&2
|
||||
missing=1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if ! launchctl list 2>/dev/null | grep -q 'cn.tkmind.goosed-18006'; then
|
||||
echo "goosed dependency check failed: launchd service cn.tkmind.goosed-18006 is not loaded" >&2
|
||||
missing=1
|
||||
fi
|
||||
if ! launchctl list 2>/dev/null | grep -q 'cn.tkmind.goosed-18007'; then
|
||||
echo "goosed dependency check failed: launchd service cn.tkmind.goosed-18007 is not loaded" >&2
|
||||
missing=1
|
||||
fi
|
||||
if [[ ! -x /Users/john/Project/tkmind_go/scripts/run-goosed-local.sh ]]; then
|
||||
echo "goosed dependency check failed: missing /Users/john/Project/tkmind_go/scripts/run-goosed-local.sh" >&2
|
||||
missing=1
|
||||
fi
|
||||
if [[ ! -x /Users/john/Project/tkmind_go/target/release/goosed && ! -x /Users/john/Project/tkmind_go/target/debug/goosed ]]; then
|
||||
echo "goosed dependency check failed: missing goosed binary under /Users/john/Project/tkmind_go/target" >&2
|
||||
portal_mcp_node="$(grep -E '^GOOSED_MCP_NODE_PATH=' /Users/john/Project/Memind/.env 2>/dev/null | tail -1 | cut -d= -f2- || true)"
|
||||
portal_mcp_server="$(grep -E '^GOOSED_MCP_SERVER_PATH=' /Users/john/Project/Memind/.env 2>/dev/null | tail -1 | cut -d= -f2- || true)"
|
||||
portal_mcp_node="${portal_mcp_node:-/opt/homebrew/opt/node@24/bin/node}"
|
||||
portal_mcp_server="${portal_mcp_server:-/Users/john/Project/Memind/mindspace-sandbox-mcp.mjs}"
|
||||
if [[ ! -x "${portal_mcp_node}" || ! -f "${portal_mcp_server}" ]]; then
|
||||
echo "goosed dependency check failed: Portal MCP paths are not valid: ${portal_mcp_node}, ${portal_mcp_server}" >&2
|
||||
missing=1
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -14,6 +14,7 @@ fi
|
||||
|
||||
export NODE_ENV=production
|
||||
export H5_PORT="${H5_PORT:-8081}"
|
||||
export H5_HOST="${H5_HOST:-127.0.0.1}"
|
||||
export H5_PUBLIC_BASE_URL="https://m.tkmind.cn"
|
||||
export TKMIND_API_TARGETS="${TKMIND_API_TARGETS_OVERRIDE:-${TKMIND_API_TARGETS:-https://127.0.0.1:18006,https://127.0.0.1:18007,https://127.0.0.1:18008,https://127.0.0.1:18009}}"
|
||||
export TKMIND_API_TARGET="${TKMIND_API_TARGET_OVERRIDE:-https://127.0.0.1:18006}"
|
||||
@@ -24,4 +25,7 @@ if [[ ! -x "${NODE_BIN}" ]]; then
|
||||
NODE_BIN="$(command -v node)"
|
||||
fi
|
||||
|
||||
export GOOSED_MCP_NODE_PATH="${GOOSED_MCP_NODE_PATH:-${NODE_BIN}}"
|
||||
export GOOSED_MCP_SERVER_PATH="${GOOSED_MCP_SERVER_PATH:-${ROOT}/mindspace-sandbox-mcp.mjs}"
|
||||
|
||||
exec "${NODE_BIN}" server.mjs
|
||||
|
||||
Reference in New Issue
Block a user