getRequest()->getParam('action'); $wantJson = $this->isTunnelAjaxRequest(); if ($action === 'start') { return $this->handleStart($wantJson); } if ($action === 'stop') { return $this->handleStop($wantJson); } $resultPage = $this->resultFactory->create(ResultFactory::TYPE_PAGE); $resultPage->setActiveMenu('MageSail_Magesail::tunnel'); $resultPage->getConfig()->getTitle()->prepend(__('Holesail Tunnel')); return $resultPage; } private function isTunnelAjaxRequest(): bool { return strtolower((string) $this->getRequest()->getHeader('X-Requested-With')) === 'xmlhttprequest' || (string) $this->getRequest()->getParam('ajax') === '1'; } private function jsonPayload(bool $ok, string $message = '', array $extra = []) { $json = $this->resultFactory->create(ResultFactory::TYPE_JSON); $json->setData(array_merge([ 'success' => $ok, 'message' => $message, 'status' => $this->tunnelStatus->getStatus(), ], $extra)); return $json; } private function handleStart(bool $wantJson) { if ($this->tunnelStatus->isRunning()) { if ($wantJson) { return $this->jsonPayload(false, (string) __('Tunnel is already running.')); } $this->messageManager->addErrorMessage(__('Tunnel is already running.')); return $this->_redirect('*/*/index'); } $secure = (bool) $this->scopeConfig->getValue('magesail/settings/mode'); $port = filter_var($this->scopeConfig->getValue('magesail/settings/port') ?: 80, FILTER_VALIDATE_INT); if (!$port || !\in_array($port, [80, 443], true)) { if ($wantJson) { return $this->jsonPayload(false, (string) __('Invalid port. Must be 80 or 443.')); } $this->messageManager->addErrorMessage(__('Invalid port. Must be 80 or 443.')); return $this->_redirect('*/*/index'); } $tunnelBaseUrl = trim((string) $this->getRequest()->getParam('tunnel_base_url', '')); if ($tunnelBaseUrl === '') { $msg = (string) __('Tunnel hostname is required (e.g. dev.myshop.local — must match your holesail-browser virtual host).'); if ($wantJson) { return $this->jsonPayload(false, $msg); } $this->messageManager->addErrorMessage($msg); return $this->_redirect('*/*/index'); } $tunnelSecureBaseUrl = trim((string) $this->getRequest()->getParam('tunnel_secure_base_url', '')); $useSecureUrls = $this->isTruthyParam($this->getRequest()->getParam('tunnel_use_secure_urls')); try { $provision = $this->tunnelStoreProvisioner->provision($tunnelBaseUrl, $tunnelSecureBaseUrl, $useSecureUrls); } catch (LocalizedException $e) { $this->magesailLog->add('Tunnel store provision failed: ' . $e->getMessage()); if ($wantJson) { return $this->jsonPayload(false, (string) $e->getMessage()); } $this->messageManager->addErrorMessage($e->getMessage()); return $this->_redirect('*/*/index'); } $result = $this->tunnelManager->start((int) $port, $secure); if (!$result['ok']) { $this->tunnelStoreProvisioner->teardownPersistedState(); } if ($result['ok']) { $this->magesailLog->add(sprintf('Tunnel started PID %s port %s', $result['pid'], $port)); $msg = (string) __( 'Tunnel started (PID: %1). Key will appear once ready.', $result['pid'] ); $nginxReloadMsg = ''; if (empty($provision['nginx_reloaded'])) { $nginxReloadMsg = (string) __( 'The NGINX global map was updated, but Magento could not reload nginx (common when the PHP user cannot signal the nginx master). ' . 'On the server run: sudo systemctl reload nginx, or set Reload wrapper script in Admin under Configuration for the Holesail Tunnel NGINX settings. ' . 'Until nginx reloads, your tunnel hostname may not route to the tunnel store.' ); } if ($wantJson) { $extra = []; if ($nginxReloadMsg !== '') { $extra['nginx_reload_required'] = true; $extra['nginx_reload_message'] = $nginxReloadMsg; } return $this->jsonPayload(true, $msg, $extra); } $this->messageManager->addSuccessMessage($msg); if ($nginxReloadMsg !== '') { $this->messageManager->addWarningMessage($nginxReloadMsg); } return $this->_redirect('*/*/index'); } else { $msg = match ($result['error'] ?? '') { 'already_running' => (string) __('Tunnel is already running.'), 'invalid_port' => (string) __('Invalid port. Must be 80 or 443.'), 'pid_capture_failed' => (string) __('Failed to capture PID: %1', $result['detail'] ?? ''), default => (string) __('Error starting tunnel: %1', $result['error'] ?? 'unknown'), }; $this->magesailLog->add('Start failed: ' . ($result['error'] ?? 'unknown')); if ($wantJson) { return $this->jsonPayload(false, $msg); } $this->messageManager->addErrorMessage($msg); return $this->_redirect('*/*/index'); } } private function handleStop(bool $wantJson) { $stop = $this->tunnelManager->stop(); $stoppedOrIdle = $stop['ok'] || (($stop['error'] ?? '') === 'not_running'); if ($stoppedOrIdle) { $this->tunnelStoreProvisioner->teardownPersistedState(); } if ($stop['ok']) { $this->magesailLog->add('Tunnel stopped'); if ($wantJson) { return $this->jsonPayload(true, (string) __('Tunnel stopped.')); } $this->messageManager->addSuccessMessage(__('Tunnel stopped.')); } else { if (($stop['error'] ?? '') === 'not_running') { if ($wantJson) { return $this->jsonPayload(true, (string) __('No tunnel running.'), ['status' => $this->tunnelStatus->getStatus()]); } $this->messageManager->addNoticeMessage(__('No tunnel running.')); } else { if ($wantJson) { return $this->jsonPayload(false, (string) __('Error stopping tunnel: %1', $stop['error'] ?? '')); } $this->messageManager->addErrorMessage(__('Error stopping tunnel: %1', $stop['error'] ?? '')); } } return $wantJson ? $this->jsonPayload($stop['ok'], '') : $this->_redirect('*/*/index'); } private function isTruthyParam(mixed $value): bool { if ($value === true || $value === 1) { return true; } if ($value === false || $value === null || $value === '') { return false; } $s = strtolower(trim((string) $value)); return \in_array($s, ['1', 'true', 'on', 'yes'], true); } protected function _isAllowed() { return $this->_authorization->isAllowed('MageSail_Magesail::tunnel'); } }