diff --git a/client/bun.lock b/client/bun.lock index 8d2db1a..84638e8 100644 --- a/client/bun.lock +++ b/client/bun.lock @@ -1,5 +1,6 @@ { "lockfileVersion": 1, + "configVersion": 0, "workspaces": { "": { "name": "client", @@ -27,6 +28,7 @@ "@types/uuid": "^11.0.0", "@vitejs/plugin-react-swc": "^4.1.0", "autoprefixer": "^10.4.21", + "baseline-browser-mapping": "^2.9.2", "eslint": "^9.36.0", "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.22", @@ -282,7 +284,7 @@ "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], - "baseline-browser-mapping": ["baseline-browser-mapping@2.8.10", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-uLfgBi+7IBNay8ECBO2mVMGZAc1VgZWEChxm4lv+TobGdG82LnXMjuNGo/BSSZZL4UmkWhxEHP2f5ziLNwGWMA=="], + "baseline-browser-mapping": ["baseline-browser-mapping@2.9.7", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-k9xFKplee6KIio3IDbwj+uaCLpqzOwakOgmqzPezM0sFJlFKcg30vk2wOiAJtkTSfx0SSQDSe8q+mWA/fSH5Zg=="], "binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="], @@ -666,6 +668,8 @@ "anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + "browserslist/baseline-browser-mapping": ["baseline-browser-mapping@2.8.10", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-uLfgBi+7IBNay8ECBO2mVMGZAc1VgZWEChxm4lv+TobGdG82LnXMjuNGo/BSSZZL4UmkWhxEHP2f5ziLNwGWMA=="], + "chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], "engine.io-client/debug": ["debug@4.3.7", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ=="], diff --git a/session-server/bun.lock b/session-server/bun.lock index 6de29f2..1747f23 100644 --- a/session-server/bun.lock +++ b/session-server/bun.lock @@ -1,10 +1,12 @@ { "lockfileVersion": 1, + "configVersion": 0, "workspaces": { "": { "name": "strem.graff.tech-session-server", "dependencies": { "got": "^14.4.9", + "tree-kill": "^1.2.2", }, "devDependencies": { "bun-types": "latest", @@ -66,6 +68,8 @@ "responselike": ["responselike@3.0.0", "", { "dependencies": { "lowercase-keys": "^3.0.0" } }, "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg=="], + "tree-kill": ["tree-kill@1.2.2", "", { "bin": { "tree-kill": "cli.js" } }, "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A=="], + "type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="], "undici-types": ["undici-types@7.14.0", "", {}, "sha512-QQiYxHuyZ9gQUIrmPo3IA+hUl4KYk8uSA7cHrcKd/l3p1OTpZcM0Tbp9x7FAtXdAYhlasd60ncPpgu6ihG6TOA=="], diff --git a/session-server/package.json b/session-server/package.json index 53f2b91..3cd6436 100644 --- a/session-server/package.json +++ b/session-server/package.json @@ -12,6 +12,7 @@ "start": "bun ./dist/index.js" }, "dependencies": { - "got": "^14.4.9" + "got": "^14.4.9", + "tree-kill": "^1.2.2" } } diff --git a/session-server/session-server.rar b/session-server/session-server.rar index 5e9f978..cce0b86 100644 Binary files a/session-server/session-server.rar and b/session-server/session-server.rar differ diff --git a/session-server/src/index.ts b/session-server/src/index.ts index e9d92e9..3081bab 100644 --- a/session-server/src/index.ts +++ b/session-server/src/index.ts @@ -5,6 +5,8 @@ import { existsSync } from "fs"; import { createServer } from "net"; import { fileURLToPath } from "url"; import { dirname, join } from "path"; +// @ts-ignore - tree-kill не имеет типов +import treeKill from "tree-kill"; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); @@ -593,7 +595,7 @@ async function startApplication(session: SessionData): Promise { console.log( `[${new Date().toISOString()}] 🛑 Остановка Cirrus для завершённой сессии ${sessionId}` ); - killProcessTree(cirrus.pid); + await killProcessTree(cirrus.pid); activeCirrusProcesses.delete(sessionId); } } @@ -620,7 +622,7 @@ async function startApplication(session: SessionData): Promise { if (mode === "stream") { const cirrus = activeCirrusProcesses.get(sessionId); if (cirrus && cirrus.pid) { - killProcessTree(cirrus.pid); + await killProcessTree(cirrus.pid); activeCirrusProcesses.delete(sessionId); } } @@ -672,7 +674,7 @@ async function startApplication(session: SessionData): Promise { if (mode === "stream") { const cirrus = activeCirrusProcesses.get(sessionId); if (cirrus && cirrus.pid) { - killProcessTree(cirrus.pid); + await killProcessTree(cirrus.pid); activeCirrusProcesses.delete(sessionId); } } @@ -685,74 +687,59 @@ async function startApplication(session: SessionData): Promise { } /** - * Убить процесс и всё его дерево дочерних процессов (для Windows) - * Принудительное завершение без попыток мягкого закрытия + * Убить процесс и всё его дерево дочерних процессов + * Использует tree-kill для надёжного завершения всего дерева процессов */ -function killProcessTree(pid: number): void { - try { - // Принудительное завершение с /F +function killProcessTree(pid: number): Promise { + return new Promise((resolve) => { console.log( `[${new Date().toISOString()}] 🔄 Принудительное завершение процесса PID ${pid}...` ); - execSync(`taskkill /pid ${pid} /T /F`, { - stdio: "ignore", - timeout: 30000, // Таймаут 30 секунд - windowsHide: true, - killSignal: "SIGKILL", + + treeKill(pid, "SIGKILL", (err: Error | undefined) => { + if (err) { + // Проверяем, действительно ли процесс всё ещё существует + const processExists = checkProcessExists(pid); + if (!processExists) { + console.log( + `[${new Date().toISOString()}] ✅ Процесс PID ${pid} уже завершён` + ); + resolve(); + return; + } + + console.error( + `[${new Date().toISOString()}] ⚠️ Ошибка при завершении дерева процессов PID ${pid}:`, + err.message + ); + + // Fallback: попытка через taskkill + try { + console.log( + `[${new Date().toISOString()}] 🔄 Попытка завершения через taskkill для PID ${pid}` + ); + execSync(`taskkill /pid ${pid} /T /F`, { + stdio: "ignore", + timeout: 10000, + windowsHide: true, + }); + console.log( + `[${new Date().toISOString()}] ✅ Процесс PID ${pid} завершён через taskkill` + ); + } catch (fallbackErr) { + console.error( + `[${new Date().toISOString()}] ❌ Не удалось завершить процесс PID ${pid}:`, + fallbackErr instanceof Error ? fallbackErr.message : fallbackErr + ); + } + } else { + console.log( + `[${new Date().toISOString()}] ✅ Дерево процессов для PID ${pid} успешно завершено` + ); + } + resolve(); }); - console.log( - `[${new Date().toISOString()}] ✅ Дерево процессов для PID ${pid} успешно завершено принудительно` - ); - } catch (error) { - // Проверяем, действительно ли процесс всё ещё существует - const processExists = checkProcessExists(pid); - if (!processExists) { - console.log( - `[${new Date().toISOString()}] ✅ Процесс PID ${pid} уже завершён` - ); - return; - } - - console.error( - `[${new Date().toISOString()}] ⚠️ Ошибка при завершении дерева процессов PID ${pid}:`, - error instanceof Error ? error.message : error - ); - - // Попытка принудительного завершения через PowerShell как последний вариант - try { - console.log( - `[${new Date().toISOString()}] 🔄 Попытка принудительного завершения через PowerShell для PID ${pid}` - ); - - // Используем агрессивный PowerShell скрипт для принудительного завершения - const psScript = ` - $proc = Get-Process -Id ${pid} -ErrorAction SilentlyContinue; - if ($proc) { - # Принудительно завершаем процесс и все дочерние - Stop-Process -Id ${pid} -Force -ErrorAction SilentlyContinue; - # Завершаем все дочерние процессы - Get-Process | Where-Object { $_.Parent.Id -eq ${pid} } | Stop-Process -Force -ErrorAction SilentlyContinue; - } - `.replace(/\n/g, " "); - - execSync( - `powershell -Command "${psScript}"`, - { - stdio: "ignore", - timeout: 15000, - windowsHide: true, - } - ); - console.log( - `[${new Date().toISOString()}] ✅ Процесс PID ${pid} завершён через PowerShell` - ); - } catch (psError) { - console.error( - `[${new Date().toISOString()}] ❌ Не удалось завершить процесс PID ${pid} даже через PowerShell:`, - psError instanceof Error ? psError.message : psError - ); - } - } + }); } /** @@ -822,16 +809,7 @@ async function stopApplication(session: SessionData): Promise { // Останавливаем UE приложение const pidToKill = appPid || appProcess?.pid; if (pidToKill) { - killPromises.push( - new Promise((resolve) => { - // Запускаем killProcessTree в отдельном потоке через setTimeout - // чтобы не блокировать основной поток - setTimeout(() => { - killProcessTree(pidToKill); - resolve(); - }, 0); - }) - ); + killPromises.push(killProcessTree(pidToKill)); } else { console.warn( `[${new Date().toISOString()}] ⚠️ Не удалось получить PID приложения для сессии ${sessionId}` @@ -842,14 +820,7 @@ async function stopApplication(session: SessionData): Promise { if (mode === "stream") { const cirrusPidToKill = cirrusPid || cirrusProcess?.pid; if (cirrusPidToKill) { - killPromises.push( - new Promise((resolve) => { - setTimeout(() => { - killProcessTree(cirrusPidToKill); - resolve(); - }, 0); - }) - ); + killPromises.push(killProcessTree(cirrusPidToKill)); } else { console.warn( `[${new Date().toISOString()}] ⚠️ Не удалось получить PID Cirrus для сессии ${sessionId}` @@ -1011,14 +982,14 @@ async function checkSessions(): Promise { `[${new Date().toISOString()}] ⚠️ Найден процесс для неактивной сессии ${sessionId}, остановка` ); if (process.pid) { - killProcessTree(process.pid); + await killProcessTree(process.pid); } activeProcesses.delete(sessionId); // Также останавливаем Cirrus если есть const cirrusProcess = activeCirrusProcesses.get(sessionId); if (cirrusProcess && cirrusProcess.pid) { - killProcessTree(cirrusProcess.pid); + await killProcessTree(cirrusProcess.pid); activeCirrusProcesses.delete(sessionId); } } diff --git a/stream.graff.estate.conf b/stream.graff.estate.conf deleted file mode 100644 index 040bccb..0000000 --- a/stream.graff.estate.conf +++ /dev/null @@ -1,51 +0,0 @@ -server { - listen 443 ssl http2; - listen [::]:443 ssl http2; - server_name stream.graff.estate; - root /var/www/stream.graff.estate/client/dist; - - # SSL - ssl_certificate /etc/letsencrypt/live/stream.graff.estate/fullchain.pem; - ssl_certificate_key /etc/letsencrypt/live/stream.graff.estate/privkey.pem; - ssl_trusted_certificate /etc/letsencrypt/live/stream.graff.estate/chain.pem; - - # security - include nginxconfig.io/security.conf; - - # logging - access_log /var/log/nginx/access.log combined buffer=512k flush=1m; - error_log /var/log/nginx/error.log warn; - - # index.html fallback - location / { - try_files $uri $uri/ /index.html; - } - - location /api { - rewrite ^/api/(.*)$ /$1 break; - proxy_pass http://127.0.0.1:6000; - proxy_set_header Host $host; - include nginxconfig.io/proxy.conf; - } - - location /socket.io { - proxy_pass http://127.0.0.1:6001; - proxy_set_header Host $host; - include nginxconfig.io/proxy.conf; - } - - # additional config - include nginxconfig.io/general.conf; -} - -# HTTP redirect -server { - listen 80; - listen [::]:80; - server_name stream.graff.estate; - include nginxconfig.io/letsencrypt.conf; - - location / { - return 301 https://stream.graff.estate$request_uri; - } -} \ No newline at end of file