CI / php (push) Successful in 2m53s
- Add EnvServicePortResolver (DeploymentConfig) for MySQL, Redis frontends, session Redis, AMQP, http_cache_hosts, optional search - Extend tunnel registry with tunnel_type, service_key, service metadata; provisionServiceTunnel without store/NGINX map; teardown guards - Admin: Type (Website|Service), service dropdown, list Type column; JS toggle - TunnelManager/start-server: forward to resolved host:port - Tests: EnvServicePortResolver + service teardown; test stubs for Magento/PSR - docs: admin-ui Type/Service and security notes
163 lines
5.1 KiB
PHP
163 lines
5.1 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace MageSail\Magesail\Model;
|
|
|
|
use Magento\Framework\App\Config\ScopeConfigInterface;
|
|
use Magento\Framework\Shell;
|
|
|
|
/**
|
|
* PID/key state and process checks per tunnel id.
|
|
*/
|
|
class TunnelStatus
|
|
{
|
|
private const XML_AUTO_RESTART = 'magesail/settings/auto_restart';
|
|
|
|
public function __construct(
|
|
private readonly Shell $shell,
|
|
private readonly ScopeConfigInterface $scopeConfig,
|
|
private readonly TunnelStoreProvisioner $tunnelStoreProvisioner,
|
|
private readonly TunnelRegistry $tunnelRegistry,
|
|
private readonly TunnelProcessPaths $tunnelProcessPaths
|
|
) {
|
|
}
|
|
|
|
public function getPidFilePath(string $tunnelId): string
|
|
{
|
|
return $this->tunnelProcessPaths->getPidFilePath($tunnelId);
|
|
}
|
|
|
|
public function getKeyFilePath(string $tunnelId): string
|
|
{
|
|
return $this->tunnelProcessPaths->getKeyFilePath($tunnelId);
|
|
}
|
|
|
|
public function getLogFilePath(string $tunnelId): string
|
|
{
|
|
return $this->tunnelProcessPaths->getLogFilePath($tunnelId);
|
|
}
|
|
|
|
/**
|
|
* @return array{running: bool, pid: int|null, key: string|null}
|
|
*/
|
|
public function getStatus(string $tunnelId): array
|
|
{
|
|
$pidFile = $this->getPidFilePath($tunnelId);
|
|
$keyFile = $this->getKeyFilePath($tunnelId);
|
|
if (!\is_file($pidFile)) {
|
|
return ['running' => false, 'pid' => null, 'key' => null];
|
|
}
|
|
$pid = (int) trim((string) file_get_contents($pidFile));
|
|
if ($pid <= 0) {
|
|
return ['running' => false, 'pid' => null, 'key' => null];
|
|
}
|
|
if (!$this->isProcessAlive($pid)) {
|
|
$this->maybeTeardownTunnelStoreForStaleProcess($tunnelId);
|
|
@unlink($pidFile);
|
|
$kf = $this->getKeyFilePath($tunnelId);
|
|
if (\is_file($kf)) {
|
|
@unlink($kf);
|
|
}
|
|
return ['running' => false, 'pid' => null, 'key' => null];
|
|
}
|
|
$key = null;
|
|
if (\is_file($keyFile)) {
|
|
$key = trim((string) file_get_contents($keyFile));
|
|
if ($key === '') {
|
|
$key = null;
|
|
}
|
|
}
|
|
return ['running' => true, 'pid' => $pid, 'key' => $key];
|
|
}
|
|
|
|
public function isRunning(string $tunnelId): bool
|
|
{
|
|
return $this->getStatus($tunnelId)['running'];
|
|
}
|
|
|
|
/**
|
|
* @return list<array{tunnel_id: string, running: bool, pid: int|null, key: string|null, label: string, store_code: string, local_port: int}>
|
|
*/
|
|
public function getAllTunnelStatuses(): array
|
|
{
|
|
$out = [];
|
|
foreach ($this->tunnelRegistry->listTunnels() as $t) {
|
|
$id = (string) ($t['id'] ?? '');
|
|
if ($id === '') {
|
|
continue;
|
|
}
|
|
$st = $this->getStatus($id);
|
|
$out[] = [
|
|
'tunnel_id' => $id,
|
|
'tunnel_type' => (string) ($t['tunnel_type'] ?? 'website'),
|
|
'label' => (string) ($t['label'] ?? ''),
|
|
'store_code' => (string) ($t['store_code'] ?? ''),
|
|
'local_port' => (int) ($t['local_port'] ?? 80),
|
|
'service_key' => (string) ($t['service_key'] ?? ''),
|
|
'service_label' => (string) ($t['service_label'] ?? ''),
|
|
'service_target_host' => (string) ($t['service_target_host'] ?? ''),
|
|
'running' => $st['running'],
|
|
'pid' => $st['pid'],
|
|
'key' => $st['key'],
|
|
];
|
|
}
|
|
return $out;
|
|
}
|
|
|
|
public function isProcessAlive(int $pid): bool
|
|
{
|
|
if ($pid <= 0) {
|
|
return false;
|
|
}
|
|
try {
|
|
$this->shell->execute("ps -p %s > /dev/null 2>&1", [$pid]);
|
|
return true;
|
|
} catch (\Exception $e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* If PID file exists but process is dead, remove pid/key files and optionally teardown store.
|
|
*/
|
|
public function cleanupStaleStateForTunnel(string $tunnelId): void
|
|
{
|
|
$pidFile = $this->getPidFilePath($tunnelId);
|
|
if (!\is_file($pidFile)) {
|
|
return;
|
|
}
|
|
$pid = (int) trim((string) file_get_contents($pidFile));
|
|
if ($pid <= 0 || !$this->isProcessAlive($pid)) {
|
|
$this->maybeTeardownTunnelStoreForStaleProcess($tunnelId);
|
|
@unlink($pidFile);
|
|
$keyFile = $this->getKeyFilePath($tunnelId);
|
|
if (\is_file($keyFile)) {
|
|
@unlink($keyFile);
|
|
}
|
|
}
|
|
}
|
|
|
|
public function cleanupAllStalePidFiles(): void
|
|
{
|
|
foreach ($this->tunnelRegistry->listTunnels() as $t) {
|
|
$id = (string) ($t['id'] ?? '');
|
|
if ($id !== '') {
|
|
$this->cleanupStaleStateForTunnel($id);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* When the tunnel process is gone and auto-restart is off, remove the tunnel store view.
|
|
*/
|
|
private function maybeTeardownTunnelStoreForStaleProcess(string $tunnelId): void
|
|
{
|
|
$autoRestart = (bool) $this->scopeConfig->getValue(self::XML_AUTO_RESTART);
|
|
if ($autoRestart) {
|
|
return;
|
|
}
|
|
$this->tunnelStoreProvisioner->teardownAfterStaleProcess($tunnelId);
|
|
}
|
|
}
|