Skip to content

Commit

Permalink
fix: grouped process and docker execs are killed with ssh process
Browse files Browse the repository at this point in the history
fix: run clear command only if exists
fix: link terminal js on dev compose better dx
fix: add error on terminal ux
  • Loading branch information
LEstradioto committed Sep 12, 2024
1 parent 2edcd01 commit 35dfb1b
Show file tree
Hide file tree
Showing 4 changed files with 47 additions and 13 deletions.
3 changes: 3 additions & 0 deletions app/Livewire/Project/Shared/Terminal.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ public function sendTerminalCommand($isContainer, $identifier, $serverUuid)
// 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);
}

Expand Down
1 change: 1 addition & 0 deletions docker-compose.dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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}"
Expand Down
42 changes: 34 additions & 8 deletions docker/coolify-realtime/terminal-server.js
Original file line number Diff line number Diff line change
Expand Up @@ -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(' ');
Expand All @@ -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');

Expand Down Expand Up @@ -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);
});

session.ptyProcess.kill();
// Loop to ensure terminal is killed before continuing
let killAttempts = 0;
const maxAttempts = 5;

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();
});
}

Expand Down
14 changes: 9 additions & 5 deletions resources/views/livewire/project/shared/terminal.blade.php
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
<div x-data="data()">
<div x-show="!terminalActive" class="flex items-center justify-center w-full py-4 mx-auto h-[510px]">
<div
class="w-full h-full bg-white border border-solid rounded dark:text-white dark:bg-coolgray-100 scrollbar border-neutral-300 dark:border-coolgray-300 p-1">
<span class="font-mono text-sm text-gray-500 ">(connection closed)</span>
<div class="w-full h-full border rounded dark:bg-coolgray-100 dark:border-coolgray-300">
<span class="font-mono text-sm text-gray-500" x-text="message"></span>
</div>
</div>
<div x-ref="terminalWrapper"
:class="fullscreen ? 'fullscreen' : 'relative w-full h-full py-4 mx-auto max-h-[510px]'">
:class="fullscreen ? 'fullscreen' : 'relative w-full h-full py-4 mx-auto max-h-[510px]'">
<div id="terminal" wire:ignore></div>
<button title="Minimize" x-show="fullscreen" class="fixed top-4 right-4" x-on:click="makeFullscreen"><svg
class="icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
Expand Down Expand Up @@ -49,6 +48,7 @@ function initializeWebSocket() {
}
function handleSocketMessage(event) {
$data.message = '(connection closed)';
// Initialize Terminal
if (event.data === 'pty-ready') {
term.open(document.getElementById('terminal'));
Expand All @@ -57,6 +57,10 @@ function handleSocketMessage(event) {
term.focus();
document.querySelector('.xterm-viewport').classList.add('scrollbar', 'rounded')
$data.resizeTerminal()
} else if (event.data === 'unprocessable') {
term.reset();
$data.terminalActive = false;
$data.message = '(sorry, something went wrong, please try again)';
} else {
pendingWrites++;
term.write(event.data, flowControlCallback);
Expand Down Expand Up @@ -91,7 +95,6 @@ function flowControlCallback() {
checkIfProcessIsRunningAndKillIt();
setTimeout(() => {
term.reset();
term.write('(connection closed)');
$data.terminalActive = false;
}, 500);
commandBuffer = '';
Expand Down Expand Up @@ -152,6 +155,7 @@ function checkIfProcessIsRunningAndKillIt() {
Alpine.data('data', () => ({
fullscreen: false,
terminalActive: false,
message: '(connection closed)',
init() {
this.$watch('terminalActive', (value) => {
this.$nextTick(() => {
Expand Down

0 comments on commit 35dfb1b

Please sign in to comment.