first commit
CI / Build & Test (push) Has been cancelled

This commit is contained in:
Raven Scott
2026-02-27 18:13:59 -05:00
commit d58a0b6e2d
64 changed files with 30550 additions and 0 deletions
+278
View File
@@ -0,0 +1,278 @@
# Architecture
Holesail Browser is a two-layer system: a browser extension and a native messaging host. Together they let the browser navigate `hs://` P2P URLs as if they were ordinary HTTPS sites, expose local services as Holesail tunnels, provide SSH access, connect to remote desktops (VNC/RDP), and manage backups — all through the browser.
## Layer Overview
```
┌──────────────────────────────────────────────────────────────────┐
│ Browser Extension (Manifest V3) │
│ │
│ content.js ──────► background.js (service worker) │
│ (event relay) │ chrome.runtime.connectNative │
│ │ PAC proxy script (*.hole.sail) │
└────────────────────────┼─────────────────────────────────────────┘
│ stdin / stdout (4-byte LE + JSON)
┌────────────────────────▼─────────────────────────────────────────┐
│ Native Host (Bare runtime) │
│ │
│ index.mjs → messenger.js → host.js │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ holesail-manager.js │ │
│ │ ├── Server tunnels (expose local port as hs:// URL) │ │
│ │ ├── Virtual hosts (hs:// URL → 127.0.0.1:19000+) │ │
│ │ └── Service tunnels (hs:// URL → user-chosen port) │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ https-proxy.js 127.0.0.1:8443 (TLS termination) │
│ connect-proxy.js 127.0.0.1:8442 (HTTP CONNECT, PAC target) │
│ certificate-authority.js (Holesail Browser CA, *.hole.sail) │
│ ssh-manager.js (Holesail tunnel → PTY → WebSocket) │
│ rdp-manager.js (Holesail tunnel → VNC/RDP → WebSocket) │
│ backup-manager.js (tar.gz backup/restore of state + certs) │
└──────────────────────────────────────────────────────────────────┘
```
---
## Extension Components
### `manifest.json`
Manifest V3. Key permissions:
| Permission | Purpose |
|-----------|---------|
| `nativeMessaging` | Connect to `com.holesail.browser` native host |
| `proxy` | Set PAC script to route `*.hole.sail` traffic |
| `declarativeNetRequest` | Redirect `*.host.test` typos to an error page |
| `scripting` | Inject scripts into pages if needed |
| `storage` | Persist proxy port state for fast PAC startup |
| `tabs` | Track tab lifecycle |
| `notifications` | Notify on native host disconnect |
Host permissions cover `*://*.hole.sail/*` and `*://*.host.local/*`.
### `background.js` — Service Worker
The central hub of the extension. Responsibilities:
- **Native messaging** — opens a port to `com.holesail.browser` via `chrome.runtime.connectNative`. Reconnects with exponential backoff (100 ms → 30 s) on disconnect.
- **PAC proxy** — sets a Proxy Auto-Config script that routes all `*.hole.sail` traffic through `127.0.0.1:8442` (the CONNECT proxy). Re-applies if another extension overrides it.
- **Message routing** — forwards requests from the dashboard to the native host and routes events back to the correct tab.
- **State relay** — propagates `settings` and `sshConnections`/`rdpConnections` from the native host's `getState` response to the dashboard.
### `content.js` — Content Script
Injected at `document_start` on every page. Subscribes to native host events and relays `holesail-host-disconnect` to the page via a `CustomEvent`.
### `dashboard.html` / `dashboard.js`
Full management UI opened when the extension icon is clicked. Pages:
| Page | Description |
|------|-------------|
| **Overview** | Host connection status, active connections, uptime |
| **Virtual Hosts** | Add/remove `hs://` URL → `*.hole.sail` hostname mappings |
| **Server Tunnels** | Expose local ports as `hs://` URLs (TCP or UDP) |
| **Service Tunnels** | Forward `hs://` URLs to local TCP ports |
| **Proxy & CA** | Proxy status, CA installation |
| **SSH** | Manage saved SSH connections, launch xterm.js terminal sessions |
| **Remote Desktop** | Manage saved VNC/RDP connections, launch viewer sessions |
| **Backups** | Take, restore, and delete tar.gz backups |
| **Logs** | Live log stream from the native host |
| **Settings** | Extension options (proxy ports, debug, backup retention, etc.) |
---
## Native Host Components
The native host runs under the [Bare](https://github.com/nicolo-ribaudo/bare) runtime — a minimal JavaScript runtime for native addons, distinct from Node.js.
### `index.mjs` — Entry Point
Sets up `bare-process` globals, creates the messenger, and calls `handleMessage` (from `host.js`) for each incoming message. Handles `SIGTERM`/`SIGINT` for graceful shutdown (closes all tunnels, SSH sessions, and RDP sessions).
### `messenger.js` — Native Messaging Protocol
Implements the Chrome/Firefox native messaging framing: each message is a 4-byte little-endian length prefix followed by a UTF-8 JSON body. Maximum message size is 1 MB. Reads from `process.stdin`, writes to `process.stdout`.
### `host.js` — Command Handler
Handles all message types from the extension. On startup it:
1. Starts the HTTPS proxy on port 8443
2. Starts the CONNECT proxy on port 8442
3. Restores persisted tunnels from `holesail-browser-storage/state.json`
Full command reference: see [NATIVE-HOST.md](NATIVE-HOST.md).
### `holesail-manager.js` — Tunnel Manager + State
Manages three tunnel types and owns all persistent state:
| Type | Direction | Port allocation |
|------|-----------|----------------|
| Server tunnel | local port → `hs://` URL | none (uses the local port you specify) |
| Virtual host | `hs://` URL → `127.0.0.1:19000+` | auto-allocated from 19000 |
| Service tunnel | `hs://` URL → user-chosen local port | user-specified |
Also manages:
- **Settings** — `getSettings()` / `updateSettings(patch)` with defaults and persistence
- **SSH connections** — `getSshConnections()` / `setSshConnections(list)` (saved connection metadata, not active sessions)
- **RDP connections** — `getRdpConnections()` / `setRdpConnections(list)` (saved connection metadata)
State is persisted to `holesail-browser-storage/state.json` after every change and restored on startup.
### `https-proxy.js` — HTTPS Reverse Proxy
Listens on `127.0.0.1:8443` with a wildcard `*.hole.sail` TLS certificate signed by the local CA. For each request:
1. Reads the `Host` header
2. Calls `holesailManager.getLocalBackend(hostname)``{ host, port }`
3. Proxies the HTTP request to that backend (the Holesail client tunnel)
### `connect-proxy.js` — HTTP CONNECT Proxy
Listens on `127.0.0.1:8442`. Accepts browser `CONNECT hostname:443` requests, replies `200 Connection established`, then pipes the raw TCP stream to `127.0.0.1:8443`. This is what the PAC script points to, because browsers send CONNECT for HTTPS targets.
### `certificate-authority.js` — CA and Certificate Management
On startup, generates (or loads) a 2048-bit RSA root CA named `Holesail Browser CA` with a 10-year validity, stored in `holesail-browser-certs/`. Signs a wildcard `*.hole.sail` certificate (1-year validity) for the HTTPS proxy. Can install the root CA into:
- **macOS** — login keychain via `security add-trusted-cert` (prompts for password via osascript)
- **Linux** — `/usr/local/share/ca-certificates/` + `update-ca-certificates`
- **Windows** — ROOT store via `certutil -addstore`
### `ssh-manager.js` — SSH Session Manager
For each SSH session:
1. Allocates a port from 20000+ and starts a Holesail client tunnel
2. Spawns `ssh` with a real PTY via `tt-native` (`forkpty(3)`)
3. Starts a `bare-ws` WebSocket server on a port from 21000+
4. Bridges PTY ↔ WebSocket so the xterm.js terminal in the dashboard can connect
PTY resize (`pty.resize(cols, rows)`) calls `ioctl(TIOCSWINSZ)`, which SSH forwards as an SSH window-change request to the remote sshd.
### `rdp-manager.js` — Remote Desktop Session Manager
For each remote desktop session:
1. Allocates a tunnel port from 22000+ and starts a Holesail client tunnel
2. Starts a `bare-ws` WebSocket server on a port from 23000+
3. Dispatches based on protocol:
- **VNC** — connects a `bare-tcp` socket to the tunnel, pipes raw bytes between the TCP socket and the WebSocket. The browser uses noVNC's RFB client for all protocol handling and rendering.
- **RDP** — creates a `node-rdpjs-2` RDP client connecting to the tunnel. Converts RDP bitmap updates to JSON and sends them to the browser. Receives mouse/keyboard input as JSON from the browser.
See [REMOTE-DESKTOP.md](REMOTE-DESKTOP.md) for full documentation.
### `backup-manager.js` — Backup Manager
Creates and manages `tar.gz` backups of both the storage directory and the certificates directory:
- Backups are stored in `holesail-browser-storage/backups/`
- Each archive contains a `storage/` prefix (state data) and a `certs/` prefix (CA and domain certificates)
- Supports create, list, restore, delete, and prune-by-retention operations
- Uses the system `tar` binary via `child_process.spawn`
See [BACKUP.md](BACKUP.md) for full documentation.
---
## Message Flow
### Dashboard → Native Host (request)
```
Dashboard: fetch state / add virtual host / start tunnel
→ chrome.runtime.sendMessage({ action: 'getState' / 'send', payload })
→ background.js: onMessage → send() → port.postMessage (native messaging)
→ messenger.js: 4-byte length + JSON → process.stdout
→ host.js: handleMessage → holesailManager / sshManager / rdpManager / backupManager
→ messenger.js: response → process.stdout
→ background.js: port.onMessage → pending.get(id).resolve(payload)
→ dashboard: receives response
```
### Native Host → Extension (tunnel events)
```
Holesail tunnel connects
→ holesail-manager.js: emit('tunnelReady', { hostname, localPort })
→ host.js: send({ type: 'event', event: 'tunnelReady', payload })
→ messenger.js: stdout
→ background.js: port.onMessage → tabs.sendMessage(tabId, { type: 'holesail-event' })
→ dashboard: updates UI
```
### Browser Navigating to `myapp.hole.sail`
```
Browser: DNS lookup for myapp.hole.sail
→ PAC script: return "PROXY 127.0.0.1:8442"
→ Browser: CONNECT myapp.hole.sail:443 → 127.0.0.1:8442
→ connect-proxy: "200 Connection established" → pipe to 127.0.0.1:8443
→ https-proxy: TLS handshake with *.hole.sail cert
→ https-proxy: Host: myapp.hole.sail → getLocalBackend() → 127.0.0.1:19000
→ https-proxy: HTTP request → 127.0.0.1:19000
→ Holesail client tunnel: P2P connection to remote peer
→ Remote peer: HTTP response
```
---
## Port Assignments
| Port range | Component | Purpose |
|------------|-----------|---------|
| 8443 | `https-proxy.js` | TLS-terminating HTTPS reverse proxy |
| 8442 | `connect-proxy.js` | HTTP CONNECT proxy (PAC target) |
| 19000+ | `holesail-manager.js` | Virtual host Holesail client tunnels |
| 2000020999 | `ssh-manager.js` | Holesail client tunnels for SSH sessions |
| 2100021999 | `ssh-manager.js` | WebSocket servers for xterm.js |
| 2200022999 | `rdp-manager.js` | Holesail client tunnels for RDP/VNC sessions |
| 2300023999 | `rdp-manager.js` | WebSocket servers for noVNC / RDP viewer |
---
## Data Persistence
`native-host/holesail-browser-storage/state.json` stores all application state:
```json
{
"version": 2,
"settings": {
"proxyPort": 8443,
"connectProxyPort": 8442,
"readyTimeoutMs": 0,
"notifyOnDisconnect": true,
"debug": false,
"disableOnFileUrls": false,
"backupRetention": 5
},
"nextServerId": 3,
"nextServiceTunnelId": 2,
"servers": [
{ "id": "server_1", "port": 3000, "host": "127.0.0.1", "secure": true, "udp": false }
],
"virtualHosts": [
{ "hostname": "myapp.hole.sail", "hsUrl": "hs://abc123..." }
],
"serviceTunnels": [
{ "id": "svc-1", "label": "Postgres", "hsUrl": "hs://def456...", "localPort": 5432 }
],
"sshConnections": [
{ "id": "ssh-abc123", "label": "My Server", "hsUrl": "hs://xyz...", "username": "admin" }
],
"rdpConnections": [
{ "id": "rdp-abc123", "label": "Work PC", "hsUrl": "hs://xyz...", "type": "vnc", "port": 5900 }
]
}
```
All state is owned by the native host. The extension reads state via `getState` / `getSettings` / `getSshConnections` / `getRdpConnections` and writes it via `updateSettings` / `setSshConnections` / `setRdpConnections`.
On first run after upgrading from a previous version, `holesail-persist.json` is automatically migrated to `state.json` and the old file is removed.