diff --git a/packages/vnc/Dockerfile b/packages/vnc/Dockerfile deleted file mode 100644 index 4338d42..0000000 --- a/packages/vnc/Dockerfile +++ /dev/null @@ -1,54 +0,0 @@ -# Use a lightweight Node.js image -FROM node:20-alpine AS builder - -# Set the working directory -WORKDIR /app - -# Copy package.json and package-lock.json -COPY packages/vnc/package.json packages/vnc/package-lock.json* ./ - -# Install ALL dependencies (including devDependencies like typescript) -RUN npm install - -# Copy source code and tsconfig -COPY packages/vnc/src ./src -COPY packages/vnc/tsconfig.json ./tsconfig.json - -# Build the TypeScript code -RUN npm run build - -# Remove development dependencies after build -RUN npm prune --production - -# --- Production Stage --- -FROM node:20-alpine - -WORKDIR /app - -# Copy built code and node_modules from builder stage -COPY --from=builder /app/dist ./dist -COPY --from=builder /app/node_modules ./node_modules -COPY packages/vnc/package.json ./package.json - -# --- Add patch application steps --- -# Copy the patches directory from the build context (relative to project root) -COPY patches ./patches - -# Install patch-package temporarily to apply patches -# Note: We install it here again in case prune removed it, and ensure it's available in the final stage. -# Using --no-save as we don't need it in the final package.json dependencies. -RUN npm install patch-package --no-save - -# Apply patches -RUN npx patch-package --error-on-fail - -# Uninstall patch-package after applying to keep the image clean -RUN npm uninstall patch-package -# --- End patch application steps --- - -# Expose the API and WebSocket ports -EXPOSE 9091 -EXPOSE 8082 - -# Command to run the application -CMD ["node", "dist/server.js"] \ No newline at end of file diff --git a/packages/vnc/guacamole-lite.d.ts b/packages/vnc/guacamole-lite.d.ts deleted file mode 100644 index e01e1e6..0000000 --- a/packages/vnc/guacamole-lite.d.ts +++ /dev/null @@ -1 +0,0 @@ -declare module 'guacamole-lite'; \ No newline at end of file diff --git a/packages/vnc/package.json b/packages/vnc/package.json deleted file mode 100644 index 881181b..0000000 --- a/packages/vnc/package.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "name": "@nexus-terminal/vnc", - "version": "1.0.0", - "main": "index.js", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1", - "build": "tsc", - "start": "node dist/server.js", - "dev": "nodemon --exec \"ts-node --files src/server.ts\"" - }, - "keywords": [], - "author": "", - "license": "ISC", - "description": "VNC service for Nexus Terminal", - "devDependencies": { - "@types/cors": "^2.8.17", - "@types/express": "^5.0.1", - "@types/node": "^22.15.2", - "@types/ws": "^8.18.1", - "nodemon": "^3.1.10", - "ts-node": "^10.9.2", - "typescript": "^5.8.3" - }, - "dependencies": { - "cors": "^2.8.5", - "express": "^5.1.0", - "guacamole-lite": "^0.7.3", - "ws": "^8.18.1" - } -} \ No newline at end of file diff --git a/packages/vnc/src/server.ts b/packages/vnc/src/server.ts deleted file mode 100644 index 3468e71..0000000 --- a/packages/vnc/src/server.ts +++ /dev/null @@ -1,219 +0,0 @@ -// @ts-ignore - Still need this for the import as no types exist -import GuacamoleLite from 'guacamole-lite'; -import express, { Request, Response } from 'express'; -import http from 'http'; -import crypto from 'crypto'; -import cors from 'cors'; - - -// --- 配置 --- -const GUAC_WS_PORT = process.env.VNC_WS_PORT || 8082; -const API_PORT = process.env.VNC_PORT || 9091; -const GUACD_HOST = process.env.GUACD_HOST || 'localhost'; -const GUACD_PORT = parseInt(process.env.GUACD_PORT || '4822', 10); - -// --- 启动时生成内存加密密钥 --- -console.log("正在为此会话生成新的内存加密密钥..."); -const ENCRYPTION_KEY_STRING = crypto.randomBytes(32).toString('hex'); -const ENCRYPTION_KEY_BUFFER = Buffer.from(ENCRYPTION_KEY_STRING, 'hex'); -console.log("内存加密密钥已生成。"); - -// --- Express 应用设置 --- -const app = express(); -const apiServer = http.createServer(app); - -const allowedOrigins = [ - process.env.FRONTEND_URL || 'http://localhost:5173', - process.env.MAIN_BACKEND_URL || 'http://localhost:3000' -]; -app.use(cors({ origin: allowedOrigins })); - - -const guacdOptions = { - host: GUACD_HOST, - port: GUACD_PORT, -}; - -const websocketOptions = { - port: GUAC_WS_PORT, - host: '0.0.0.0', -}; - -const clientOptions = { - crypt: { - // 将实际的密钥 Buffer 传递给 guacamole-lite 用于其内部加密操作 - key: ENCRYPTION_KEY_BUFFER, - cypher: 'aes-256-cbc' // 确保加密和解密之间的密码算法一致 - }, - // 默认连接设置 - connectionDefaultSettings: { - // VNC 通常不需要像 RDP 那样的特定默认连接设置 - // 参数将主要通过 API 请求提供 - }, -}; - -let guacServer: any; - -try { - console.log(`[VNC 服务] 正在使用选项初始化 GuacamoleLite: WS 端口=${websocketOptions.port}, Guacd=${guacdOptions.host}:${guacdOptions.port}`); - guacServer = new GuacamoleLite(websocketOptions, guacdOptions, clientOptions); - console.log(`[VNC 服务] GuacamoleLite 初始化成功。`); - - if (guacServer.on) { - guacServer.on('error', (error: Error) => { - console.error(`[VNC 服务] GuacamoleLite 服务器错误:`, error); - }); - guacServer.on('connection', (client: any) => { - const clientId = client.id || '未知客户端ID'; - console.log(`[VNC 服务] Guacd 连接事件触发。客户端 ID: ${clientId}`); - - - if (client && typeof client.on === 'function') { - client.on('disconnect', (reason: string) => { - console.log(`[VNC 服务] Guacd 连接断开。客户端 ID: ${clientId}, 原因: ${reason || '未知'}`); - }); - client.on('error', (err: Error) => { - console.error(`[VNC 服务] Guacd 客户端错误。客户端 ID: ${clientId}, 错误:`, err); - }); - - client.on('message', (message: Buffer | string) => { - // 在回滚状态下移除了消息处理 - }); - - } else { - // 对没有 'on' 方法的客户端进行最小化处理 - } - }); - } -} catch (error) { - console.error(`[VNC 服务] 初始化 GuacamoleLite 失败:`, error); - process.exit(1); -} - -// 更新了 encryptToken 以匹配 guacamole-lite 期望的格式 (aes-256-cbc 和特定的 JSON 结构) -// 现在直接接受密钥 Buffer 以进行正确的加密操作 -const encryptToken = (data: string, keyBuffer: Buffer): string => { - try { - const iv = crypto.randomBytes(16); // AES-CBC 通常使用 16 字节的 IV - // 使用密钥 Buffer 进行 Node.js 加密操作 - const cipher = crypto.createCipheriv('aes-256-cbc', keyBuffer, iv); - - let encrypted = cipher.update(data, 'utf8', 'base64'); - encrypted += cipher.final('base64'); - - // 构建 guacamole-lite 的解密函数期望的 JSON 对象 - const output = { - iv: iv.toString('base64'), - value: encrypted - }; - - // 将 JSON 字符串化,然后对整个字符串进行 Base64 编码 - const jsonString = JSON.stringify(output); - return Buffer.from(jsonString).toString('base64'); - - } catch (e) { - console.error("令牌加密失败:", e); - throw new Error("令牌加密失败。"); - } -}; - - - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -app.get('/api/get-vnc-token', (req: any, res: any) => { - const { hostname, port, password } = req.query; // VNC 主要参数 - - if (!hostname || !port || typeof password === 'undefined') { - return res.status(400).json({ error: '缺少必需的 VNC 参数 (hostname, port, password)' }); - } - - const connectionParams: any = { // 使用 any 类型以允许动态添加参数 - connection: { - type: 'vnc', - settings: { - hostname: hostname as string, - port: port as string, - password: password as string, - // 从查询中包含动态(或默认)的大小参数 - width: String(req.query.width || '1024'), - height: String(req.query.height || '768'), - // VNC 特有的参数可以根据需要在这里添加 - // 例如: 'username': req.query.username (如果 VNC 服务器需要) - } - } - }; - - // 如果提供了 username,则添加到 settings 中 - if (req.query.username) { - connectionParams.connection.settings.username = req.query.username as string; - } - - // Guacamole VNC 支持的其他参数可以类似地添加 - // 例如: 'enable-audio': req.query.enableAudio || 'false' - - try { - const tokenData = JSON.stringify(connectionParams); - const encryptedToken = encryptToken(tokenData, ENCRYPTION_KEY_BUFFER); - res.json({ token: encryptedToken }); - } catch (error) { - console.error("/api/get-vnc-token 接口出错:", error); - res.status(500).json({ error: '生成令牌失败' }); - } -}); - -apiServer.listen(API_PORT, () => { - console.log(`[VNC 服务] API 服务器正在监听端口 ${API_PORT}`); - console.log(`[VNC 服务] Guacamole WebSocket 服务器应在端口 ${GUAC_WS_PORT} 上运行 (由 GuacamoleLite 管理)`); -}); - -const gracefulShutdown = (signal: string) => { - console.log(`收到 ${signal} 信号。正在优雅地关闭...`); - - let guacClosed = false; - let apiClosed = false; - - const tryExit = () => { - if (guacClosed && apiClosed) { - console.log("所有服务器已关闭。正在退出。"); - process.exit(0); - } - }; - - apiServer.close((err) => { - if (err) { - console.error("关闭 API 服务器时出错:", err); - } else { - console.log("API 服务器已关闭。"); - } - apiClosed = true; - tryExit(); - }); - - // @ts-ignore - 假设基于通用模式存在 close 方法 - if (typeof guacServer !== 'undefined' && guacServer && typeof guacServer.close === 'function') { - console.log("正在关闭 Guacamole 服务器..."); // 添加了关闭日志 - // @ts-ignore - guacServer.close(() => { - console.log("Guacamole 服务器已关闭。"); // 添加了关闭日志 - guacClosed = true; - tryExit(); - }); - } else { - console.log("Guacamole 服务器未运行或不支持 close() 方法。"); - guacClosed = true; - tryExit(); - } - - // 超时后强制退出 - setTimeout(() => { - console.error("关闭超时。强制退出。"); - process.exit(1); - }, 10000); // 10 秒超时 -}; - -process.on('SIGINT', () => gracefulShutdown('SIGINT')); -process.on('SIGTERM', () => gracefulShutdown('SIGTERM')); - -process.on('SIGUSR2', () => { - gracefulShutdown('SIGUSR2 (nodemon restart)'); -}); \ No newline at end of file diff --git a/packages/vnc/tsconfig.json b/packages/vnc/tsconfig.json deleted file mode 100644 index e1b4b9a..0000000 --- a/packages/vnc/tsconfig.json +++ /dev/null @@ -1,117 +0,0 @@ -{ - "compilerOptions": { - /* Visit https://aka.ms/tsconfig to read more about this file */ - - /* Projects */ - // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ - // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ - // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ - // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ - // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ - // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ - - /* Language and Environment */ - "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ - // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ - // "jsx": "preserve", /* Specify what JSX code is generated. */ - // "libReplacement": true, /* Enable lib replacement. */ - // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ - // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ - // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ - // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ - // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ - // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ - // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ - // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ - // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ - - /* Modules */ - "module": "commonjs", /* Specify what module code is generated. */ - "rootDir": "src", /* Specify the root folder within your source files. */ - // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ - // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ - // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ - // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ - // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ - // "types": [], /* Specify type package names to be included without being referenced in a source file. */ - // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ - // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ - // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ - // "rewriteRelativeImportExtensions": true, /* Rewrite '.ts', '.tsx', '.mts', and '.cts' file extensions in relative import paths to their JavaScript equivalent in output files. */ - // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ - // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ - // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ - // "noUncheckedSideEffectImports": true, /* Check side effect imports. */ - "resolveJsonModule": true, /* Enable importing .json files. */ - // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ - // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ - - /* JavaScript Support */ - // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ - // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ - // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ - - /* Emit */ - // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ - // "declarationMap": true, /* Create sourcemaps for d.ts files. */ - // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ - "sourceMap": true, /* Create source map files for emitted JavaScript files. */ - // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ - // "noEmit": true, /* Disable emitting files from a compilation. */ - // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ - "outDir": "dist", /* Specify an output folder for all emitted files. */ - // "removeComments": true, /* Disable emitting comments. */ - // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ - // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ - // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ - // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ - // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ - // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ - // "newLine": "crlf", /* Set the newline character for emitting files. */ - // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ - // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ - // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ - // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ - // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ - - /* Interop Constraints */ - // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ - // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ - // "isolatedDeclarations": true, /* Require sufficient annotation on exports so other tools can trivially generate declaration files. */ - // "erasableSyntaxOnly": true, /* Do not allow runtime constructs that are not part of ECMAScript. */ - // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ - "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ - // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ - "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ - - /* Type Checking */ - "strict": true, /* Enable all strict type-checking options. */ - // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ - // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ - // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ - // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ - // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ - // "strictBuiltinIteratorReturn": true, /* Built-in iterators are instantiated with a 'TReturn' type of 'undefined' instead of 'any'. */ - // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ - // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ - // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ - // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ - // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ - // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ - // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ - // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ - // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ - // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ - // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ - // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ - // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ - - /* Completeness */ - // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ - "skipLibCheck": true /* Skip type checking all .d.ts files. */ - }, - "include": [ - "src/**/*", // Include all files in the src directory - "guacamole-lite.d.ts" // Include the specific .d.ts file - ] -} \ No newline at end of file