fix(release): restart m.tkmind.cn portal tunnel after Portal deploy
Bundle memind-portal-tunnel.sh into the runtime artifact and kickstart cn.tkmind.memind-portal-tunnel on 103 after Portal health checks so 105 nginx can reach 127.0.0.1:19081 following runtime releases. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -181,8 +181,18 @@ async function writeMetadata() {
|
||||
type: 'module',
|
||||
};
|
||||
await writeFile(path.join(runtimeRoot, 'package.json'), `${JSON.stringify(runtimePackageJson, null, 2)}\n`);
|
||||
const tunnelScript = path.join(root, 'scripts', 'memind-portal-tunnel.sh');
|
||||
const runtimeScriptsDir = path.join(runtimeRoot, 'scripts');
|
||||
await fs.mkdir(runtimeScriptsDir, { recursive: true });
|
||||
if (await exists(tunnelScript)) {
|
||||
await fs.copyFile(tunnelScript, path.join(runtimeScriptsDir, 'memind-portal-tunnel.sh'));
|
||||
await fs.chmod(path.join(runtimeScriptsDir, 'memind-portal-tunnel.sh'), 0o755);
|
||||
} else {
|
||||
throw new Error(`缺少隧道脚本: ${tunnelScript}`);
|
||||
}
|
||||
|
||||
await writeFile(
|
||||
path.join(runtimeRoot, 'scripts', 'run-memind-portal-prod.sh'),
|
||||
path.join(runtimeScriptsDir, 'run-memind-portal-prod.sh'),
|
||||
[
|
||||
'#!/usr/bin/env bash',
|
||||
'set -euo pipefail',
|
||||
@@ -242,6 +252,8 @@ async function writeMetadata() {
|
||||
'Deployment and operations transport:',
|
||||
' 105 fixed IP: 120.26.184.105',
|
||||
' 103 / Studio fixed IP: 58.38.22.103',
|
||||
' m.tkmind.cn: 105 nginx -> 127.0.0.1:19081 -> reverse SSH tunnel -> Portal :8081',
|
||||
' scripts/memind-portal-tunnel.sh must stay in runtime; release restarts cn.tkmind.memind-portal-tunnel',
|
||||
' Do not switch back to 10.10.* LAN paths unless explicitly required.',
|
||||
'',
|
||||
].join('\n'),
|
||||
@@ -256,6 +268,7 @@ async function main() {
|
||||
await copyNodeModules();
|
||||
await writeMetadata();
|
||||
await fs.chmod(path.join(runtimeRoot, 'scripts', 'run-memind-portal-prod.sh'), 0o755);
|
||||
await fs.chmod(path.join(runtimeRoot, 'scripts', 'memind-portal-tunnel.sh'), 0o755);
|
||||
console.log('');
|
||||
console.log(`Portal runtime 已生成: ${runtimeRoot}`);
|
||||
}
|
||||
|
||||
@@ -10,6 +10,9 @@ BACKUP_DIR="${REMOTE_ROOT}/backups/memind"
|
||||
ARCHIVE_DIR="${REMOTE_ROOT}/archives"
|
||||
HEALTH_URL="${STUDIO_HEALTH_URL:-http://127.0.0.1:8081/api/status}"
|
||||
PORTAL_LABEL="cn.tkmind.memind-portal"
|
||||
PORTAL_TUNNEL_LABEL="cn.tkmind.memind-portal-tunnel"
|
||||
MEMIND_PORTAL_TUNNEL_HOST="${MEMIND_PORTAL_TUNNEL_HOST:-ssh105-public}"
|
||||
MEMIND_PORTAL_TUNNEL_REMOTE_PORT="${MEMIND_PORTAL_TUNNEL_REMOTE_PORT:-19081}"
|
||||
LAUNCHD_GUI="gui/$(id -u)"
|
||||
RELEASE_TS="$(date +%Y%m%d-%H%M%S)"
|
||||
SHORT_SHA="$(git -C "${ROOT}" rev-parse --short HEAD 2>/dev/null || echo no-git)"
|
||||
@@ -41,7 +44,8 @@ usage() {
|
||||
4. 停止旧 Portal 服务
|
||||
5. 用 runtime artifact 替换 live 目录,只继承持久目录
|
||||
6. 启动 Portal,保持旧端口 8081
|
||||
7. 健康检查通过后,旧源码目录移入 archive,不再保留可运行 live 源码
|
||||
7. 健康检查通过后重启 m.tkmind.cn 反向隧道 (105:19081 -> 103:8081)
|
||||
8. 旧源码目录移入 archive,不再保留可运行 live 源码
|
||||
EOF
|
||||
}
|
||||
|
||||
@@ -139,7 +143,7 @@ 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; do
|
||||
for required in server.mjs mindspace-sandbox-mcp.mjs dist package.json scripts/run-memind-portal-prod.sh scripts/memind-portal-tunnel.sh; do
|
||||
if [[ ! -e "${RUNTIME_ROOT}/${required}" ]]; then
|
||||
echo "runtime 产物缺失: ${RUNTIME_ROOT}/${required}" >&2
|
||||
missing=1
|
||||
@@ -273,7 +277,7 @@ scp -q "${BUNDLE_PATH}" "${MANIFEST_PATH}" "${SHA_PATH}" "${HOST}:${INCOMING_DIR
|
||||
|
||||
say "在 103 停旧服务并切换到无源码 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}' PORTAL_LABEL='${PORTAL_LABEL}' LAUNCHD_GUI='${LAUNCHD_GUI}' /bin/bash" <<'REMOTE_SCRIPT'
|
||||
"RELEASE_ID='${RELEASE_ID}' APP_DIR='${APP_DIR}' INCOMING_DIR='${INCOMING_DIR}' BACKUP_DIR='${BACKUP_DIR}' ARCHIVE_DIR='${ARCHIVE_DIR}' HEALTH_URL='${HEALTH_URL}' PORTAL_LABEL='${PORTAL_LABEL}' PORTAL_TUNNEL_LABEL='${PORTAL_TUNNEL_LABEL}' MEMIND_PORTAL_TUNNEL_HOST='${MEMIND_PORTAL_TUNNEL_HOST}' MEMIND_PORTAL_TUNNEL_REMOTE_PORT='${MEMIND_PORTAL_TUNNEL_REMOTE_PORT}' LAUNCHD_GUI='${LAUNCHD_GUI}' /bin/bash" <<'REMOTE_SCRIPT'
|
||||
set -euo pipefail
|
||||
|
||||
RUNTIME_DIR="${APP_DIR}.runtime-${RELEASE_ID}"
|
||||
@@ -297,6 +301,67 @@ say() {
|
||||
printf '\n[remote %s] %s\n' "$(date +%H:%M:%S)" "$*"
|
||||
}
|
||||
|
||||
ensure_portal_tunnel() {
|
||||
local tunnel_script="${APP_DIR}/scripts/memind-portal-tunnel.sh"
|
||||
local tunnel_label="${PORTAL_TUNNEL_LABEL:-cn.tkmind.memind-portal-tunnel}"
|
||||
local tunnel_host="${MEMIND_PORTAL_TUNNEL_HOST:-ssh105-public}"
|
||||
local remote_port="${MEMIND_PORTAL_TUNNEL_REMOTE_PORT:-19081}"
|
||||
|
||||
[[ -x "${tunnel_script}" ]] || {
|
||||
echo "missing tunnel script: ${tunnel_script}" >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
cat > "${HOME}/Library/LaunchAgents/${tunnel_label}.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>${tunnel_label}</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>${tunnel_script}</string>
|
||||
</array>
|
||||
<key>EnvironmentVariables</key>
|
||||
<dict>
|
||||
<key>MEMIND_PORTAL_TUNNEL_HOST</key>
|
||||
<string>${tunnel_host}</string>
|
||||
</dict>
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
<key>KeepAlive</key>
|
||||
<true/>
|
||||
<key>StandardOutPath</key>
|
||||
<string>${HOME}/Library/Logs/memind-portal-tunnel.log</string>
|
||||
<key>StandardErrorPath</key>
|
||||
<string>${HOME}/Library/Logs/memind-portal-tunnel.log</string>
|
||||
</dict>
|
||||
</plist>
|
||||
EOF
|
||||
|
||||
launchctl bootout "${LAUNCHD_GUI}/${tunnel_label}" >/dev/null 2>&1 || true
|
||||
launchctl bootstrap "${LAUNCHD_GUI}" "${HOME}/Library/LaunchAgents/${tunnel_label}.plist" >/dev/null 2>&1 || true
|
||||
launchctl enable "${LAUNCHD_GUI}/${tunnel_label}" >/dev/null 2>&1 || true
|
||||
launchctl kickstart -k "${LAUNCHD_GUI}/${tunnel_label}" >/dev/null 2>&1 || true
|
||||
sleep 2
|
||||
|
||||
if ! launchctl print "${LAUNCHD_GUI}/${tunnel_label}" 2>/dev/null | grep -q 'state = running'; then
|
||||
echo "portal tunnel failed to start (${tunnel_label})" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
if command -v ssh >/dev/null 2>&1; then
|
||||
tunnel_code="$(ssh -o BatchMode=yes -o ConnectTimeout=10 "${tunnel_host}" \
|
||||
"curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:${remote_port}/api/status" 2>/dev/null || true)"
|
||||
if [[ "${tunnel_code}" == "200" ]]; then
|
||||
say "105 隧道端口 ${remote_port} 健康"
|
||||
else
|
||||
echo "warn: 105:${remote_port} 健康检查未通过 (code=${tunnel_code:-000});请确认 105 nginx upstream 指向 127.0.0.1:${remote_port}" >&2
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
rollback() {
|
||||
if [[ -d "${OLD_LIVE_DIR}" && ! -d "${APP_DIR}" ]]; then
|
||||
mv "${OLD_LIVE_DIR}" "${APP_DIR}"
|
||||
@@ -306,6 +371,7 @@ rollback() {
|
||||
if ! curl -sf "${HEALTH_URL}" >/dev/null 2>&1; then
|
||||
nohup "${APP_DIR}/scripts/run-memind-portal-prod.sh" >> "${HOME}/Library/Logs/memind-portal.log" 2>&1 &
|
||||
fi
|
||||
ensure_portal_tunnel >/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
trap 'rollback' ERR
|
||||
@@ -435,6 +501,9 @@ if [[ "${portal_code}" != "200" ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
say "重启 m.tkmind.cn 反向隧道"
|
||||
ensure_portal_tunnel
|
||||
|
||||
say "检查 live 目录中不再保留源码树"
|
||||
allowed_live_mjs=(
|
||||
"${APP_DIR}/server.mjs"
|
||||
|
||||
Reference in New Issue
Block a user