- Add TunnelRegistry (var/magesail_tunnels.json) and TunnelProcessPaths; migrate legacy magesail_tunnel_store.json and PID/key files - Provision unique store codes per tunnel; website/group selection; stop vs remove; per-tunnel NGINX map, cron, status/logtail - Fix log path: use DirectoryList::LOG (not LOG_DIR) - Ignore missing store codes for MAGE_RUN_* (stale nginx / magesail_tunnel) - Allow duplicate local ports; Admin port presets 443/80/8080 + custom; update docs and system.xml comments
128 lines
4.7 KiB
PHP
128 lines
4.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 processes per tunnel id; 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,
|
|
private readonly TunnelRegistry $tunnelRegistry,
|
|
private readonly TunnelProcessPaths $tunnelProcessPaths
|
|
) {
|
|
}
|
|
|
|
public function getScriptDir(): string
|
|
{
|
|
$configured = trim((string) $this->scopeConfig->getValue(self::XML_SCRIPT_PATH));
|
|
if ($configured !== '') {
|
|
return $configured;
|
|
}
|
|
return BP . '/' . self::DEFAULT_SCRIPT_REL;
|
|
}
|
|
|
|
/**
|
|
* Starts the Node process for an existing registry tunnel (uses persisted port and secure flag).
|
|
*
|
|
* @return array{ok: bool, pid?: int, error?: string}
|
|
*/
|
|
public function start(string $tunnelId): array
|
|
{
|
|
$tunnel = $this->tunnelRegistry->getById($tunnelId);
|
|
if ($tunnel === null) {
|
|
return ['ok' => false, 'error' => 'unknown_tunnel'];
|
|
}
|
|
$port = (int) ($tunnel['local_port'] ?? 0);
|
|
$secure = (bool) ($tunnel['secure'] ?? false);
|
|
if ($this->tunnelStatus->isRunning($tunnelId)) {
|
|
return ['ok' => false, 'error' => 'already_running'];
|
|
}
|
|
if ($port < 1 || $port > 65535) {
|
|
return ['ok' => false, 'error' => 'invalid_port'];
|
|
}
|
|
$dir = $this->tunnelProcessPaths->getVarTunnelDir();
|
|
if (!\is_dir($dir) && !@mkdir($dir, 0775, true) && !\is_dir($dir)) {
|
|
return ['ok' => false, 'error' => 'could_not_create_var_dir'];
|
|
}
|
|
$scriptDir = $this->getScriptDir();
|
|
$keyFile = $this->tunnelProcessPaths->getKeyFilePath($tunnelId);
|
|
$logFile = $this->tunnelProcessPaths->getLogFilePath($tunnelId);
|
|
$pidFile = $this->tunnelProcessPaths->getPidFilePath($tunnelId);
|
|
$jsArgs = json_encode([
|
|
'port' => $port,
|
|
'secure' => $secure,
|
|
'keyfile' => $keyFile,
|
|
'logfile' => $logFile,
|
|
], JSON_THROW_ON_ERROR);
|
|
$arg = escapeshellarg($jsArgs);
|
|
$dirEscaped = escapeshellarg($scriptDir);
|
|
$log = escapeshellarg($logFile);
|
|
$fullCmd = "cd $dirEscaped && 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', [
|
|
'tunnel_id' => $tunnelId,
|
|
'store_code' => $tunnel['store_code'] ?? '',
|
|
'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(string $tunnelId): array
|
|
{
|
|
$pidFile = $this->tunnelProcessPaths->getPidFilePath($tunnelId);
|
|
$keyFile = $this->tunnelProcessPaths->getKeyFilePath($tunnelId);
|
|
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', [
|
|
'tunnel_id' => $tunnelId,
|
|
'pid' => $pid,
|
|
]);
|
|
return ['ok' => true];
|
|
} catch (\Throwable $e) {
|
|
return ['ok' => false, 'error' => $e->getMessage()];
|
|
}
|
|
}
|
|
}
|