@@ -15,7 +15,7 @@ That is correct for production, but painful for tunnels: every request might bou
|
||||
|
||||
## What MageSail does (website tunnels)
|
||||
|
||||
Plugins (see [docs/request-routing-and-plugins.md](../../docs/request-routing-and-plugins.md)) run when tunnel traffic is detected (header `X-MageSail-Store-Code`, query `magesail_store`, hostname match against **website** tunnel base URLs in the registry, etc.):
|
||||
Plugins (see [docs/request-routing-and-plugins.md](../../docs/request-routing-and-plugins.md)) run when tunnel traffic is detected (header `X-MageSail-Store-Code`, query `magesail_store`, hostname match against **website** tunnel base URLs in the registry — including **`X-Forwarded-Host` / `HTTP_X_FORWARDED_HOST`** when `HTTP_HOST` is an internal listener address, etc.):
|
||||
|
||||
- **Skip / relax base URL redirect** for detected tunnel requests so the tunnel host or local listener stays usable.
|
||||
- **Rewrite generated base URLs** in links where appropriate so HTML still points at the tunnel host.
|
||||
|
||||
@@ -7,6 +7,7 @@ $nginxHttpsUpstream = <<<'NGINX_HTTPS'
|
||||
# Replace TUNNEL_HOST with your tunnel hostname (e.g. dev.myshop.local).
|
||||
# Replace LOCAL_PORT with the tunnel local port from Manage Tunnel (e.g. 443).
|
||||
# If Host matching alone does not bind the tunnel store, uncomment X-MageSail-Store-Code.
|
||||
# If PHP sees Host 127.0.0.1, X-Forwarded-Host lets MageSail match the tunnel hostname.
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
@@ -16,6 +17,7 @@ server {
|
||||
proxy_ssl_server_name on;
|
||||
proxy_ssl_verify off; # dev only when using self-signed localhost certs
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-Host $host;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
# proxy_set_header X-MageSail-Store-Code "STORE_CODE";
|
||||
@@ -32,6 +34,7 @@ server {
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:LOCAL_PORT;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-Host $host;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
# proxy_set_header X-MageSail-Store-Code "STORE_CODE";
|
||||
@@ -49,6 +52,7 @@ $apacheVhost = <<<'APACHE'
|
||||
ProxyPass / http://127.0.0.1:LOCAL_PORT/
|
||||
ProxyPassReverse / http://127.0.0.1:LOCAL_PORT/
|
||||
RequestHeader set X-Forwarded-Proto "http"
|
||||
RequestHeader set X-Forwarded-Host "TUNNEL_HOST"
|
||||
# RequestHeader set X-MageSail-Store-Code "STORE_CODE"
|
||||
</VirtualHost>
|
||||
APACHE;
|
||||
@@ -64,6 +68,11 @@ curl -sk https://127.0.0.1:LOCAL_PORT/ \
|
||||
# Or use query (also honored by MageSail):
|
||||
curl -sk "https://127.0.0.1:LOCAL_PORT/?magesail_store=STORE_CODE" \
|
||||
-H "Host: TUNNEL_HOST"
|
||||
|
||||
# When Host must be 127.0.0.1 but the tunnel hostname should still resolve the store:
|
||||
curl -sk https://127.0.0.1:LOCAL_PORT/ \
|
||||
-H "Host: 127.0.0.1" \
|
||||
-H "X-Forwarded-Host: TUNNEL_HOST"
|
||||
CURL;
|
||||
?>
|
||||
<div class="magesail-howto">
|
||||
@@ -89,7 +98,7 @@ CURL;
|
||||
<ol class="magesail-howto-list">
|
||||
<li><?= $block->escapeHtml(__('Open Manage Tunnel, use the Add a tunnel tab, and choose Website, Store group, Local port, and Tunnel hostname (and optional secure hostname).')) ?></li>
|
||||
</ol>
|
||||
<p><?= $block->escapeHtml(__('After Create and start tunnel, note the tunnel’s store code (e.g. mgtun_…) and local port (e.g. 443, 80, 8080). Point your Holesail client at 127.0.0.1 and that port; you may need routing hints (header, query, or NGINX map) so Magento uses the tunnel store—see Manage Tunnel help text and the project docs folder.')) ?></p>
|
||||
<p><?= $block->escapeHtml(__('After Create and start tunnel, note the tunnel’s store code (e.g. mgtun_…) and local port (e.g. 443, 80, 8080). The new store copies CMS and theme settings from that website’s default store view so the tunnel storefront matches your main site on that website. Point your Holesail client at 127.0.0.1 and that port; you may need routing hints (header, query, X-Forwarded-Host, or NGINX map) so Magento uses the tunnel store—see Manage Tunnel help text and the project docs folder.')) ?></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -98,7 +107,7 @@ CURL;
|
||||
<span class="title"><?= $block->escapeHtml(__('Virtual host / reverse proxy examples')) ?></span>
|
||||
</div>
|
||||
<div class="admin__page-section-content">
|
||||
<p><?= $block->escapeHtml(__('Placeholders: TUNNEL_HOST = hostname from Manage Tunnel, LOCAL_PORT = that tunnel’s local port, STORE_CODE = store code from the tunnel row. Magento can resolve the tunnel store from matching Host and/or X-MageSail-Store-Code (or magesail_store / ___store query); see request-routing-and-plugins.md in the repo.')) ?></p>
|
||||
<p><?= $block->escapeHtml(__('Placeholders: TUNNEL_HOST = hostname from Manage Tunnel, LOCAL_PORT = that tunnel’s local port, STORE_CODE = store code from the tunnel row. Magento can resolve the tunnel store from matching Host, X-Forwarded-Host (when Host is an internal address), X-MageSail-Store-Code, or magesail_store / ___store query; see request-routing-and-plugins.md in the repo.')) ?></p>
|
||||
|
||||
<h3><?= $block->escapeHtml(__('NGINX (reverse proxy to HTTPS on localhost:443)')) ?></h3>
|
||||
<div class="magesail-codeblock">
|
||||
@@ -214,7 +223,7 @@ CURL;
|
||||
<span class="title"><?= $block->escapeHtml(__('Hostname, NGINX map, and multistore')) ?></span>
|
||||
</div>
|
||||
<div class="admin__page-section-content">
|
||||
<p><?= $block->escapeHtml(__('Tunnel hostname fields must match the host the browser (or proxy) sends. If you use a global NGINX map (MageSail → NGINX Global Map), hostnames there should stay in sync with the tunnel store; reload nginx after map changes when prompted.')) ?></p>
|
||||
<p><?= $block->escapeHtml(__('Tunnel hostname fields must match the host the browser (or proxy) sends. If PHP receives Host 127.0.0.1 or another internal name, forward the public tunnel hostname with X-Forwarded-Host. If you use a global NGINX map (MageSail → NGINX Global Map), hostnames there should stay in sync with the tunnel store; reload nginx after map changes when prompted.')) ?></p>
|
||||
<p><?= $block->escapeHtml(__('More detail: see tunnel-store-and-multistore.md and nginx-global-map.md in the project docs folder in the repository.')) ?></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -17,7 +17,8 @@ MageSail is a Magento 2 extension that integrates **[Holesail](https://holesail.
|
||||
- **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).
|
||||
- **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; 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.
|
||||
- **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)).
|
||||
- **PID and logs** — Per tunnel: `var/magesail_tunnels/{id}.pid`, `{id}.key`, `var/log/magesail_tunnel_{id}.log`; optional DB rows in **`magesail_logs`**.
|
||||
- **Cron** — Daily job checks each tunnel’s PID file. If the process is dead and **Auto-restart** is **No**, it **removes that tunnel** — same end state as **Remove**: for **website** tunnels that means store (when allowed), NGINX map line, registry, PID/key; for **service** tunnels, registry and PID/key only. If **Auto-restart** is **Yes**, it clears stale PID/key and starts Node again (saved port and secure mode).
|
||||
|
||||
@@ -130,7 +131,7 @@ Used when you manage `MAGE_RUN_CODE` / `MAGE_RUN_TYPE` via a shared **global map
|
||||
- **Service** — Pick a **Service** from the dropdown (endpoints discovered from `app/etc/env.php`); optional label; **Holesail secure tunnel** as needed. Port and target host come from env (no store group or tunnel hostnames). If nothing is discoverable, submit is disabled.
|
||||
3. Click **Create and start tunnel**.
|
||||
|
||||
For **Website**, the module creates a **new store view** with a generated code (e.g. `mgtun_…`), persists **`var/magesail_tunnels.json`**, may update the NGINX map, then starts Node. For **Service**, it only writes the registry row (plus `service_key`, target host, `local_port`) and starts Node — **no** store, **no** map line.
|
||||
For **Website**, the module creates a **new store view** with a generated code (e.g. `mgtun_…`), sets scoped base URLs, copies **CMS + theme** defaults from the website **default** store and mirrors CMS page/block store assignments, persists **`var/magesail_tunnels.json`**, may update the NGINX map, then starts Node. For **Service**, it only writes the registry row (plus `service_key`, target host, `local_port`) and starts Node — **no** store, **no** map line.
|
||||
|
||||
PID and key live under **`var/magesail_tunnels/{id}.pid`** and **`.key`**. The UI polls **`magesail/tunnel/status`** (optionally with `tunnel_id`) until the key appears.
|
||||
|
||||
@@ -146,7 +147,7 @@ For **website** tunnels, so Magento resolves the tunnel store, your proxy or bro
|
||||
- Query: **`magesail_store=`** + store code
|
||||
- Or native: **`___store=`** + store code
|
||||
|
||||
If the request host matches the provisioned tunnel base URL host, detection can work without those hints. Details: [docs/request-routing-and-plugins.md](docs/request-routing-and-plugins.md).
|
||||
If the request host matches the provisioned tunnel base URL host, detection can work without those hints. When PHP sees an internal **`Host`** (e.g. `127.0.0.1`) but the user browses a tunnel hostname, configure your proxy to send **`X-Forwarded-Host`** with that hostname. Details: [docs/request-routing-and-plugins.md](docs/request-routing-and-plugins.md).
|
||||
|
||||
### Sharing access and making connections
|
||||
|
||||
@@ -193,6 +194,8 @@ Short table — expanded in [docs/troubleshooting.md](docs/troubleshooting.md).
|
||||
| Invalid port | Use a **local port** between 1 and 65535 (presets or custom). If two tunnels share a port, the second **Start** may fail unless your OS/stack allows multiple binds. |
|
||||
| Stale PID | Delete `var/magesail_tunnels/<id>.pid` manually or use **Start** after a clean stop. If you **wait for daily cron** with **Auto-restart** = **No**, the module may **remove the whole tunnel** (not only the PID file). Check for zombie `start-server.js`. |
|
||||
| 301 to production URL | Tunnel plugins + NGINX map + reload; see [docs/tunnel-store-and-multistore.md](docs/tunnel-store-and-multistore.md). |
|
||||
| **504** / Gateway Timeout on tunnel | Often slow or failing **catalog image** resize: ensure **`pub/media/catalog/product/cache`** exists and is writable by the PHP user; check **`var/log/exception.log`**. Increase proxy **`fastcgi_read_timeout`** if first-hit generation is slow. |
|
||||
| **Too few arguments** / DI errors after upgrade | Run **`php bin/magento setup:di:compile`** and flush caches after pulling MageSail changes that change constructors. |
|
||||
| Reload / map permission errors | Map file writable by PHP user; wrapper + sudoers — [Magesail/scripts/README-NGINX-RELOAD.md](Magesail/scripts/README-NGINX-RELOAD.md). |
|
||||
| `start-server.js` / `node_modules` errors on start | **Scripts directory** must contain `start-server.js` and a `node_modules` folder after `npm install`. |
|
||||
|
||||
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
# MageSail documentation
|
||||
|
||||
MageSail (`MageSail_Magesail`) integrates [Holesail](https://www.npmjs.com/package/holesail) with Magento 2 Admin: **Website** tunnels (store views, optional NGINX map, storefront routing) and **Service** tunnels (TCP to backends discovered from `app/etc/env.php`), plus lifecycle, cron, and logs.
|
||||
MageSail (`MageSail_Magesail`) integrates [Holesail](https://www.npmjs.com/package/holesail) with Magento 2 Admin: **Website** tunnels (store views aligned with the website default store’s **CMS and theme**, optional NGINX map, storefront routing including **`X-Forwarded-Host`**) and **Service** tunnels (TCP to backends discovered from `app/etc/env.php`), plus lifecycle, cron, and logs.
|
||||
|
||||
## Development tests (repository root)
|
||||
|
||||
@@ -14,8 +14,8 @@ PHPUnit, PHPStan (tests + scanned `Model/`), and PHPCS run from the repo root af
|
||||
| [installation.md](installation.md) | Composer vs `app/code`, enablement, Node/npm, legacy file migration |
|
||||
| [configuration.md](configuration.md) | All Admin config paths (tunnel + NGINX) |
|
||||
| [admin-ui.md](admin-ui.md) | MageSail sidebar menu and tunnel screens |
|
||||
| [tunnel-store-and-multistore.md](tunnel-store-and-multistore.md) | Website vs Service, registry schema, per-tunnel store views, base URLs, stop vs remove |
|
||||
| [request-routing-and-plugins.md](request-routing-and-plugins.md) | DI plugins, headers, and base-URL behavior |
|
||||
| [tunnel-store-and-multistore.md](tunnel-store-and-multistore.md) | Website vs Service, registry schema, per-tunnel store views, base URLs, CMS/theme copy from website default, `X-Forwarded-Host`, stop vs remove |
|
||||
| [request-routing-and-plugins.md](request-routing-and-plugins.md) | DI plugins, headers, forwarded-host hostname matching, and base-URL behavior |
|
||||
| [nginx-global-map.md](nginx-global-map.md) | Map file edits, `nginx -t`, reload, permissions |
|
||||
| [cron-and-monitoring.md](cron-and-monitoring.md) | Daily cron, auto-restart, DB logs |
|
||||
| [php-classes-reference.md](php-classes-reference.md) | PHP classes by layer (quick index) |
|
||||
|
||||
+2
-2
@@ -35,7 +35,7 @@ The same URL shows two in-page tabs: **Tunnels list** (grid, log tails, start/st
|
||||
- **Tunnel hostname (secure, optional)** — same rules; stored as `https://host/` when set.
|
||||
- **Use secure URLs on storefront and in admin** — drives scoped `web/secure/*` when provisioning.
|
||||
- **Holesail secure tunnel** — private vs public Holesail mode for this tunnel (defaults from **MageSail → Holesail Tunnel → Configuration** if unset in the form).
|
||||
- **Create and start tunnel** — **Website**: provisions the store, updates the NGINX map if configured, writes the registry row, then starts Node. **Service**: writes the registry row (resolved port/host) and starts Node only.
|
||||
- **Create and start tunnel** — **Website**: provisions the store (base URLs; copies **CMS + theme** from the website **default** store and mirrors **`cms_page_store` / `cms_block_store`**), updates the NGINX map if configured, writes the registry row, then starts Node. **Service**: writes the registry row (resolved port/host) and starts Node only.
|
||||
|
||||
**AJAX / JSON**
|
||||
|
||||
@@ -74,7 +74,7 @@ Requires **`MageSail_Magesail::config`** ACL resource. Operational details: [ngi
|
||||
- **Block**: [`Block/Adminhtml/Howto.php`](../Magesail/Block/Adminhtml/Howto.php) — init JSON for [`magesail-howto-copy.js`](../Magesail/view/adminhtml/web/js/magesail-howto-copy.js) (copy feedback strings).
|
||||
- **Menu id**: `MageSail_Magesail::howto` — under **Holesail Tunnel**; **ACL**: `MageSail_Magesail::tunnel_view`.
|
||||
|
||||
In-page guide: recommends **Holesail Browser** (extension) as the primary client for Magento; tunnel workflow; **Copy** on each **NGINX** / **Apache** / **cURL** example block copies that snippet to the clipboard and shows **Copied!** on the button briefly; **NGINX Proxy Manager** remains prose-only (no single config file). CLI/npm option; **Use TLS** when using local port **443**; security notes; pointers to multistore / NGINX docs. Styles: [`magesail-howto.css`](../Magesail/view/adminhtml/web/css/magesail-howto.css).
|
||||
In-page guide: recommends **Holesail Browser** (extension) as the primary client for Magento; tunnel workflow (including **CMS/theme copy** from the website default store and **`X-Forwarded-Host`** in proxy examples); **Copy** on each **NGINX** / **Apache** / **cURL** example block copies that snippet to the clipboard and shows **Copied!** on the button briefly; **NGINX Proxy Manager** remains prose-only (no single config file). CLI/npm option; **Use TLS** when using local port **443**; security notes; pointers to multistore / NGINX / routing docs. Styles: [`magesail-howto.css`](../Magesail/view/adminhtml/web/css/magesail-howto.css).
|
||||
|
||||
### About
|
||||
|
||||
|
||||
@@ -66,6 +66,10 @@ npm install
|
||||
|
||||
[`package.json`](../Magesail/scripts/package.json) pins Holesail; keep `node_modules/` out of VCS unless your workflow requires otherwise.
|
||||
|
||||
## After upgrading MageSail (git pull / Composer update)
|
||||
|
||||
Run **`php bin/magento setup:upgrade`** when `db_schema.xml` or setup patches change, then **`php bin/magento setup:di:compile`** whenever PHP constructors or `di.xml` change (skipping compile can cause runtime **“Too few arguments to function …”** errors). Finish with **`cache:flush`**.
|
||||
|
||||
## Verification
|
||||
|
||||
- `php bin/magento module:status MageSail_Magesail` → enabled.
|
||||
|
||||
@@ -4,6 +4,8 @@ MageSail can **append** and **remove** lines inside your existing **`map $http_h
|
||||
|
||||
**Service** tunnels (TCP to env-discovered backends) **do not** add or remove map lines; the global map is only for **HTTP** storefront/admin routing to a store view.
|
||||
|
||||
Magento’s [`TunnelHostnameMatcher`](../Magesail/Model/TunnelHostnameMatcher.php) can still resolve the tunnel store using **`X-Forwarded-Host`** if PHP receives an internal **`Host`**. Your **`map`** directive usually keys on **`$http_host`**; if that value is not the tunnel hostname, either change the **`map` source** (e.g. to a forwarded-host variable your stack sets) or rely on MageSail header/query detection in PHP.
|
||||
|
||||
Implementation: [`NginxGlobalMapManager`](../Magesail/Model/NginxGlobalMapManager.php), [`NginxMapParser`](../Magesail/Model/NginxMapParser.php), configured under **MageSail → Holesail Tunnel → Configuration** (group **NGINX Configuration**; see [configuration.md](configuration.md)).
|
||||
|
||||
## Validation strategy
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@ MageSail exposes a local Magento instance (or **TCP services** from env) through
|
||||
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.
|
||||
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, 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.
|
||||
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.
|
||||
|
||||
```mermaid
|
||||
|
||||
@@ -25,13 +25,13 @@ Module namespace: **`MageSail\Magesail`**. Paths are relative to [`Magesail/`](.
|
||||
|-------|------|------------------|
|
||||
| `TunnelManager` | `Model/TunnelManager.php` | Start/stop Node Holesail **per tunnel id**; passes JSON args including **`forwardHost`** (`service_target_host`) to `start-server.js`; script path resolution; shell invocation. |
|
||||
| `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; NGINX map updates for website only; `teardownTunnel` branches on `tunnel_type`. |
|
||||
| `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. |
|
||||
| `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`. |
|
||||
| `TunnelHostnameMatcher` | `Model/TunnelHostnameMatcher.php` | Hostname match against persisted tunnel base URLs. |
|
||||
| `TunnelHostnameMatcher` | `Model/TunnelHostnameMatcher.php` | Hostname match against persisted tunnel base URLs; tries **`HTTP_HOST`**, then **`HTTP_X_FORWARDED_HOST`** and **`X-Forwarded-Host`** (comma-separated values). |
|
||||
| `AdminPathDetector` | `Model/AdminPathDetector.php` | Detect admin area paths / configured admin front name. |
|
||||
| `NginxGlobalMapManager` | `Model/NginxGlobalMapManager.php` | Read/write global map file; insert/remove tunnel lines; validate and reload nginx. |
|
||||
| `NginxMapParser` | `Model/NginxMapParser.php` | Parse and manipulate map blocks in the file content. |
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Tier 4: not covered by off-Magento PHPUnit
|
||||
|
||||
The workspace [`composer.json`](../composer.json) does not install `magento/framework` from Packagist (private `repo.magento.com` is required). Unit tests therefore use **minimal Magento stubs** under [`Magesail/Test/Stubs/Magento/`](../Magesail/Test/Stubs/Magento/) plus [`Magesail/Test/Stubs/Psr/Log/`](../Magesail/Test/Stubs/Psr/Log/) (`LoggerInterface`) for PSR-4 types referenced by mocked collaborators.
|
||||
The workspace [`composer.json`](../composer.json) does not install `magento/framework` from Packagist (private `repo.magento.com` is required). Unit tests therefore use **minimal Magento stubs** under [`Magesail/Test/Stubs/Magento/`](../Magesail/Test/Stubs/Magento/) (including [`Framework/App/ResourceConnection.php`](../Magesail/Test/Stubs/Magento/Framework/App/ResourceConnection.php) for `TunnelStoreProvisioner` collaborators) plus [`Magesail/Test/Stubs/Psr/Log/`](../Magesail/Test/Stubs/Psr/Log/) (`LoggerInterface`) for PSR-4 types referenced by mocked collaborators.
|
||||
|
||||
The following production classes are **not** exercised by the repository’s PHPUnit suite. They need either **refactoring** (extract pure services + seams) or **Magento integration / functional tests** inside a full Magento project (`dev/tests/integration` or MFTF):
|
||||
|
||||
|
||||
@@ -9,9 +9,9 @@ This pipeline applies to **Magento HTTP(S)** requests (typically **website** tun
|
||||
[`TunnelRequestDetector`](../Magesail/Model/TunnelRequestDetector.php) resolves an effective tunnel store code from, in order:
|
||||
|
||||
1. Query parameter **`magesail_store`**
|
||||
2. Header **`X-MageSail-Store-Code`** (also `HTTP_X_MAGESAIL_SERVER` path via server var)
|
||||
2. Header **`X-MageSail-Store-Code`** (also server var **`HTTP_X_MAGESAIL_STORE_CODE`**)
|
||||
3. Parsing **`magesail_store`** from the request URI query string if needed
|
||||
4. [`TunnelHostnameMatcher`](../Magesail/Model/TunnelHostnameMatcher.php) — compares `HTTP_HOST` to **website** tunnel base URL hosts in [`TunnelRegistry`](../Magesail/Model/TunnelRegistry.php) (`var/magesail_tunnels.json`). Rows with **`tunnel_type: service`** are excluded from hostname matching (`findByRequestHost`).
|
||||
4. [`TunnelHostnameMatcher`](../Magesail/Model/TunnelHostnameMatcher.php) — compares host candidates to **website** tunnel base URL hosts in [`TunnelRegistry`](../Magesail/Model/TunnelRegistry.php) (`var/magesail_tunnels.json`). Matching tries **`HTTP_HOST` first**, then **`HTTP_X_FORWARDED_HOST`** and the **`X-Forwarded-Host`** header (comma-separated lists are split). That way the tunnel store still resolves when PHP-FPM sees an internal host (e.g. `127.0.0.1`) but the client or an edge proxy sent the tunnel hostname in a forwarded-host header. Rows with **`tunnel_type: service`** are excluded from hostname matching (`findByRequestHost`).
|
||||
|
||||
Each resolved code is accepted only if that **store view exists** in Magento (`StoreRepository`); otherwise the detector returns `null` (stale header, bookmark, or registry out of sync).
|
||||
|
||||
@@ -41,7 +41,7 @@ flowchart TB
|
||||
subgraph detect [Detection]
|
||||
Q[magesail_store_query]
|
||||
H[X-MageSail-Store-Code_header]
|
||||
HOST[HTTP_HOST_vs_registry]
|
||||
HOST[HTTP_HOST_and_X-Forwarded-Host_vs_registry]
|
||||
end
|
||||
detect --> TRD[TunnelRequestDetector]
|
||||
TRD --> SF[StoreFromMageSailHeaderPlugin]
|
||||
|
||||
@@ -25,6 +25,9 @@
|
||||
| Admin **404** on tunnel host | Backend host validation | Ensure tunnel detection fires: header `X-MageSail-Store-Code`, query `magesail_store`, or hostname matches provisioned base URL. See [request-routing-and-plugins.md](request-routing-and-plugins.md). |
|
||||
| **301** to production base URL | Redirect-to-base | Confirm tunnel plugins active (module enabled, `setup:di:compile`, cache flush). For NGINX stacks, confirm map lines exist and nginx was reloaded. |
|
||||
| Wrong store | Map / header mismatch | Align **MAGE_RUN_CODE** with that tunnel’s **store code** (shown in Manage Tunnel; e.g. `mgtun_…`); or pass `___store=<store_code>`. |
|
||||
| Wrong store when **`Host` is `127.0.0.1`** | Edge proxy overwrites **Host** | Forward the client hostname with **`X-Forwarded-Host`** (or ensure **`HTTP_HOST`** is the tunnel hostname). See [request-routing-and-plugins.md](request-routing-and-plugins.md). |
|
||||
| **504** / Gateway Timeout | Image resize / media permissions | **`FileSystemException`** on **`pub/media/catalog/product/cache`** in **`var/log/exception.log`** → create the directory and fix ownership/mode so PHP can write. Also check upstream **`fastcgi_read_timeout`**. |
|
||||
| **`Too few arguments`… constructor** after module update | Stale generated code | Run **`php bin/magento setup:di:compile`** and **`cache:flush`**. |
|
||||
|
||||
## NGINX map and reload
|
||||
|
||||
|
||||
@@ -18,6 +18,15 @@ MageSail provisions a **dedicated store view per website tunnel** with a **gener
|
||||
|
||||
Legacy installs migrated from older MageSail may still use the deprecated code **`magesail_tunnel`** (`TunnelStoreProvisioner::STORE_CODE`).
|
||||
|
||||
## Storefront defaults copied from the website default (website tunnels only)
|
||||
|
||||
When a **website** tunnel is provisioned, [`TunnelStoreProvisioner`](../Magesail/Model/TunnelStoreProvisioner.php) aligns the **new** store view with that website’s **default** store view (not with another tunnel store):
|
||||
|
||||
- **Scoped config** copied when present on the default store: `web/default/cms_home_page`, `web/default/cms_no_route`, `web/default/cms_no_cookies`, `web/default/front`, and `design/theme/theme_id` (so the tunnel storefront uses the same CMS home / theme as the main site on that website—e.g. Blank + Breeze or Luma, depending on how the default store is configured).
|
||||
- **`cms_page_store` / `cms_block_store`** — rows for the default store (and global `store_id` **0**) are **mirrored** to the new tunnel `store_id` via `INSERT IGNORE`, so CMS pages and blocks assigned to the default storefront are also visible on the tunnel store.
|
||||
|
||||
Failures during this step are **logged as a warning**; base URLs and the store view are still created. After changing the website default’s theme or CMS assignments, **new** tunnels pick up the current defaults at provision time; existing tunnel stores are not auto-resynced.
|
||||
|
||||
## Base URLs (website tunnels only)
|
||||
|
||||
You must supply at least the **unsecure hostname** (e.g. `dev.myshop.local` without `http://`). The module saves **`http://hostname/`** as the unsecure base URL. Optional **secure hostname** (without `https://`) becomes **`https://hostname/`**. You can still paste a full `http://` or `https://` URL; schemes are preserved when already present.
|
||||
@@ -65,6 +74,8 @@ For **website** tunnels: for PHP-FPM to receive `MAGE_RUN_CODE` / `MAGE_RUN_TYPE
|
||||
|
||||
For requests that **do not** go through that map (e.g. some local proxies), you can still select the tunnel store using headers or query parameters — see [request-routing-and-plugins.md](request-routing-and-plugins.md). The store code is **per website tunnel** (shown in the Admin table).
|
||||
|
||||
If the stack in front of PHP sets **`Host`** to an internal name (e.g. `127.0.0.1`) but the browser used your tunnel hostname, ensure the proxy forwards **`X-Forwarded-Host`** (comma-separated chains are supported) with that public hostname so [`TunnelHostnameMatcher`](../Magesail/Model/TunnelHostnameMatcher.php) can match the registry. The optional NGINX global map still keys off its configured **`map` source variable** (often `$http_host`); align map entries with the host Magento must resolve, or rely on header/query detection when the map cannot see the client host.
|
||||
|
||||
## Stale store codes and nginx
|
||||
|
||||
If the global map or a bookmark still sends **`MAGE_RUN_CODE`** for a **deleted** store (for example an old `magesail_tunnel` line), [`StoreFromMageSailHeaderPlugin`](../Magesail/Plugin/App/Request/StoreFromMageSailHeaderPlugin.php) clears the invalid code so Magento can fall back to default scope instead of throwing `NoSuchEntityException`. **Update the map** to the current store code per hostname so routing matches your tunnels.
|
||||
|
||||
Reference in New Issue
Block a user