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 */ 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); } }