From e03e4f2e915a43cedec5a7a310b2ff8cd6c583f7 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Wed, 2 Oct 2024 15:16:55 +0200 Subject: [PATCH] refactor: Improve SSH command generation in Terminal.php and terminal-server.js --- app/Livewire/Project/Shared/Terminal.php | 4 +- docker/coolify-realtime/terminal-server.js | 47 +++++++++++++++++++--- resources/js/terminal.js | 22 +++++++++- 3 files changed, 64 insertions(+), 9 deletions(-) diff --git a/app/Livewire/Project/Shared/Terminal.php b/app/Livewire/Project/Shared/Terminal.php index 27be462272..916db650f6 100644 --- a/app/Livewire/Project/Shared/Terminal.php +++ b/app/Livewire/Project/Shared/Terminal.php @@ -34,9 +34,9 @@ public function sendTerminalCommand($isContainer, $identifier, $serverUuid) if ($status !== 'running') { return; } - $command = SshMultiplexingHelper::generateSshCommand($server, "docker exec -it {$identifier} sh -c 'if [ -f ~/.profile ]; then . ~/.profile; fi; if [ -n \"\$SHELL\" ]; then exec \$SHELL; else sh; fi'"); + $command = SshMultiplexingHelper::generateSshCommand($server, "docker exec -it {$identifier} sh -c 'PATH=\$PATH:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin && if [ -f ~/.profile ]; then . ~/.profile; fi && if [ -n \"\$SHELL\" ]; then exec \$SHELL; else sh; fi'"); } else { - $command = SshMultiplexingHelper::generateSshCommand($server, "sh -c 'if [ -f ~/.profile ]; then . ~/.profile; fi; if [ -n \"\$SHELL\" ]; then exec \$SHELL; else sh; fi'"); + $command = SshMultiplexingHelper::generateSshCommand($server, 'PATH=$PATH:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin && if [ -f ~/.profile ]; then . ~/.profile; fi && if [ -n "$SHELL" ]; then exec $SHELL; else sh; fi'); } // ssh command is sent back to frontend then to websocket diff --git a/docker/coolify-realtime/terminal-server.js b/docker/coolify-realtime/terminal-server.js index 8658ecdf83..28e400d2e3 100755 --- a/docker/coolify-realtime/terminal-server.js +++ b/docker/coolify-realtime/terminal-server.js @@ -55,15 +55,37 @@ const verifyClient = async (info, callback) => { const wss = new WebSocketServer({ server, path: '/terminal/ws', verifyClient: verifyClient }); const userSessions = new Map(); +let inactivityTimer; +const inactivityTime = 60 * 1000; +const inactivityInterval = 10 * 1000; wss.on('connection', (ws) => { const userId = generateUserId(); - const userSession = { ws, userId, ptyProcess: null, isActive: false }; + const userSession = { ws, userId, ptyProcess: null, isActive: false, lastActivityTime: Date.now() }; userSessions.set(userId, userSession); - ws.on('message', (message) => handleMessage(userSession, message)); + // ws.on('pong', () => { + // console.log('pong'); + // userSession.lastActivityTime = Date.now(); + // clearInterval(inactivityTimer); + // inactivityTimer = setInterval(() => { + // const inactiveTime = Date.now() - userSession.lastActivityTime; + // console.log('inactiveTime', inactiveTime); + // if (inactiveTime > inactivityTime) { + // killPtyProcess(userId); + // clearInterval(inactivityTimer); + // } + // }, inactivityInterval); + // }); + + ws.on('message', (message) => { + handleMessage(userSession, message); + userSession.lastActivityTime = Date.now(); + + }); ws.on('error', (err) => handleError(err, userId)); ws.on('close', () => handleClose(userId)); + }); const messageHandlers = { @@ -108,7 +130,6 @@ function parseMessage(message) { async function handleCommand(ws, command, userId) { const userSession = userSessions.get(userId); - if (userSession && userSession.isActive) { const result = await killPtyProcess(userId); if (!result) { @@ -139,13 +160,27 @@ async function handleCommand(ws, command, userId) { ws.send('pty-ready'); - ptyProcess.onData((data) => ws.send(data)); + ptyProcess.onData((data) => { + ws.send(data); + // userSession.lastActivityTime = Date.now(); + // clearInterval(inactivityTimer); + // inactivityTimer = setInterval(() => { + // const inactiveTime = Date.now() - userSession.lastActivityTime; + // console.log('inactiveTime', inactiveTime); + // if (inactiveTime > inactivityTime) { + // killPtyProcess(userId); + // clearInterval(inactivityTimer); + // } + // }, inactivityInterval); + }); // when parent closes ptyProcess.onExit(({ exitCode, signal }) => { console.error(`Process exited with code ${exitCode} and signal ${signal}`); ws.send('pty-exited'); userSession.isActive = false; + // clearInterval(inactivityTimer); + }); if (timeout) { @@ -179,7 +214,7 @@ async function killPtyProcess(userId) { // 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'); + session.ptyProcess.write('set +o history\nkill -TERM -$$ && exit\nset -o history\n'); setTimeout(() => { if (!session.isActive || !session.ptyProcess) { @@ -228,5 +263,5 @@ function extractHereDocContent(commandString) { } server.listen(6002, () => { - console.log('Server listening on port 6002'); + console.log('Coolify realtime terminal server listening on port 6002. Let the hacking begin!'); }); diff --git a/resources/js/terminal.js b/resources/js/terminal.js index fb69628c08..9d2469f044 100644 --- a/resources/js/terminal.js +++ b/resources/js/terminal.js @@ -16,6 +16,7 @@ export function initializeTerminalComponent() { paused: false, MAX_PENDING_WRITES: 5, keepAliveInterval: null, + reconnectInterval: null, init() { this.setupTerminal(); @@ -48,6 +49,9 @@ export function initializeTerminalComponent() { document.addEventListener(event, () => { this.checkIfProcessIsRunningAndKillIt(); clearInterval(this.keepAliveInterval); + if (this.reconnectInterval) { + clearInterval(this.reconnectInterval); + } }, { once: true }); }); @@ -103,11 +107,27 @@ export function initializeTerminalComponent() { }; this.socket.onclose = () => { console.log('WebSocket connection closed'); - + this.reconnect(); }; } }, + reconnect() { + if (this.reconnectInterval) { + clearInterval(this.reconnectInterval); + } + this.reconnectInterval = setInterval(() => { + console.log('Attempting to reconnect...'); + this.initializeWebSocket(); + if (this.socket && this.socket.readyState === WebSocket.OPEN) { + console.log('Reconnected successfully'); + clearInterval(this.reconnectInterval); + this.reconnectInterval = null; + window.location.reload(); + } + }, 5000); + }, + handleSocketMessage(event) { this.message = '(connection closed)'; if (event.data === 'pty-ready') {