This commit is contained in:
2026-05-10 13:18:46 +08:00
parent dada5a4797
commit 1c16152708
17 changed files with 1197 additions and 99 deletions

View File

@@ -281,6 +281,100 @@ Get-CimInstance Win32_Process |
fi
}
is_tcp_port_available() {
local host="$1"
local port="$2"
# 中文注释:用真实 bind 探测端口,覆盖 127.0.0.1 与 0.0.0.0 互相占用的情况。
node - "${host}" "${port}" <<'NODE'
const net = require('net');
const host = process.argv[2];
const port = Number(process.argv[3]);
if (!Number.isInteger(port) || port <= 0 || port > 65535) {
process.exit(1);
}
const server = net.createServer();
const timer = setTimeout(() => {
server.close();
process.exit(1);
}, 1000);
server.once('error', () => {
clearTimeout(timer);
process.exit(1);
});
server.once('listening', () => {
clearTimeout(timer);
server.close(() => process.exit(0));
});
server.listen({ host, port });
NODE
}
describe_tcp_port_owner() {
local port="$1"
# 中文注释Windows 下直接读取监听端口对应进程,便于用户精确停止旧 dev 栈。
if command -v powershell.exe >/dev/null 2>&1; then
GENARRATIVE_TCP_PORT="${port}" powershell.exe -NoProfile -Command '
$portNumber = [int]$env:GENARRATIVE_TCP_PORT
$connections = Get-NetTCPConnection -State Listen -LocalPort $portNumber -ErrorAction SilentlyContinue
$seen = @{}
foreach ($connection in $connections) {
$processId = [int]$connection.OwningProcess
if ($seen.ContainsKey($processId)) {
continue
}
$seen[$processId] = $true
$process = Get-CimInstance Win32_Process -Filter "ProcessId = $processId" -ErrorAction SilentlyContinue
if ($process) {
$commandLine = ($process.CommandLine -replace "\s+", " ").Trim()
"pid=$processId name=$($process.Name) address=$($connection.LocalAddress):$($connection.LocalPort) command=$commandLine"
} else {
"pid=$processId address=$($connection.LocalAddress):$($connection.LocalPort)"
}
}
' 2>/dev/null || true
return
fi
if command -v lsof >/dev/null 2>&1; then
lsof -nP -iTCP:"${port}" -sTCP:LISTEN 2>/dev/null |
awk 'NR > 1 { print "pid=" $2 " name=" $1 " address=" $9 }' || true
return
fi
if command -v ss >/dev/null 2>&1; then
ss -ltnp "sport = :${port}" 2>/dev/null || true
fi
}
ensure_tcp_port_available() {
local label="$1"
local host="$2"
local port="$3"
local owners
# 中文注释:完整栈不复用旧 api-server / Vite避免健康检查命中旧进程后继续误判。
if is_tcp_port_available "${host}" "${port}"; then
return
fi
owners="$(describe_tcp_port_owner "${port}")"
echo "[dev:rust] ${label} 端口已被占用,无法启动: ${host}:${port}" >&2
if [[ -n "${owners}" ]]; then
echo "[dev:rust] 当前监听进程:" >&2
echo "${owners}" >&2
fi
echo "[dev:rust] 请停止占用进程,或通过对应端口参数换端口后重试。" >&2
exit 1
}
wait_for_api_server() {
local health_url="$1"
local timeout_seconds="$2"
@@ -545,6 +639,10 @@ echo "[dev:rust] spacetime root: ${SPACETIME_ROOT_DIR}"
echo "[dev:rust] spacetime data: ${SPACETIME_DATA_DIR}"
echo "[dev:rust] api timeout: ${API_SERVER_TIMEOUT_SECONDS}s"
ensure_tcp_port_available "Rust api-server" "${API_HOST}" "${API_PORT}"
ensure_tcp_port_available "主站 Vite" "${WEB_HOST}" "${WEB_PORT}"
ensure_tcp_port_available "后台 Vite" "${ADMIN_WEB_HOST}" "${ADMIN_WEB_PORT}"
if [[ "${SKIP_SPACETIME}" -ne 1 ]]; then
mkdir -p "${SPACETIME_ROOT_DIR}" "${SPACETIME_DATA_DIR}"
SPACETIME_ROOT_OWNER="$(describe_spacetime_root_owner "${SPACETIME_DATA_DIR}")"
@@ -633,7 +731,7 @@ echo "[dev:rust] 启动 vite"
ADMIN_WEB_TARGET="http://${ADMIN_WEB_TARGET_HOST}:${ADMIN_WEB_PORT}" \
ADMIN_WEB_PORT="${ADMIN_WEB_PORT}" \
VITE_DEV_HOST="${WEB_HOST}" \
exec node "${VITE_CLI_PATH}" "--port=${WEB_PORT}" "--host=${WEB_HOST}"
exec node "${VITE_CLI_PATH}" "--port=${WEB_PORT}" "--host=${WEB_HOST}" "--strictPort"
) &
PIDS+=("$!")
NAMES+=("vite")
@@ -644,7 +742,7 @@ echo "[dev:rust] 启动 admin vite"
ADMIN_API_TARGET="${RUST_SERVER_TARGET}" \
GENARRATIVE_API_TARGET="${RUST_SERVER_TARGET}" \
GENARRATIVE_API_PORT="${API_PORT}" \
exec node "${VITE_CLI_PATH}" "--host=${ADMIN_WEB_HOST}" "--port=${ADMIN_WEB_PORT}"
exec node "${VITE_CLI_PATH}" "--host=${ADMIN_WEB_HOST}" "--port=${ADMIN_WEB_PORT}" "--strictPort"
) &
PIDS+=("$!")
NAMES+=("admin-vite")