Add built-in service targets for SSH on 127.0.0.1 (ports 22 and 2223) and a
CI / php (push) Successful in 1m1s
CI / php (push) Successful in 1m1s
Custom Port option with ssh_custom_port validation in Admin. List env.php-discovered services first; SSH shortcuts and Custom Port last. Rename the custom option label to "Custom Port" and align tunnel labels/i18n. Update tunnel UI (phtml/JS), EnvServicePortResolver, controller, tests, and docs.
This commit is contained in:
@@ -98,6 +98,11 @@ class Tunnel extends Template
|
||||
return $this->envServicePortResolver->getDiscoveredServices();
|
||||
}
|
||||
|
||||
public function hasEnvDiscoveredServices(): bool
|
||||
{
|
||||
return $this->envServicePortResolver->hasEnvDiscoveredServices();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<array{value: string, label: string}>
|
||||
*/
|
||||
@@ -259,6 +264,7 @@ class Tunnel extends Template
|
||||
'groupsByWebsite' => \is_array($groups) ? $groups : [],
|
||||
'defaultTab' => \count($this->listTunnelsFromRegistry()) === 0 ? 'add' : 'list',
|
||||
'discoveredServices' => $this->envServicePortResolver->getDiscoveredServices(),
|
||||
'sshCustomServiceKey' => EnvServicePortResolver::SERVICE_KEY_SSH_CUSTOM,
|
||||
'translations' => [
|
||||
'copied' => (string) __('Copied!'),
|
||||
'copyFailed' => (string) __('Copy failed'),
|
||||
@@ -288,6 +294,7 @@ class Tunnel extends Template
|
||||
'selectWebsiteGroup' => (string) __('Select website and store group.'),
|
||||
'enterHostname' => (string) __('Enter the tunnel hostname.'),
|
||||
'invalidPort' => (string) __('Enter a valid local port (1–65535).'),
|
||||
'sshPortRequired' => (string) __('Enter a valid SSH port (1–65535).'),
|
||||
'runningPidTpl' => (string) __('Running (PID %1)', 'X'),
|
||||
'clientLabel' => (string) __('Client:'),
|
||||
],
|
||||
|
||||
@@ -318,6 +318,18 @@ class Index extends Action
|
||||
return $this->_redirect('*/*/index');
|
||||
}
|
||||
$resolved = $this->envServicePortResolver->resolveService($serviceKey);
|
||||
if ($resolved === null && $serviceKey === EnvServicePortResolver::SERVICE_KEY_SSH_CUSTOM) {
|
||||
$customPort = (int) $this->getRequest()->getParam('ssh_custom_port', 0);
|
||||
if ($customPort < 1 || $customPort > 65535) {
|
||||
$msg = (string) __('Enter a valid SSH port (1–65535).');
|
||||
if ($wantJson) {
|
||||
return $this->jsonPayload(false, $msg);
|
||||
}
|
||||
$this->messageManager->addErrorMessage($msg);
|
||||
return $this->_redirect('*/*/index');
|
||||
}
|
||||
$resolved = ['host' => '127.0.0.1', 'port' => $customPort];
|
||||
}
|
||||
if ($resolved === null) {
|
||||
$msg = (string) __(
|
||||
'That service is not available anymore. Refresh the page to reload the list from env.php.'
|
||||
@@ -335,6 +347,9 @@ class Index extends Action
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($serviceKey === EnvServicePortResolver::SERVICE_KEY_SSH_CUSTOM) {
|
||||
$serviceLabel = (string) __('Custom Port — 127.0.0.1:%1', $resolved['port']);
|
||||
}
|
||||
|
||||
try {
|
||||
$provision = $this->tunnelStoreProvisioner->provisionServiceTunnel(
|
||||
|
||||
@@ -7,10 +7,17 @@ namespace MageSail\Magesail\Model;
|
||||
use Magento\Framework\App\DeploymentConfig;
|
||||
|
||||
/**
|
||||
* Discovers TCP endpoints from app/etc/env.php (via DeploymentConfig) for service-type Holesail tunnels.
|
||||
* Discovers TCP endpoints from app/etc/env.php (via DeploymentConfig) for service-type Holesail tunnels,
|
||||
* plus built-in SSH shortcuts to 127.0.0.1 (fixed ports or custom port from the Admin request).
|
||||
*/
|
||||
class EnvServicePortResolver
|
||||
{
|
||||
public const SERVICE_KEY_SSH_22 = 'magesail_ssh_22';
|
||||
|
||||
public const SERVICE_KEY_SSH_2223 = 'magesail_ssh_2223';
|
||||
|
||||
public const SERVICE_KEY_SSH_CUSTOM = 'magesail_ssh_custom';
|
||||
|
||||
private const MYSQL_DEFAULT_KEY = 'mysql_default';
|
||||
|
||||
private const SESSION_REDIS_KEY = 'session_redis';
|
||||
@@ -22,10 +29,86 @@ class EnvServicePortResolver
|
||||
) {
|
||||
}
|
||||
|
||||
public static function isSshShortcutServiceKey(string $serviceKey): bool
|
||||
{
|
||||
return \in_array(
|
||||
$serviceKey,
|
||||
[self::SERVICE_KEY_SSH_22, self::SERVICE_KEY_SSH_2223, self::SERVICE_KEY_SSH_CUSTOM],
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<array{service_key: string, label: string, host: string, port: int}>
|
||||
*/
|
||||
public function getDiscoveredServices(): array
|
||||
{
|
||||
return array_merge($this->collectEnvDiscoveredServices(), $this->getSshShortcutDefinitions());
|
||||
}
|
||||
|
||||
/**
|
||||
* True when env.php yields at least one TCP endpoint (excluding built-in SSH shortcuts).
|
||||
*/
|
||||
public function hasEnvDiscoveredServices(): bool
|
||||
{
|
||||
return \count($this->collectEnvDiscoveredServices()) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<array{service_key: string, label: string, host: string, port: int}>
|
||||
*/
|
||||
private function getSshShortcutDefinitions(): array
|
||||
{
|
||||
return [
|
||||
[
|
||||
'service_key' => self::SERVICE_KEY_SSH_22,
|
||||
'label' => 'SSH: 22 (127.0.0.1)',
|
||||
'host' => '127.0.0.1',
|
||||
'port' => 22,
|
||||
],
|
||||
[
|
||||
'service_key' => self::SERVICE_KEY_SSH_2223,
|
||||
'label' => 'SSH: 2223 (127.0.0.1)',
|
||||
'host' => '127.0.0.1',
|
||||
'port' => 2223,
|
||||
],
|
||||
[
|
||||
'service_key' => self::SERVICE_KEY_SSH_CUSTOM,
|
||||
'label' => 'Custom Port',
|
||||
'host' => '127.0.0.1',
|
||||
'port' => 0,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{host: string, port: int}|null
|
||||
*/
|
||||
public function resolveService(string $serviceKey): ?array
|
||||
{
|
||||
if ($serviceKey === self::SERVICE_KEY_SSH_22) {
|
||||
return ['host' => '127.0.0.1', 'port' => 22];
|
||||
}
|
||||
if ($serviceKey === self::SERVICE_KEY_SSH_2223) {
|
||||
return ['host' => '127.0.0.1', 'port' => 2223];
|
||||
}
|
||||
if ($serviceKey === self::SERVICE_KEY_SSH_CUSTOM) {
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach ($this->collectEnvDiscoveredServices() as $row) {
|
||||
if ($row['service_key'] === $serviceKey) {
|
||||
return ['host' => $row['host'], 'port' => $row['port']];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<array{service_key: string, label: string, host: string, port: int}>
|
||||
*/
|
||||
private function collectEnvDiscoveredServices(): array
|
||||
{
|
||||
$out = [];
|
||||
foreach ($this->discoverMysql() as $row) {
|
||||
@@ -50,20 +133,6 @@ class EnvServicePortResolver
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{host: string, port: int}|null
|
||||
*/
|
||||
public function resolveService(string $serviceKey): ?array
|
||||
{
|
||||
foreach ($this->getDiscoveredServices() as $row) {
|
||||
if ($row['service_key'] === $serviceKey) {
|
||||
return ['host' => $row['host'], 'port' => $row['port']];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<array{service_key: string, label: string, host: string, port: int}>
|
||||
*/
|
||||
|
||||
@@ -102,4 +102,57 @@ class EnvServicePortResolverTest extends TestCase
|
||||
]);
|
||||
$this->assertNull($r->resolveService('does_not_exist'));
|
||||
}
|
||||
|
||||
public function testSshShortcutsAreListedAndResolveExceptCustom(): void
|
||||
{
|
||||
$r = $this->resolverWithFlatConfig([]);
|
||||
$keys = [];
|
||||
foreach ($r->getDiscoveredServices() as $s) {
|
||||
$keys[] = $s['service_key'];
|
||||
}
|
||||
$this->assertContains(EnvServicePortResolver::SERVICE_KEY_SSH_22, $keys);
|
||||
$this->assertContains(EnvServicePortResolver::SERVICE_KEY_SSH_2223, $keys);
|
||||
$this->assertContains(EnvServicePortResolver::SERVICE_KEY_SSH_CUSTOM, $keys);
|
||||
$this->assertFalse($r->hasEnvDiscoveredServices());
|
||||
|
||||
$r22 = $r->resolveService(EnvServicePortResolver::SERVICE_KEY_SSH_22);
|
||||
$this->assertSame(['host' => '127.0.0.1', 'port' => 22], $r22);
|
||||
$r2223 = $r->resolveService(EnvServicePortResolver::SERVICE_KEY_SSH_2223);
|
||||
$this->assertSame(['host' => '127.0.0.1', 'port' => 2223], $r2223);
|
||||
$this->assertNull($r->resolveService(EnvServicePortResolver::SERVICE_KEY_SSH_CUSTOM));
|
||||
}
|
||||
|
||||
public function testHasEnvDiscoveredServicesWhenMysqlPresent(): void
|
||||
{
|
||||
$r = $this->resolverWithFlatConfig([
|
||||
'db/connection/default' => ['host' => '127.0.0.1'],
|
||||
]);
|
||||
$this->assertTrue($r->hasEnvDiscoveredServices());
|
||||
}
|
||||
|
||||
public function testIsSshShortcutServiceKeyDetectsBuiltIns(): void
|
||||
{
|
||||
$this->assertTrue(EnvServicePortResolver::isSshShortcutServiceKey(EnvServicePortResolver::SERVICE_KEY_SSH_22));
|
||||
$this->assertFalse(EnvServicePortResolver::isSshShortcutServiceKey('mysql_default'));
|
||||
}
|
||||
|
||||
public function testSshShortcutsAreLastInDiscoveredListAfterEnvServices(): void
|
||||
{
|
||||
$r = $this->resolverWithFlatConfig([
|
||||
'db/connection/default' => ['host' => '127.0.0.1'],
|
||||
]);
|
||||
$keys = array_column($r->getDiscoveredServices(), 'service_key');
|
||||
$iMysql = array_search('mysql_default', $keys, true);
|
||||
$i22 = array_search(EnvServicePortResolver::SERVICE_KEY_SSH_22, $keys, true);
|
||||
$i2223 = array_search(EnvServicePortResolver::SERVICE_KEY_SSH_2223, $keys, true);
|
||||
$iCustom = array_search(EnvServicePortResolver::SERVICE_KEY_SSH_CUSTOM, $keys, true);
|
||||
$this->assertNotFalse($iMysql);
|
||||
$this->assertNotFalse($i22);
|
||||
$this->assertNotFalse($i2223);
|
||||
$this->assertNotFalse($iCustom);
|
||||
$this->assertLessThan($i22, $iMysql);
|
||||
$this->assertLessThan($i2223, $i22);
|
||||
$this->assertLessThan($iCustom, $i2223);
|
||||
$this->assertSame(\count($keys) - 1, $iCustom);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,3 +27,10 @@
|
||||
"Error stopping tunnel: %1","Error stopping tunnel: %1"
|
||||
"Holesail Tunnel","Holesail Tunnel"
|
||||
"Configuration","Configuration"
|
||||
"Enter a valid SSH port (1–65535).","Enter a valid SSH port (1–65535)."
|
||||
"Custom Port — 127.0.0.1:%1","Custom Port — 127.0.0.1:%1"
|
||||
"Custom Port","Custom Port"
|
||||
"Forwards TCP to 127.0.0.1 on this port (e.g. alternate sshd).","Forwards TCP to 127.0.0.1 on this port (e.g. alternate sshd)."
|
||||
"No TCP services were found in env.php. SSH presets (127.0.0.1) are still available; otherwise check db, cache, session, queue, and http_cache_hosts.","No TCP services were found in env.php. SSH presets (127.0.0.1) are still available; otherwise check db, cache, session, queue, and http_cache_hosts."
|
||||
"Website: Magento store + hostname routing. Service: TCP to env.php endpoints or built-in SSH presets on 127.0.0.1 (no store view).","Website: Magento store + hostname routing. Service: TCP to env.php endpoints or built-in SSH presets on 127.0.0.1 (no store view)."
|
||||
"Website tunnels use a dedicated store view (e.g. mgtun_…) and optional NGINX map. Service tunnels forward TCP to SSH on 127.0.0.1 (ports 22, 2223, or custom) and/or hosts and ports from env.php (MySQL, Redis, AMQP, etc.)—no store view.","Website tunnels use a dedicated store view (e.g. mgtun_…) and optional NGINX map. Service tunnels forward TCP to SSH on 127.0.0.1 (ports 22, 2223, or custom) and/or hosts and ports from env.php (MySQL, Redis, AMQP, etc.)—no store view."
|
||||
|
||||
|
@@ -13,7 +13,7 @@ $nginxReloadHint = (string) __(
|
||||
);
|
||||
$defaultMagesailTab = \count($registry) === 0 ? 'add' : 'list';
|
||||
$discoveredServices = $block->getDiscoveredServices();
|
||||
$hasDiscoveredServices = \count($discoveredServices) > 0;
|
||||
$hasEnvDiscoveredServices = $block->hasEnvDiscoveredServices();
|
||||
?>
|
||||
<div class="magesail-tunnel" id="magesail-root">
|
||||
<div class="page-title-wrapper">
|
||||
@@ -60,7 +60,7 @@ $hasDiscoveredServices = \count($discoveredServices) > 0;
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="message message-notice notice" style="margin-bottom:1rem">
|
||||
<?= $block->escapeHtml(__('Website tunnels use a dedicated store view (e.g. mgtun_…) and optional NGINX map. Service tunnels forward TCP to hosts and ports from env.php (MySQL, Redis, AMQP, etc.)—no store view.')) ?>
|
||||
<?= $block->escapeHtml(__('Website tunnels use a dedicated store view (e.g. mgtun_…) and optional NGINX map. Service tunnels forward TCP to SSH on 127.0.0.1 (ports 22, 2223, or custom) and/or hosts and ports from env.php (MySQL, Redis, AMQP, etc.)—no store view.')) ?>
|
||||
</div>
|
||||
|
||||
<table class="data-grid admin__table-primary" id="magesail-tunnel-table">
|
||||
@@ -230,7 +230,7 @@ $hasDiscoveredServices = \count($discoveredServices) > 0;
|
||||
<option value="service"><?= $block->escapeHtml(__('Service')) ?></option>
|
||||
</select>
|
||||
</div>
|
||||
<p class="admin__field-note"><?= $block->escapeHtml(__('Website: Magento store + hostname routing. Service: TCP to a port from env.php (no store view).')) ?></p>
|
||||
<p class="admin__field-note"><?= $block->escapeHtml(__('Website: Magento store + hostname routing. Service: TCP to env.php endpoints or built-in SSH presets on 127.0.0.1 (no store view).')) ?></p>
|
||||
</div>
|
||||
<div id="magesail-add-website-fields">
|
||||
<div class="admin__field">
|
||||
@@ -309,19 +309,33 @@ $hasDiscoveredServices = \count($discoveredServices) > 0;
|
||||
<div class="admin__field _required">
|
||||
<label class="admin__field-label" for="magesail-service-key"><span><?= $block->escapeHtml(__('Service to expose')) ?></span></label>
|
||||
<div class="admin__field-control">
|
||||
<select id="magesail-service-key" class="admin__control-select"<?= $hasDiscoveredServices ? '' : ' disabled="disabled"' ?>>
|
||||
<select id="magesail-service-key" class="admin__control-select">
|
||||
<option value=""><?= $block->escapeHtml(__('— Select a service —')) ?></option>
|
||||
<?php foreach ($discoveredServices as $svc): ?>
|
||||
<option value="<?= $block->escapeHtmlAttr($svc['service_key']) ?>"><?= $block->escapeHtml($svc['label']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<?php if (!$hasDiscoveredServices): ?>
|
||||
<p class="admin__field-note message message-warning warning" id="magesail-no-services-msg">
|
||||
<?= $block->escapeHtml(__('No TCP services were found in env.php. Check db, cache, session, queue, and http_cache_hosts.')) ?>
|
||||
<?php if (!$hasEnvDiscoveredServices): ?>
|
||||
<p class="admin__field-note message message-notice notice" id="magesail-no-services-msg">
|
||||
<?= $block->escapeHtml(__('No TCP services were found in env.php. SSH presets (127.0.0.1) are still available; otherwise check db, cache, session, queue, and http_cache_hosts.')) ?>
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div id="magesail-ssh-custom-port-wrap" class="admin__field _required" hidden="hidden">
|
||||
<label class="admin__field-label" for="magesail-ssh-custom-port"><span><?= $block->escapeHtml(__('Custom Port')) ?></span></label>
|
||||
<div class="admin__field-control">
|
||||
<input type="number"
|
||||
id="magesail-ssh-custom-port"
|
||||
class="admin__control-text"
|
||||
min="1"
|
||||
max="65535"
|
||||
autocomplete="off"
|
||||
placeholder="<?= $block->escapeHtmlAttr(__('Port')) ?>"
|
||||
style="max-width:8rem;"/>
|
||||
<p class="admin__field-note"><?= $block->escapeHtml(__('Forwards TCP to 127.0.0.1 on this port (e.g. alternate sshd).')) ?></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="admin__field admin__field-option">
|
||||
<input type="checkbox" id="magesail-tunnel-holesail-secure" class="admin__control-checkbox" <?= $defaultHolesail === '1' ? 'checked="checked"' : '' ?>/>
|
||||
|
||||
@@ -96,12 +96,27 @@ define(['jquery', 'MageSail_Magesail/js/magesail-clipboard', 'domReady!'], funct
|
||||
});
|
||||
|
||||
var discoveredCount = (config.discoveredServices || []).length;
|
||||
var sshCustomServiceKey = config.sshCustomServiceKey || '';
|
||||
|
||||
function toggleSshCustomPort() {
|
||||
var $wrap = $('#magesail-ssh-custom-port-wrap');
|
||||
if (!$wrap.length) {
|
||||
return;
|
||||
}
|
||||
var isCustom = sshCustomServiceKey !== '' && $('#magesail-service-key').val() === sshCustomServiceKey;
|
||||
if (isCustom) {
|
||||
$wrap.removeAttr('hidden');
|
||||
} else {
|
||||
$wrap.attr('hidden', 'hidden');
|
||||
}
|
||||
}
|
||||
|
||||
function toggleAddTunnelType() {
|
||||
var v = $('#magesail-tunnel-add-type').val();
|
||||
if (v === 'service') {
|
||||
$('#magesail-add-website-fields').attr('hidden', 'hidden');
|
||||
$('#magesail-add-service-fields').removeAttr('hidden');
|
||||
toggleSshCustomPort();
|
||||
} else {
|
||||
$('#magesail-add-website-fields').removeAttr('hidden');
|
||||
$('#magesail-add-service-fields').attr('hidden', 'hidden');
|
||||
@@ -119,7 +134,9 @@ define(['jquery', 'MageSail_Magesail/js/magesail-clipboard', 'domReady!'], funct
|
||||
}
|
||||
|
||||
$('#magesail-tunnel-add-type').on('change', toggleAddTunnelType);
|
||||
$('#magesail-service-key').on('change', toggleSshCustomPort);
|
||||
toggleAddTunnelType();
|
||||
toggleSshCustomPort();
|
||||
|
||||
var $busyOverlay = $('#magesail-busy-overlay');
|
||||
|
||||
@@ -435,6 +452,14 @@ define(['jquery', 'MageSail_Magesail/js/magesail-clipboard', 'domReady!'], funct
|
||||
flash(t.selectService || '', true);
|
||||
return;
|
||||
}
|
||||
if (sshCustomServiceKey !== '' && sk === sshCustomServiceKey) {
|
||||
var sp = parseInt($('#magesail-ssh-custom-port').val(), 10);
|
||||
if (!sp || sp < 1 || sp > 65535) {
|
||||
btn.prop('disabled', false);
|
||||
flash(t.sshPortRequired || '', true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
postData = {
|
||||
form_key: formKey,
|
||||
action: 'start',
|
||||
@@ -444,6 +469,9 @@ define(['jquery', 'MageSail_Magesail/js/magesail-clipboard', 'domReady!'], funct
|
||||
service_key: sk,
|
||||
tunnel_holesail_secure: hs
|
||||
};
|
||||
if (sshCustomServiceKey !== '' && sk === sshCustomServiceKey) {
|
||||
postData.ssh_custom_port = String(parseInt($('#magesail-ssh-custom-port').val(), 10));
|
||||
}
|
||||
} else {
|
||||
var baseUrl = ($('#magesail-tunnel-base-url').val() || '').trim();
|
||||
var wid = $('#magesail-website').val();
|
||||
|
||||
@@ -15,7 +15,7 @@ MageSail is a Magento 2 extension that integrates **[Holesail](https://holesail.
|
||||
- **Secure tunneling** — Private (authenticated) vs public Holesail modes, mapped to **Secure Tunnel** Yes/No in Admin.
|
||||
- **Admin lifecycle** — Create, start, stop, and remove tunnels under **MageSail → Holesail Tunnel → Manage Tunnel**; status polling and per-tunnel log tail via AJAX.
|
||||
- **Website tunnels** — **Type: Website** under **Add tunnel**: each tunnel gets a **generated store code** (e.g. `mgtun_…`) on the **website / store group** you choose, with base URLs you supply. **Remove** tears down the store and NGINX map line; **Stop** only stops the Node process (with safety rules if that store is the website default on delete).
|
||||
- **Service tunnels** — **Type: Service**: TCP forward to endpoints **discovered from env** via `DeploymentConfig` (no fixed service list). No store view, no NGINX map row; **Remove** drops the registry row and PID/key only. See [docs/admin-ui.md](docs/admin-ui.md).
|
||||
- **Service tunnels** — **Type: Service**: TCP forward to **SSH on 127.0.0.1** (presets **22**, **2223**, or a **custom** port) and/or endpoints **discovered from env** via `DeploymentConfig`. No store view, no NGINX map row; **Remove** drops the registry row and PID/key only. See [docs/admin-ui.md](docs/admin-ui.md).
|
||||
- **NGINX global map** — Optional append/remove of `map $http_host $MAGE_RUN_CODE` / `$MAGE_RUN_TYPE` lines for **website** tunnels only, plus `nginx -t` and reload (or wrapper script). Admin screen: **MageSail → Holesail Tunnel → NGINX Global Map**.
|
||||
- **Storefront / admin routing** — For **website** tunnels, plugins detect HTTP traffic via header **`X-MageSail-Store-Code`**, query **`magesail_store`**, `___store`, or **hostname** vs persisted base URLs (**`HTTP_HOST`** plus **`X-Forwarded-Host` / `HTTP_X_FORWARDED_HOST`** when the client host differs from what PHP sees); adjust `MAGE_RUN_*`, backend front name resolution, generated base URLs, and **skip redirect-to-base** where appropriate. **Service** tunnels do not participate in Magento HTTP routing.
|
||||
- **Tunnel storefront content** — Each new **website** tunnel store copies **CMS home / no-route / no-cookies / front** paths and **theme** from that website’s **default** store view, and mirrors **`cms_page_store` / `cms_block_store`** so pages and blocks assigned to the default storefront appear on the tunnel (see [docs/tunnel-store-and-multistore.md](docs/tunnel-store-and-multistore.md)).
|
||||
|
||||
+2
-2
@@ -18,7 +18,7 @@ The same URL shows two in-page tabs: **Tunnels list** (grid, log tails, start/st
|
||||
|
||||
- Lists all tunnels from [`TunnelRegistry`](../Magesail/Model/TunnelRegistry.php) (`var/magesail_tunnels.json`): **Type** (**Website** vs **Service**), label, **store code** (em dash for service tunnels), **local port**, host/target, run/stop state, Holesail key (when running), and actions.
|
||||
- **Website** tunnels — dedicated store view, base URLs, optional NGINX global map (same as before).
|
||||
- **Service** tunnels — TCP forward to an endpoint discovered from `app/etc/env.php` by [`EnvServicePortResolver`](../Magesail/Model/EnvServicePortResolver.php): MySQL (`db/connection/default`, default port **3306** if omitted), each Redis `cache/frontend/*` backend, session Redis when `session.save` is `redis`, `queue/amqp`, each `http_cache_hosts` entry, and optional Elasticsearch/OpenSearch under `catalog/search` when host and port are set. No store view and no NGINX map row. The Node script forwards to `service_target_host:local_port` (see [`start-server.js`](../Magesail/scripts/start-server.js) `forwardHost`). **Security:** exposing databases, Redis, or AMQP over Holesail is sensitive—use **Holesail secure tunnel**, restrict Admin ACL, and use normal client credentials from env (passwords are not shown in the Admin UI).
|
||||
- **Service** tunnels — TCP forward to an endpoint from [`EnvServicePortResolver`](../Magesail/Model/EnvServicePortResolver.php): **built-in SSH** to **`127.0.0.1`** on port **22**, **2223**, or a **custom** port you enter; plus endpoints discovered from `app/etc/env.php` (MySQL `db/connection/default`, default port **3306** if omitted, each Redis `cache/frontend/*` backend, session Redis when `session.save` is `redis`, `queue/amqp`, each `http_cache_hosts` entry, and optional Elasticsearch/OpenSearch under `catalog/search` when host and port are set). No store view and no NGINX map row. The Node script forwards to `service_target_host:local_port` (see [`start-server.js`](../Magesail/scripts/start-server.js) `forwardHost`). **Security:** exposing SSH, databases, Redis, or AMQP over Holesail is sensitive—use **Holesail secure tunnel**, restrict Admin ACL, and use normal client credentials from env (passwords are not shown in the Admin UI).
|
||||
- **Start** — starts the Node process for that tunnel only (uses saved port and Holesail secure flag). No re-provisioning.
|
||||
- **Stop** — stops the Node process and removes that tunnel’s PID/key files; **registry row, store view, and NGINX map line stay** so you can start again.
|
||||
- **Remove** — **Website**: stops the process if needed, removes the NGINX map entry, deletes the store view when safe, and removes the tunnel from the registry. **Service**: removes registry row and PID/key files only (no store).
|
||||
@@ -28,7 +28,7 @@ The same URL shows two in-page tabs: **Tunnels list** (grid, log tails, start/st
|
||||
|
||||
**Add tunnel** (Add a tunnel tab — new tunnel)
|
||||
|
||||
- **Type** — **Website** (default): current Magento tunnel form. **Service**: choose one discovered TCP service (dropdown built from env); optional label; **Holesail secure tunnel** applies to both. If nothing is discoverable in env, the Service flow cannot be submitted.
|
||||
- **Type** — **Website** (default): current Magento tunnel form. **Service**: choose one TCP target (env-discovered services first, then **SSH: 22**, **SSH: 2223**, and **Custom Port** last for **127.0.0.1**); optional label; **Holesail secure tunnel** applies to both. The Service dropdown always includes those SSH shortcuts even when env.php has no other endpoints.
|
||||
- **Website** and **Store group** — required when Type is **Website**; the new dedicated store view is created under that scope.
|
||||
- **Local port** — preset **443**, **80**, or **8080**, or **Custom…** with any port **1–65535**. Multiple tunnels may use the same port in configuration; whether more than one Node process can bind depends on the OS (see field note in Admin).
|
||||
- **Tunnel hostname (unsecure)** — required; hostname only (e.g. `dev.myshop.local`); stored as `http://host/`. Must match the browser / Holesail client vhost.
|
||||
|
||||
+2
-2
@@ -3,7 +3,7 @@
|
||||
MageSail exposes a local Magento instance (or **TCP services** from env) through **Holesail** P2P tunnels. Admins create **multiple tunnels** from **MageSail → Holesail Tunnel → Manage Tunnel**, each with its own **local port** (presets 443 / 80 / 8080 or custom).
|
||||
|
||||
- **Website** tunnels — **Dedicated store view** (generated code, e.g. `mgtun_…`), base URLs, and optional **NGINX `map`** lines so tunneled hostnames resolve to the correct store without manual multistore edits.
|
||||
- **Service** tunnels — Forward to backends **discovered from `app/etc/env.php`** via [`EnvServicePortResolver`](../Magesail/Model/EnvServicePortResolver.php); **no** store view and **no** NGINX map updates.
|
||||
- **Service** tunnels — Forward TCP to **SSH on `127.0.0.1`** (ports **22**, **2223**, or **custom**) and/or backends **discovered from `app/etc/env.php`** via [`EnvServicePortResolver`](../Magesail/Model/EnvServicePortResolver.php); **no** store view and **no** NGINX map updates.
|
||||
|
||||
## Components
|
||||
|
||||
@@ -15,7 +15,7 @@ MageSail exposes a local Magento instance (or **TCP services** from env) through
|
||||
1. Admin configures **MageSail → Holesail Tunnel → Configuration** (default secure mode, scripts path, Node binary, monitor cron schedule, auto-restart, NGINX paths).
|
||||
2. Under **Manage Tunnel → Add tunnel**, Admin chooses **Type**:
|
||||
- **Website** — website/group, port, hostnames, options; the module creates a **new store view**, may append **hostname → store code** to the configured **global map**, writes **`var/magesail_tunnels.json`**, starts Node.
|
||||
- **Service** — selected `service_key` from env discovery; writes **registry** only (with resolved host/port), starts Node; **no** store, **no** map.
|
||||
- **Service** — selected `service_key` (SSH shortcuts or env discovery); writes **registry** only (with resolved host/port), starts Node; **no** store, **no** map.
|
||||
3. For **website** tunnels, incoming HTTP(S) requests can bind to the tunnel store via **header**, **query**, **`___store`**, **hostname match** against persisted base URLs in the registry (using **`HTTP_HOST`** and, when present, **`X-Forwarded-Host` / `HTTP_X_FORWARDED_HOST`**), or **NGINX `MAGE_RUN_*`** when the map is active. **Non-existent** store codes from nginx or bookmarks are ignored so Magento can fall back (see [`StoreFromMageSailHeaderPlugin`](../Magesail/Plugin/App/Request/StoreFromMageSailHeaderPlugin.php)). **Service** tunnels are plain TCP; Magento HTTP routing does not apply. New **website** tunnels copy **CMS + theme defaults** from the website’s **default** store view and mirror **`cms_page_store` / `cms_block_store`** assignments so the tunnel storefront matches that website’s main demo (see [tunnel-store-and-multistore.md](tunnel-store-and-multistore.md)).
|
||||
4. Plugins adjust **admin front name resolution**, **store base URLs** for links, and **skip redirect-to-base** for **website** tunnel traffic so local/proxy URLs stay usable.
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ Module namespace: **`MageSail\Magesail`**. Paths are relative to [`Magesail/`](.
|
||||
| `TunnelStatus` | `Model/TunnelStatus.php` | PID/key/log paths per tunnel; process alive checks; `cleanupStaleStateForTunnel` (may call full `teardownTunnel` when auto-restart is off). |
|
||||
| `TunnelStoreProvisioner` | `Model/TunnelStoreProvisioner.php` | Provision/teardown **website** tunnel store views; `provisionServiceTunnel` for **service** rows (registry only); scoped base URLs; optional **redirect-to-base** disable per config; after create, copies **web/default** CMS paths and **design/theme/theme_id** from the website **default** store and **mirrors** `cms_page_store` / `cms_block_store` rows (`ResourceConnection`); NGINX map updates for website only; `teardownTunnel` branches on `tunnel_type`. |
|
||||
| `TunnelRegistry` | `Model/TunnelRegistry.php` | Read/write `var/magesail_tunnels.json`; migrate legacy `magesail_tunnel_store.json`; normalize `tunnel_type`, `service_*`; hostname lookup skips **service** rows. |
|
||||
| `EnvServicePortResolver` | `Model/EnvServicePortResolver.php` | Discover TCP endpoints from `DeploymentConfig` / `env.php`; stable `service_key`; `resolveService()` for Admin start validation. |
|
||||
| `EnvServicePortResolver` | `Model/EnvServicePortResolver.php` | Discover TCP endpoints from `DeploymentConfig` / `env.php` plus built-in **SSH** shortcuts (`magesail_ssh_22`, `magesail_ssh_2223`, `magesail_ssh_custom` — custom port supplied on create in [`Tunnel/Index`](../Magesail/Controller/Adminhtml/Tunnel/Index.php)); `hasEnvDiscoveredServices()`; `resolveService()` for Admin start validation. |
|
||||
| `TunnelProcessPaths` | `Model/TunnelProcessPaths.php` | Paths for `var/magesail_tunnels/{id}.pid`, `.key`, and per-tunnel logs. |
|
||||
| `TunnelStoreState` | `Model/TunnelStoreState.php` | Legacy read of `var/magesail_tunnel_store.json` (migration only). |
|
||||
| `TunnelRequestDetector` | `Model/TunnelRequestDetector.php` | Resolve tunnel store code from request (query, header, URI, hostname); returns `null` if the code does not exist in `StoreRepository`. |
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
| | `npm install` not run in `scripts/` | Run `npm install` in the configured **Scripts directory**. |
|
||||
| **Invalid port** | Out of range or empty custom port | Use **1–65535**. In **Manage Tunnel**, pick preset **443**, **80**, **8080**, or **Custom** and enter a valid port. |
|
||||
| **Tunnel hostname is required** | Empty unsecure field on start | For **Type: Website**, fill **Tunnel hostname (unsecure)** (e.g. `dev.myshop.local`) to match your browser/proxy host. |
|
||||
| **No TCP services** / Service submit disabled | Nothing discoverable in `env.php` | **Type: Service** needs host+port in `db`, `cache/frontend`, `session/redis`, `queue/amqp`, `http_cache_hosts`, or search config as implemented in [`EnvServicePortResolver`](../Magesail/Model/EnvServicePortResolver.php). Fix or complete env; refresh Admin. |
|
||||
| **No TCP services** in env | Nothing discoverable in `env.php` | **Type: Service** still offers **SSH** to `127.0.0.1` (ports 22, 2223, or custom). For MySQL/Redis/etc., add host+port in `db`, `cache/frontend`, `session/redis`, `queue/amqp`, `http_cache_hosts`, or search config as implemented in [`EnvServicePortResolver`](../Magesail/Model/EnvServicePortResolver.php); refresh Admin. |
|
||||
| Unknown or invalid **service** | Stale `service_key` after env edit | Refresh **Manage Tunnel** and pick a service from the current list; env must still resolve the key at start time. |
|
||||
|
||||
## PID / process
|
||||
|
||||
Reference in New Issue
Block a user