# Architecture Holesail Browser is composed of three parts: a browser extension, a native host process, and the Holesail P2P network. The extension and native host communicate via Chrome's native messaging protocol; the native host manages all tunnel connections and runs a local HTTPS proxy that the browser routes virtual host traffic through. ## Component overview ``` ┌─────────────────────────────────────────────────────────────────┐ │ Browser (Chrome / Firefox) │ │ │ │ ┌──────────────┐ ┌──────────────────────────────────────┐ │ │ │ background.js│ │ dashboard/dashboard.html │ │ │ │ (service │◄──►│ (management UI — virtual hosts, │ │ │ │ worker) │ │ SSH, RDP, backups, settings, logs) │ │ │ └──────┬───────┘ └──────────────────────────────────────┘ │ │ │ PAC script: *.hole.sail → PROXY 127.0.0.1:8442 │ │ │ *.custom.tld → PROXY 127.0.0.1:8442 │ │ │ Native messaging: chrome.runtime.connectNative(...) │ └─────────┼───────────────────────────────────────────────────────┘ │ stdin/stdout (4-byte length-prefixed JSON) ▼ ┌─────────────────────────────────────────────────────────────────┐ │ Native Host (~/.holesail-browser/holesail-browser-host) │ │ │ │ ┌──────────┐ ┌──────────────────┐ ┌──────────────────────┐ │ │ │ host.js │ │ holesail- │ │ certificate- │ │ │ │ (command │ │ manager.js │ │ authority.js │ │ │ │ dispatch)│ │ (tunnel lifecycle│ │ (root CA + per-TLD │ │ │ └────┬─────┘ │ + state.json) │ │ wildcard certs, │ │ │ │ └────────┬─────────┘ │ keychain install) │ │ │ │ │ └──────────────────────┘ │ │ ┌────▼─────────────────▼──────────────────────────────────┐ │ │ │ https-proxy.js connect-proxy.js │ │ │ │ 127.0.0.1:8443 127.0.0.1:8442 │ │ │ │ (SNI-aware TLS, (HTTP CONNECT handler, │ │ │ │ per-TLD wildcard pipes to 127.0.0.1:8443) │ │ │ │ certs, HTTP forwarding) │ │ │ └─────────────────────────────────────────────────────────┘ │ │ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────┐ │ │ │ ssh-manager │ │ rdp-manager │ │ backup-manager │ │ │ │ (PTY + WS │ │ (VNC/RDP + │ │ (tar.gz snapshots) │ │ │ │ bridge) │ │ WS bridge) │ │ │ │ │ └──────────────┘ └──────────────┘ └──────────────────────┘ │ └─────────────────────────────────────────────────────────────────┘ │ │ Holesail P2P (Noise protocol, DHT) ▼ ┌──────────────────────────────────────────────────────────────┐ │ Remote peers (HTTP servers, SSH daemons, VNC/RDP servers) │ └──────────────────────────────────────────────────────────────┘ ``` ## Source files ### Native host (`native-host/`) #### Top-level files | File | Purpose | |------|---------| | `index.mjs` | Entry point. Bootstraps Bare globals, creates the native messaging `messenger`, wires `handleMessage` from `host.js`. Handles `SIGTERM`/`SIGINT` for graceful shutdown. | | `messenger.js` | Chrome/Firefox native messaging framing: 4-byte little-endian length prefix + UTF-8 JSON body. Max 1 MB per message. | | `host.js` | Thin shim — re-exports `handleMessage` and `cleanup` from `host/message-router.js`. Kept at the top level so `index.mjs` requires it unchanged. | | `holesail-manager.js` | Thin shim — re-exports the full API from `holesail-manager/index.js`. Kept at the top level so `host/message-router.js` requires it unchanged. | | `https-proxy.js` | SNI-aware HTTPS reverse proxy on `127.0.0.1:8443`. Uses a pure-JS TLS ClientHello parser to extract the SNI hostname from each incoming connection, derives the wildcard parent domain, and presents a per-TLD wildcard cert. Supports any hostname depth (e.g. `i.love.hole.sail`). Returns a 502 HTML page for unknown hostnames. | | `connect-proxy.js` | HTTP CONNECT proxy on `127.0.0.1:8442`. Accepts `CONNECT hostname:443`, replies `200 Connection established`, then pipes the raw TCP stream to `127.0.0.1:8443`. | | `certificate-authority.js` | Generates a 2048-bit RSA root CA (10-year validity) using `node-forge`. Signs per-TLD wildcard domain certs on demand (1-year). Installs the CA into the OS trust store. Fingerprint-verifies to detect stale entries. | | `ssh-manager.js` | Per SSH session: starts a Holesail client tunnel, spawns `ssh` with a real PTY via `tt-native` (`forkpty`), starts a `bare-ws` WebSocket server, and bridges PTY ↔ WebSocket. SSH is spawned only after the browser WebSocket client connects and sends a ready-signal. Password delivery uses `SSH_ASKPASS` + a named FIFO — public key auth is tried first; a password prompt appears in xterm.js only if key auth fails and no password is saved. | | `rdp-manager.js` | Per RDP/VNC session: starts a Holesail client tunnel, starts a WebSocket server. VNC: transparent byte pipe. RDP: `node-rdpjs-2` client, converts bitmap updates to JSON. | | `backup-manager.js` | Creates/restores `tar.gz` backups of `state.json` + all certificates. Supports create, list, restore, delete, and auto-prune by retention count. | #### `host/` — host orchestration modules | File | Purpose | |------|---------| | `paths.js` | `resolveBase()` — detects standalone binary vs. dev mode and returns the correct base directory. Exports `BASE_DIR` and `STORAGE_PATH` constants used by all other host modules. | | `logger.js` | `log()` and `debugLog()` — timestamped writes to `holesail-browser.log` (or `BRIDGE_SWARM_LOG` override) and `process.stderr`. Respects `HOLESAIL_DEBUG` env var. | | `startup.js` | `initStartup()` — deferred proxy startup sequence: loads persisted settings, waits for CA readiness, starts the HTTPS and CONNECT proxies, then calls `restorePersistedTunnels()`. Exports promise accessors used by the message router. | | `message-router.js` | `handleMessageAsync()` — the 30-case `switch` that dispatches every browser message to the appropriate manager. Wires all managers together on first require. Exports `handleMessage` and `cleanup`. | #### `holesail-manager/` — tunnel and state sub-modules | File | Purpose | |------|---------| | `state.js` | `loadState()`, `saveStateSync()`, `buildDefaultState()`. Handles `state.json` read/write and migration from the legacy `holesail-persist.json` format. | | `settings.js` | `getSettings()`, `updateSettings()`, `getProxyPort()`, `setProxyPort()`. Owns `currentSettings` and `runtimeProxyPort`. | | `connections.js` | `getSshConnections/set`, `getRdpConnections/set`. Owns the in-memory SSH and RDP connection lists (persisted in `state.json`, not live sessions). | | `port-allocator.js` | `allocateTunnelPort()`, `releaseTunnelPort()`. Manages the virtual-host tunnel port pool starting at 19000 with a free list for recycling. | | `servers.js` | `startServer()`, `stopServer()`, `getServers()`. Manages Holesail server tunnels (server mode — exposes a local port to the P2P network). | | `virtual-hosts.js` | `setVirtualHost()`, `removeVirtualHost()`, `getVirtualHosts()`, `getLocalBackend()`, `getLocalPortForHostname()`, `getVirtualHostMap()`. Manages virtual host client tunnels routed through the HTTPS proxy. | | `service-tunnels.js` | `startServiceTunnel()`, `stopServiceTunnel()`, `getServiceTunnels()`. Manages direct TCP client tunnels (not HTTP-proxied). | | `index.js` | Assembles all sub-modules, injects shared `saveState` and `emit` callbacks, exposes `restorePersistedState()` and `cleanup()`, and re-exports the complete original `holesail-manager.js` API. | ### Extension (`extension/`) #### Top-level files | File | Purpose | |------|---------| | `manifest.json` | Manifest V3. Permissions: `nativeMessaging`, `proxy`, `declarativeNetRequest`, `tabs`, `notifications`. Optional host permissions requested at runtime for custom TLDs. | | `background.js` | Service worker entry point. Declares constants (`DEBUG_VERBOSE`, `browser` shim), then loads all background modules via `importScripts()` in dependency order, applies the initial PAC script, and connects to the native host. | | `content.js` | Minimal content script. Relays `holesail-host-disconnect` to the page as a `CustomEvent`. | | `wrong-domain.html` | Error page for `*.host.test` (common typo), redirected via `declarativeNetRequest`. | #### `background/` — service worker modules | File | Purpose | |------|---------| | `logs.js` | In-memory log ring buffer (500 entries), `log()`/`debugLog()` helpers, `broadcastLogs()` to open dashboard tabs, `dashboardTabs` set. | | `state.js` | All shared mutable state: `extensionState`, `activeConnections`, `tabSwarms`, `swarmRefCount`, `pacConfirmedActive`, `notifyOnDisconnect`, port defaults. | | `proxy.js` | `applyPAC()` — builds and installs the PAC script; `clearProxy()`; `getActiveTlds()` helper; `proxy.settings.onChange` listener to re-apply if overridden. | | `native-messaging.js` | `connect()`, `send()`, `scheduleReconnect()`, `retryGetStateForConnectProxy()`. Owns the `port` reference, `pending` map, `subscribedTabs` set, and all `port.onMessage`/`port.onDisconnect` logic. | | `tab-lifecycle.js` | `tabs.onRemoved` listener — decrements swarm ref counts, destroys swarms when their last tab closes, cleans up `subscribedTabs` and `dashboardTabs`. | | `message-router.js` | `runtime.onMessage` dispatcher — handles `registerSwarm`, `send`, `subscribe`/`unsubscribe`, `registerDashboard`/`unregisterDashboard`, and `getState` actions. | #### `dashboard/` — management UI | File | Purpose | |------|---------| | `dashboard.html` | HTML shell + ordered `