MageSail - Holesail Tunnel Integration for Magento 2
Full documentation: docs/README.md (installation, configuration, NGINX, routing plugins, troubleshooting).
Overview
MageSail is a Magento 2 extension that integrates Holesail (P2P tunneling with HyperDHT / Hyperswarm) so you can expose a local Magento dev site without traditional port forwarding or a central SaaS relay. A shareable key (e.g. hs://…) allows remote clients to reach your local HTTP/HTTPS port.
Version: package 1.0.4 — see Magesail/composer.json and Magesail/etc/module.xml (setup_version).
Key features
- 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.
- Tunnel store views — 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). - NGINX global map — Optional append/remove of
map $http_host $MAGE_RUN_CODE/$MAGE_RUN_TYPElines plusnginx -tand reload (or wrapper script). Admin screen: MageSail → Holesail Tunnel → NGINX Global Map. - Storefront / admin routing — Plugins detect tunnel traffic via header
X-MageSail-Store-Code, querymagesail_store,___store, or hostname vs persisted tunnel URLs; adjustMAGE_RUN_*, backend front name resolution, generated base URLs, and skip redirect-to-base where appropriate. - PID and logs — Per tunnel:
var/magesail_tunnels/{id}.pid,{id}.key,var/log/magesail_tunnel_{id}.log; optional DB rows inmagesail_logs. - Cron — Daily job checks each tunnel’s PID file. If the process is dead and Auto-restart is No, it removes that tunnel (store, NGINX map line, registry row, PID/key) — same end state as Remove for a crashed tunnel. If Auto-restart is Yes, it clears stale PID/key and starts Node again (saved port and secure mode).
Good for client demos, remote QA, and team access without deploying to staging.
Private vs public keys (Holesail)
Holesail supports private (default, authenticated) and public (open) modes. In Admin, Secure Tunnel = Yes means private mode, No means public.
Private mode
- A seed derives a keypair; the server keeps authentication material; peers need the shared secret to connect.
- Treat shared seeds like SSH private keys: only share with trusted parties.
Public mode
- A discoverable public address; anyone with the key can reach the tunnel endpoint.
- Protect sensitive data with Magento’s own auth (customer login, admin ACL, etc.).
Private mode is recommended for most dev sharing.
Requirements
- Magento 2: 2.4.x (see Composer constraints in
Magesail/composer.json). - PHP: 8.1, 8.2, or 8.3.
- Node.js: 18+ for
start-server.js. - npm: Install dependencies under
Magesail/scripts/. - OS: Linux/macOS typical; Windows untested (WSL may work). Shell tools:
ps,kill,nohup. - Composer: For packaged installs.
Holesail and npm dependencies are pulled when you run npm install in the scripts directory (see docs/scripts.md).
Installation
1. Install the package
Composer — package name magesail/magesail:
composer require magesail/magesail:^1.0
If the package is not on Packagist, add a path or VCS repository in the project composer.json, then composer require magesail/magesail:@dev.
Set MageSail → Holesail Tunnel → Configuration → Scripts directory to the absolute path of the folder that contains start-server.js, e.g. <magento-root>/vendor/magesail/magesail/scripts.
Git clone under app/code:
cd <magento-root>/app/code/
git clone <repository-url> MageSail
Module root: app/code/MageSail/Magesail/.
2. Enable the module
php bin/magento module:enable MageSail_Magesail
php bin/magento setup:upgrade
php bin/magento setup:di:compile
php bin/magento cache:flush
3. Verify
php bin/magento module:status MageSail_Magesail→ enabled.- Admin: MageSail → Holesail Tunnel (Manage Tunnel, NGINX Global Map, Configuration).
- Database: table
magesail_logs.
4. Node dependencies (one-time)
cd <magento-root>/app/code/MageSail/Magesail/scripts
# or: cd <magento-root>/vendor/magesail/magesail/scripts
npm install
Configuration
All settings: MageSail → Holesail Tunnel → Configuration (standard Magento config UI; not listed under Stores → Configuration).
Tunnel settings
| Field | Default | Description |
|---|---|---|
| Secure Tunnel | Yes | Private vs public Holesail mode. |
| Scripts directory | (empty) | Absolute path to start-server.js folder. Empty = default under app/code/MageSail/Magesail/scripts from Magento root. |
| Node.js binary | (empty) | Empty = node on PHP’s PATH. Set a full path if Node is not on PATH (nvm, fnm, etc.). |
| Auto-restart on crash | No | If Yes, the monitor cron restarts each tunnel whose process died (saved port and secure mode). If No, the monitor tears down tunnels with a stale PID (store + map + registry), not only the PID files. |
| Monitor cron schedule | 0 0 * * * |
Magento cron expression for the tunnel health job (default: daily at midnight). Edit in Configuration → Settings. |
Scoped globally / per website / per store view. After changes: php bin/magento cache:flush config.
NGINX configuration
Used when you manage MAGE_RUN_CODE / MAGE_RUN_TYPE via a shared global map file (e.g. Ansible). Fields include Global map file path, nginx binary path, Temporary PID file (for isolated nginx -t), Reload wrapper script (when PHP cannot nginx -s reload), map variable names, MAGE_RUN_TYPE value, and Disable base URL redirect for tunnel. See docs/configuration.md and docs/nginx-global-map.md.
Advanced (app/etc/config.php): you can set magesail/settings/* and magesail/nginx/* keys to match system.xml paths.
Usage
Admin menu
- MageSail → Holesail Tunnel → Manage Tunnel — start/stop, base URLs, key display, log snippet.
- MageSail → Holesail Tunnel → NGINX Global Map — edit/validate/reload the configured map file (requires config ACL).
- MageSail → Holesail Tunnel → Configuration — tunnel defaults, scripts path, Node binary, monitor cron schedule, auto-restart, NGINX paths (requires config ACL; opens standard system config for section
magesail).
Starting a tunnel
- Open Manage Tunnel.
- Under Add tunnel, choose Website and Store group, Local port (preset 443, 80, 8080, or Custom), and Tunnel hostname (unsecure) (required), e.g.
dev.myshop.local— nohttp://; Magento storeshttp://…/automatically. It must match the host in the browser / Holesail client vhost. - Optionally set Tunnel hostname (secure), Use secure URLs on storefront and in admin, and Holesail secure tunnel. Full
http:///https://URLs still work if pasted. - Click Create and start tunnel.
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. 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.
For an existing stopped tunnel, use Start on that row (no re-provision).
If the map was updated but nginx could not reload (common for the PHP user), follow the on-screen hint or docs/nginx-global-map.md.
Proxy / client routing
So Magento resolves the tunnel store, your proxy or browser can use the tunnel’s store code from Admin (example: mgtun_abc12345):
- Header:
X-MageSail-Store-Code— value = that store code - 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.
Sharing access
- Copy the hs://… (or key string shown) for Holesail clients.
- Local port override example:
holesail hs://… --port 1234thencurl http://127.0.0.1:1234/. - You may add the shop hostname to
/etc/hostspointing at127.0.0.1when using a local listener. - TLS to localhost often uses dev certs; browsers may warn.
Stopping or removing a tunnel
- Stop — Kills the Node process and removes that tunnel’s PID/key files. The store, NGINX map line, and registry row stay so you can start again.
- Remove — Stops the process, removes the NGINX map line, deletes the store view when allowed, and removes the tunnel from the registry.
Manual: kill $(cat var/magesail_tunnels/<id>.pid) and remove stale *.pid / *.key under that directory if needed.
Monitoring
- UI — running state, PID, key.
- File log —
tail -f var/log/magesail_tunnel_<id>.log(per tunnel) - Database —
magesail_logsfor start/stop/cron messages.
Cron
- Schedule: configurable under Configuration → Settings → Monitor cron schedule (stored as
crontab/default/jobs/magesail_monitor/schedule/cron_expr; default0 0 * * *).Magesail/etc/crontab.xmlpoints at that path; default is also inMagesail/etc/config.xml. - Behavior: For each tunnel, if its PID file exists but the process is dead: with Auto-restart on crash = No, the job runs full teardown for that tunnel (as after Remove, subject to the same website-default rules). With Auto-restart = Yes, it removes stale PID/key, then starts Node using that tunnel’s saved local port and Holesail secure flag. A warning is logged either way.
Troubleshooting
Short table — expanded in docs/troubleshooting.md.
| Issue | Suggestion |
|---|---|
| Start fails / “Command failed” | Node.js binary in Configuration if node is not on PHP’s PATH; run npm install in Scripts directory. |
| “Tunnel hostname is required” | Fill unsecure hostname before start. |
| 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. |
| Reload / map permission errors | Map file writable by PHP user; wrapper + sudoers — 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. |
Development / CI
From the repository root (not only inside Magesail/):
composer install
./vendor/bin/phpunit -c phpunit.xml.dist
./vendor/bin/phpstan analyse --memory-limit=512M
./vendor/bin/phpcs --standard=phpcs.xml
CI (Gitea Actions): primary workflow .gitea/workflows/ci.yml. Gitea checks .gitea/workflows before .github/workflows, so that file is what runs on Gitea. On Gitea 1.19+, enable Actions if needed ([actions] ENABLED=true before 1.21), register an act runner, and ensure a runner accepts runs-on: ssh (see .gitea/workflows/ci.yml; change the label if your runner uses a different one). The job uses PHP and Composer on the runner’s PATH (no setup-php action: current act_runner builds only support older Node runtimes for composite actions, while upstream setup-php targets Node 24). .github/workflows/ci.yml mirrors the same steps (short uses: names for checkout). The module’s Magesail/composer.json targets Magento 2.4.x (magento/framework >=103.0 <106, etc.); install that package inside a Magento project with Composer + repo.magento.com credentials.
License
This project is licensed under the GNU General Public License v3.0 or later — see LICENSE and Magesail/composer.json.