Files
ladill-hosting/app/Services/Hosting/Terminal/LocalPtyShellSession.php
T
isaaccladandCursor e251a4cf60
Deploy Ladill Hosting / deploy (push) Failing after 17s
Initial Ladill Hosting app with Gitea deploy pipeline.
Shared web hosting extracted from the platform monolith, with CI deploy
to /var/www/ladill-hosting matching Bird/Domains/Email.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-06 16:24:20 +00:00

138 lines
3.0 KiB
PHP

<?php
namespace App\Services\Hosting\Terminal;
class LocalPtyShellSession implements ShellSession
{
/** @var resource|null */
private $process = null;
/** @var array<int, resource> */
private array $pipes = [];
public function __construct(
private string $command,
) {}
public function boot(): void
{
$spec = [
0 => ['pipe', 'r'],
1 => ['pty'],
2 => ['pty'],
];
$process = proc_open($this->command, $spec, $pipes, base_path());
if (! is_resource($process)) {
throw new \RuntimeException('Unable to start local PTY shell.');
}
$this->process = $process;
$this->pipes = $pipes;
foreach ($this->pipes as $pipe) {
stream_set_blocking($pipe, false);
}
}
public function read(): string
{
if (! $this->isAlive()) {
return '';
}
$output = '';
$stream = $this->outputStream();
if (! $stream) {
return '';
}
$read = [$stream];
$write = null;
$except = null;
$available = @stream_select($read, $write, $except, 0, 200000);
if ($available === false || $available === 0) {
return '';
}
$chunk = @stream_get_contents($stream);
if ($chunk !== false && $chunk !== '') {
$output .= $chunk;
}
return $output;
}
public function write(string $input): void
{
$stream = $this->inputStream();
if ($input === '' || ! $stream) {
return;
}
@fwrite($stream, $input);
@fflush($stream);
}
public function resize(int $cols, int $rows): void
{
// proc_open PTYs do not expose a portable runtime resize API.
}
public function isAlive(): bool
{
return is_resource($this->process) && (proc_get_status($this->process)['running'] ?? false);
}
public function close(): void
{
$input = $this->inputStream();
if ($input) {
@fwrite($input, "exit\n");
@fflush($input);
}
foreach ($this->pipes as $pipe) {
if (is_resource($pipe)) {
@fclose($pipe);
}
}
$this->pipes = [];
if (is_resource($this->process)) {
@proc_terminate($this->process);
@proc_close($this->process);
}
$this->process = null;
}
/** @return resource|null */
private function inputStream()
{
return isset($this->pipes[0]) && is_resource($this->pipes[0]) ? $this->pipes[0] : null;
}
/** @return resource|null */
private function outputStream()
{
if (isset($this->pipes[1]) && is_resource($this->pipes[1])) {
return $this->pipes[1];
}
if (isset($this->pipes[2]) && is_resource($this->pipes[2])) {
return $this->pipes[2];
}
return isset($this->pipes[0]) && is_resource($this->pipes[0]) ? $this->pipes[0] : null;
}
}