Files
isaaccladandCursor b6c8ac343f Extract Ladill Servers as standalone app at servers.ladill.com.
VPS and dedicated server ordering, managed panels, SSO, billing, and launcher integration — forked from hosting infrastructure with server-focused routes and dashboard.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-06 19:18:30 +00:00

98 lines
2.3 KiB
PHP

<?php
namespace App\Services\Hosting\Terminal;
use phpseclib3\Net\SSH2;
class RemoteSshShellSession implements ShellSession
{
private const STARTUP_TIMEOUT_SECONDS = 10;
private const READ_TIMEOUT_SECONDS = 0.1;
private bool $closed = false;
public function __construct(
private SSH2 $ssh,
private string $launchCommand,
private int $cols = 120,
private int $rows = 30,
) {}
public function boot(): void
{
$this->ssh->setWindowSize($this->cols, $this->rows);
$this->ssh->setTimeout(self::STARTUP_TIMEOUT_SECONDS);
$this->ssh->enablePTY();
if ($this->ssh->exec($this->launchCommand, false) !== true) {
throw new \RuntimeException('Unable to start remote terminal shell.');
}
}
public function read(): string
{
if (! $this->isAlive()) {
return '';
}
$output = '';
$this->ssh->setTimeout(self::READ_TIMEOUT_SECONDS);
while (true) {
$chunk = $this->ssh->read('', SSH2::READ_NEXT);
if ($chunk === true) {
if ($this->ssh->isTimeout()) {
break;
}
$this->closed = true;
break;
}
if (! is_string($chunk) || $chunk === '') {
break;
}
$output .= $chunk;
}
return $output;
}
public function write(string $input): void
{
if ($input === '' || ! $this->isAlive()) {
return;
}
$this->ssh->write($input);
}
public function resize(int $cols, int $rows): void
{
// phpseclib applies the PTY size during shell creation; runtime resize is left as a no-op
// to avoid injecting visible shell commands into the user session.
}
public function isAlive(): bool
{
return ! $this->closed && $this->ssh->isConnected();
}
public function close(): void
{
if ($this->ssh->isConnected()) {
try {
$this->ssh->write("exit\n");
} catch (\Throwable) {
// Ignore shutdown write failures.
}
$this->ssh->disconnect();
}
$this->closed = true;
}
}