diff --git a/server/src/utils/ptyControlChannel.ts b/server/src/utils/ptyControlChannel.ts index e50429b..7bb2ad5 100644 --- a/server/src/utils/ptyControlChannel.ts +++ b/server/src/utils/ptyControlChannel.ts @@ -16,7 +16,6 @@ import { unlink } from 'node:fs/promises' import net from 'node:net' -import os from 'node:os' import path from 'node:path' export interface CreatePtyControlChannelOptions { @@ -1230,28 +1229,16 @@ class WindowsPtyControlTransport implements PtyControlTransport { } } -function getDefaultControlDirectoryCandidates(): string[] { - const baseDir = process.cwd() - const effectiveUserId = typeof process.geteuid === 'function' - ? String(process.geteuid()) - : 'unknown' - const candidates = [ - path.join(baseDir, 'data', 'terminal-control'), - path.join(baseDir, 'server', 'data', 'terminal-control'), - path.join(baseDir, '..', 'server', 'data', 'terminal-control'), - path.join(os.homedir(), '.gsm3', 'terminal-control'), - path.join(os.tmpdir(), `gsm3-terminal-control-${effectiveUserId}`) - ] - - return [...new Set(candidates.map((candidate) => path.resolve(candidate)))] -} - async function selectPosixControlDirectory( options: CreatePtyControlChannelOptions, platform: NodeJS.Platform, securityFlags: PosixSecurityFlags ): Promise { - const directoryCandidates = options.directoryCandidates ?? getDefaultControlDirectoryCandidates() + const candidates = [ + path.join(process.cwd(), 'data', 'terminal-control'), + path.join(process.cwd(), 'server', 'data', 'terminal-control') + ] + const directoryCandidates = options.directoryCandidates ?? candidates for (const rawCandidate of directoryCandidates) { let descriptor: number | null = null @@ -1317,9 +1304,7 @@ async function selectPosixControlDirectory( throw new PtyControlStageError( 'directory', - `PTY control directory unavailable platform=${platform} sessionId=${options.sessionId} stage=directory; ` + - 'checked paths under data/, ~/.gsm3/, and $TMPDIR. ' + - 'If data/terminal-control was created by another user (e.g. root), remove it or chown it to the panel runtime user.' + `PTY control directory unavailable platform=${platform} sessionId=${options.sessionId} stage=directory` ) } diff --git a/server/src/utils/ptyManager.ts b/server/src/utils/ptyManager.ts index e97275d..f078195 100644 --- a/server/src/utils/ptyManager.ts +++ b/server/src/utils/ptyManager.ts @@ -21,29 +21,19 @@ class PtyManager { /** * 获取 lib 目录的候选路径列表。 - * 顺序兼容打包、开发、Docker 内置资产和旧版 Docker 布局。 + * 顺序兼容打包后环境和开发环境,不得改变。 */ private getLibDirCandidates(): string[] { - const baseDir = process.cwd() - return Array.from(new Set([ - ...this.getWritableLibDirCandidates(), - path.join(baseDir, 'builtin', 'data', 'lib'), // 当前 Docker 镜像内置资产目录 - path.join(baseDir, '..', 'data', 'lib') // 兼容旧版 Docker/启动脚本布局 - ])) - } - - /** 获取允许服务端下载或替换资产的运行时目录。 */ - private getWritableLibDirCandidates(): string[] { - const baseDir = process.cwd() - return [ - path.join(baseDir, 'data', 'lib'), - path.join(baseDir, 'server', 'data', 'lib') + const candidates = [ + path.join(process.cwd(), 'data', 'lib'), + path.join(process.cwd(), 'server', 'data', 'lib') ] + return candidates } /** 优先使用第一个已存在目录;均不存在时创建第一个可写目录。 */ private async getTargetDir(): Promise { - const candidates = this.getWritableLibDirCandidates() + const candidates = this.getLibDirCandidates() for (const candidate of candidates) { try { @@ -79,21 +69,6 @@ class PtyManager { */ async getPtyPath(): Promise { const asset = getPtyAsset() - - for (const candidate of this.getLibDirCandidates()) { - const targetPath = path.join(candidate, asset.name) - if (!await verifyPtyAsset(targetPath, asset)) { - continue - } - - try { - await probePtyAsset(targetPath, asset) - return targetPath - } catch { - logger.warn(`PTY 资产能力探测失败,将尝试其他路径: ${targetPath}`) - } - } - const targetDir = await this.getTargetDir() return ensurePtyAsset({ asset, targetDir, logger }) } @@ -117,13 +92,13 @@ class PtyManager { const targetPath = path.join(candidate, asset.name) if (!await verifyPtyAsset(targetPath, asset)) { - continue + return false } try { await probePtyAsset(targetPath, asset) return true } catch { - continue + return false } } diff --git a/server/src/utils/zipToolsManager.ts b/server/src/utils/zipToolsManager.ts index b2cdba5..e8c74db 100644 --- a/server/src/utils/zipToolsManager.ts +++ b/server/src/utils/zipToolsManager.ts @@ -127,12 +127,10 @@ class ZipToolsManager { */ private getLibDirCandidates(): string[] { const baseDir = process.cwd() - return Array.from(new Set([ - path.join(baseDir, 'data', 'lib'), // 打包后或 Docker 运行时目录 - path.join(baseDir, 'server', 'data', 'lib'), // 开发环境目录 - path.join(baseDir, 'builtin', 'data', 'lib'), // 当前 Docker 镜像内置资产目录 - path.join(baseDir, '..', 'data', 'lib'), // 兼容旧版 Docker/启动脚本布局 - ])) + return [ + path.join(baseDir, 'data', 'lib'), // 打包后环境 + path.join(baseDir, 'server', 'data', 'lib'), // 开发环境 + ] } /** @@ -232,11 +230,7 @@ class ZipToolsManager { logger.info(`Zip-Tools 下载完成: ${targetPath}`) } catch (error: any) { // 清理可能的残留文件 - try { - await fs.unlink(targetPath) - } catch (cleanupError) { - logger.debug(`清理 Zip-Tools 残留文件失败: ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}`) - } + try { await fs.unlink(targetPath) } catch { /* 忽略 */ } const message = `Zip-Tools 下载失败(GitHub): ${error.message || error}` logger.error(message) throw new Error(message) @@ -348,11 +342,7 @@ class ZipToolsManager { logger.info(`7z 下载完成: ${targetPath}`) } catch (error: any) { // 清理可能的残留文件 - try { - await fs.unlink(targetPath) - } catch (cleanupError) { - logger.debug(`清理 7z 残留文件失败: ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}`) - } + try { await fs.unlink(targetPath) } catch { /* 忽略 */ } const message = `7z 下载失败(GitHub): ${error.message || error}` logger.error(message) throw new Error(message) diff --git a/start.sh b/start.sh index df41685..24e9d38 100644 --- a/start.sh +++ b/start.sh @@ -7,25 +7,6 @@ echo " GSM3 游戏服务端管理面板" echo "======================================" echo -# 将打包内置的 lib 资产同步到运行时目录(Docker 数据卷会覆盖 server/data/lib) -seed_runtime_lib_assets() { - local runtime_lib="$1" - local builtin_lib="$2" - - mkdir -p "$runtime_lib" - if [ ! -d "$builtin_lib" ]; then - return - fi - - for asset in "$builtin_lib"/*; do - [ -f "$asset" ] || continue - local dest="$runtime_lib/$(basename "$asset")" - if [ ! -e "$dest" ]; then - cp -a "$asset" "$dest" - fi - done -} - # 检查是否存在GSM3应用文件 if [ -f "server/index.js" ]; then echo "🚀 启动GSM3管理面板..." @@ -56,18 +37,42 @@ if [ -f "server/index.js" ]; then # Docker 的持久卷会遮蔽镜像内的 server/data,补充卷中缺失的内置运行时资产。 BUILTIN_LIB_DIR="server/builtin/data/lib" RUNTIME_LIB_DIR="server/data/lib" - seed_runtime_lib_assets "$RUNTIME_LIB_DIR" "$BUILTIN_LIB_DIR" + if [ -d "$BUILTIN_LIB_DIR" ]; then + mkdir -p "$RUNTIME_LIB_DIR" + cp -an "$BUILTIN_LIB_DIR"/. "$RUNTIME_LIB_DIR"/ 2>/dev/null || true + fi - # PTY 由服务端固定资产管理器校验和探测,避免依赖镜像中未安装的 file 命令。 - # 内置资产缺失或损坏时,服务端会自动选择可写目录并恢复固定版本。 + # PTY 文件已迁移到 data/lib/ 目录,启动时由服务端自动检测和下载 + # 如果 data/lib/ 中存在 PTY 文件,验证并设置可执行权限 + ARCH=$(uname -m) + if [ "$ARCH" = "x86_64" ]; then + PTY_FILE="$RUNTIME_LIB_DIR/pty_linux_x64" + elif [ "$ARCH" = "aarch64" ]; then + PTY_FILE="$RUNTIME_LIB_DIR/pty_linux_arm64" + else + PTY_FILE="" + fi + if [ -n "$PTY_FILE" ] && [ -f "$PTY_FILE" ]; then + # 验证是否为有效的ELF二进制文件 + if file "$PTY_FILE" 2>/dev/null | grep -q "ELF"; then + chmod +x "$PTY_FILE" + echo "✅ PTY权限设置完成 ($ARCH)" + else + echo "⚠️ PTY文件无效(非ELF二进制),已删除,服务启动时将自动重新下载" + rm -f "$PTY_FILE" + fi + else + echo "ℹ️ PTY文件将在服务启动时自动下载" + fi + # 启动应用 cd server node index.js else echo "❌ 未找到GSM3应用文件,正在启动传统Steam服务器管理..." echo - + # 传统的Steam服务器管理菜单 ARCH=$(uname -m) while true; do