diff --git a/app/Livewire/Project/Shared/Terminal.php b/app/Livewire/Project/Shared/Terminal.php index e070ee367..70b8fd18f 100644 --- a/app/Livewire/Project/Shared/Terminal.php +++ b/app/Livewire/Project/Shared/Terminal.php @@ -38,6 +38,9 @@ class Terminal extends Component // 1. Laravel Pusher/Echo connection (not possible without a sdk) // 2. Ratchet / Revolt / ReactPHP / Event Loop (possible but hard to implement and huge dependencies) // 3. Just found out about this https://github.com/sirn-se/websocket-php, perhaps it can be used + // 4. Follow-up discussions here: + // - https://github.com/coollabsio/coolify/issues/2298 + // - https://github.com/coollabsio/coolify/discussions/3362 $this->dispatch('send-back-command', $command); } diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 16bb354a5..dfd8684cc 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -53,6 +53,7 @@ services: - "6002:6002" volumes: - ./storage:/var/www/html/storage + - ./docker/coolify-realtime/terminal-server.js:/terminal/terminal-server.js environment: SOKETI_DEBUG: "false" SOKETI_DEFAULT_APP_ID: "${PUSHER_APP_ID:-coolify}" diff --git a/docker/coolify-realtime/terminal-server.js b/docker/coolify-realtime/terminal-server.js index 6afbd3445..c03f9b0df 100755 --- a/docker/coolify-realtime/terminal-server.js +++ b/docker/coolify-realtime/terminal-server.js @@ -106,7 +106,12 @@ async function handleCommand(ws, command, userId) { const userSession = userSessions.get(userId); if (userSession && userSession.isActive) { - await killPtyProcess(userId); + const result = await killPtyProcess(userId); + if (!result) { + // if terminal is still active, even after we tried to kill it, dont continue and show error + ws.send('unprocessable'); + return; + } } const commandString = command[0].split('\n').join(' '); @@ -129,7 +134,8 @@ async function handleCommand(ws, command, userId) { userSession.ptyProcess = ptyProcess; userSession.isActive = true; ptyProcess.write(hereDocContent + '\n'); - ptyProcess.write('clear\n'); + // clear the terminal if the user has clear command + ptyProcess.write('command -v clear >/dev/null 2>&1 && clear\n'); ws.send('pty-ready'); @@ -162,12 +168,32 @@ async function killPtyProcess(userId) { if (!session?.ptyProcess) return false; return new Promise((resolve) => { - session.ptyProcess.on('exit', () => { - session.isActive = false; - resolve(true); - }); + // Loop to ensure terminal is killed before continuing + let killAttempts = 0; + const maxAttempts = 5; - session.ptyProcess.kill(); + const attemptKill = () => { + killAttempts++; + + // session.ptyProcess.kill() wont work here because of https://github.com/moby/moby/issues/9098 + // patch with https://github.com/moby/moby/issues/9098#issuecomment-189743947 + session.ptyProcess.write('kill -TERM -$$ && exit\n'); + + setTimeout(() => { + if (!session.isActive || !session.ptyProcess) { + resolve(true); + return; + } + + if (killAttempts < maxAttempts) { + attemptKill(); + } else { + resolve(false); + } + }, 500); + }; + + attemptKill(); }); } diff --git a/resources/views/livewire/project/shared/terminal.blade.php b/resources/views/livewire/project/shared/terminal.blade.php index f351bd42f..11c59810e 100644 --- a/resources/views/livewire/project/shared/terminal.blade.php +++ b/resources/views/livewire/project/shared/terminal.blade.php @@ -1,12 +1,11 @@