Files
MageSail/Magesail/Model/TunnelManager.php
T
2026-03-12 17:05:22 -05:00

109 lines
3.7 KiB
PHP

<?php
declare(strict_types=1);
namespace MageSail\Magesail\Model;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\Shell;
use Magento\Framework\Event\ManagerInterface as EventManager;
/**
* Start/stop tunnel process; shared by admin Index and cron auto-restart.
*/
class TunnelManager
{
private const XML_SCRIPT_PATH = 'magesail/settings/script_path';
private const DEFAULT_SCRIPT_REL = 'app/code/MageSail/Magesail/scripts';
public function __construct(
private readonly Shell $shell,
private readonly TunnelStatus $tunnelStatus,
private readonly ScopeConfigInterface $scopeConfig,
private readonly EventManager $eventManager
) {
}
public function getScriptDir(): string
{
$configured = trim((string) $this->scopeConfig->getValue(self::XML_SCRIPT_PATH));
if ($configured !== '') {
return $configured;
}
return BP . '/' . self::DEFAULT_SCRIPT_REL;
}
/**
* @return array{ok: bool, pid?: int, error?: string}
*/
public function start(int $port, bool $secure): array
{
if ($this->tunnelStatus->isRunning()) {
return ['ok' => false, 'error' => 'already_running'];
}
if (!\in_array($port, [80, 443], true)) {
return ['ok' => false, 'error' => 'invalid_port'];
}
$scriptDir = $this->getScriptDir();
$keyFile = $this->tunnelStatus->getKeyFilePath();
$logFile = $this->tunnelStatus->getLogFilePath();
$pidFile = $this->tunnelStatus->getPidFilePath();
$jsArgs = json_encode([
'port' => $port,
'secure' => $secure,
'keyfile' => $keyFile,
'logfile' => $logFile,
], JSON_THROW_ON_ERROR);
$arg = escapeshellarg($jsArgs);
$dir = escapeshellarg($scriptDir);
$log = escapeshellarg($logFile);
$fullCmd = "cd $dir && nohup node start-server.js $arg > $log 2>&1 & echo \$!";
try {
$output = $this->shell->execute($fullCmd);
$pid = (int) trim($output);
if ($pid > 0) {
file_put_contents($pidFile, (string) $pid);
$this->eventManager->dispatch('magesail_tunnel_start_after', [
'pid' => $pid,
'port' => $port,
'secure' => $secure,
]);
return ['ok' => true, 'pid' => $pid];
}
return ['ok' => false, 'error' => 'pid_capture_failed', 'detail' => $output];
} catch (\Throwable $e) {
return ['ok' => false, 'error' => $e->getMessage()];
}
}
/**
* @return array{ok: bool, error?: string}
*/
public function stop(): array
{
$pidFile = $this->tunnelStatus->getPidFilePath();
$keyFile = $this->tunnelStatus->getKeyFilePath();
if (!\is_file($pidFile)) {
return ['ok' => false, 'error' => 'not_running'];
}
$pid = (int) trim((string) file_get_contents($pidFile));
if ($pid <= 0 || !$this->tunnelStatus->isProcessAlive($pid)) {
@unlink($pidFile);
if (\is_file($keyFile)) {
@unlink($keyFile);
}
return ['ok' => false, 'error' => 'not_running'];
}
try {
$this->shell->execute('kill %s', [$pid]);
@unlink($pidFile);
if (\is_file($keyFile)) {
@unlink($keyFile);
}
$this->eventManager->dispatch('magesail_tunnel_stop_after', ['pid' => $pid]);
return ['ok' => true];
} catch (\Throwable $e) {
return ['ok' => false, 'error' => $e->getMessage()];
}
}
}