@@ -1,26 +1,27 @@
|
|||||||
# Holesail Browser
|
# Holesail Browser
|
||||||
|
|
||||||
Browse P2P Holesail tunnels directly in your browser. Holesail Browser is a Chrome/Firefox extension paired with a native host that routes `*.hole.sail` domains through Holesail tunnels — with automatic TLS, no port-forwarding, and no central servers.
|
Browse P2P Holesail tunnels directly in your browser. Holesail Browser is a Chrome/Firefox extension paired with a native host that routes virtual host domains (e.g. `*.hole.sail` or any private TLD you choose) through Holesail tunnels — with automatic TLS, no port-forwarding, and no central servers.
|
||||||
|
|
||||||
## How it works
|
## How it works
|
||||||
|
|
||||||
1. You add a virtual host in the dashboard: `myapp.hole.sail` → `hs://abc123...`
|
1. You add a virtual host in the dashboard: `myapp.hole.sail` → `hs://abc123...`
|
||||||
2. The extension sets a PAC script that routes `*.hole.sail` → local CONNECT proxy (port 8442)
|
2. The extension sets a PAC script that routes `*.hole.sail` (and any other custom TLDs) → local CONNECT proxy (port 8442)
|
||||||
3. The CONNECT proxy pipes the raw TLS stream to the HTTPS proxy (port 8443)
|
3. The CONNECT proxy pipes the raw TLS stream to the HTTPS proxy (port 8443)
|
||||||
4. The HTTPS proxy terminates TLS (using a locally-trusted wildcard cert) and forwards HTTP to the Holesail tunnel
|
4. The HTTPS proxy reads the TLS ClientHello, extracts the SNI hostname, and presents a per-TLD wildcard cert (signed by the local CA)
|
||||||
5. The Holesail tunnel connects P2P to the remote peer over the DHT
|
5. The Holesail tunnel connects P2P to the remote peer over the DHT
|
||||||
|
|
||||||
All traffic is end-to-end encrypted via the Noise protocol. The local CA is only used for the browser↔proxy TLS leg.
|
All traffic is end-to-end encrypted via the Noise protocol. The local CA is only used for the browser↔proxy TLS leg.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- **Virtual Hosts** — browse any `hs://` URL as `https://name.hole.sail/`
|
- **Virtual Hosts** — browse any `hs://` URL as `https://name.hole.sail/` or any custom private TLD (e.g. `https://api.my.internal/`)
|
||||||
- **Server Tunnels** — expose a local port as an `hs://` key (TCP or UDP)
|
- **Custom TLDs** — use any private two-tier TLD, not just `.hole.sail`; PAC script and certificates are updated automatically
|
||||||
|
- **Server Tunnels** — expose a local port as an `hs://` key (TCP or UDP); supports an optional label for easy identification
|
||||||
- **Service Tunnels** — forward a remote `hs://` peer to a local TCP port
|
- **Service Tunnels** — forward a remote `hs://` peer to a local TCP port
|
||||||
- **SSH** — in-browser SSH terminal via xterm.js, over a Holesail tunnel
|
- **SSH** — in-browser SSH terminal via xterm.js, over a Holesail tunnel; supports public key, saved password, and interactive auth
|
||||||
- **Remote Desktop** — VNC (noVNC) and RDP viewer in the browser, over a Holesail tunnel
|
- **Remote Desktop** — VNC (noVNC) and RDP viewer in the browser, over a Holesail tunnel; passwords saved securely
|
||||||
- **Backups** — `tar.gz` snapshots of all state and certificates, with configurable retention
|
- **Backups** — `tar.gz` snapshots of all state and certificates, with configurable retention
|
||||||
- **Auto CA** — generates and installs a local root CA; signs a wildcard `*.hole.sail` cert
|
- **Auto CA** — generates and installs a local root CA; issues per-TLD wildcard certs on demand via JS-layer SNI
|
||||||
- **Persistent state** — all tunnels, connections, and settings survive restarts
|
- **Persistent state** — all tunnels, connections, and settings survive restarts
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
@@ -83,21 +84,23 @@ For a permanent install in regular Firefox, the extension must be signed by Mozi
|
|||||||
|
|
||||||
## First run
|
## First run
|
||||||
|
|
||||||
1. Click the Holesail Browser icon in your toolbar to open the dashboard
|
1. Click the Holesail Browser icon in your toolbar to open the **Overview** dashboard
|
||||||
2. Go to **Proxy & CA** → click **Install Root CA**
|
2. Go to **Proxy & CA** → click **Install Root CA**
|
||||||
3. **Fully quit and reopen Chrome** (Cmd+Q on macOS) for the CA trust to take effect
|
3. **Fully quit and reopen Chrome** (Cmd+Q on macOS) for the CA trust to take effect
|
||||||
4. Go to **Virtual Hosts** → add a hostname and `hs://` key
|
4. Go to **Virtual Hosts** → add a hostname (e.g. `myapp.hole.sail`) and its `hs://` key
|
||||||
5. Navigate to `https://your-hostname.hole.sail/`
|
5. Navigate to `https://myapp.hole.sail/`
|
||||||
|
|
||||||
> **Note:** The CA must be installed and Chrome must be restarted before `*.hole.sail` sites will load without a certificate warning.
|
> **Note:** The CA must be installed and Chrome must be restarted before virtual host sites will load without a certificate warning. You can use any private two-tier TLD — not just `.hole.sail`.
|
||||||
|
|
||||||
## Dashboard pages
|
## Dashboard pages
|
||||||
|
|
||||||
|
The dashboard uses a single topbar title per page. The **Overview** page is the landing page and features a sticky Quick Actions bar at the bottom with equal-height scrollable list cards for all data types.
|
||||||
|
|
||||||
| Page | Description |
|
| Page | Description |
|
||||||
|------|-------------|
|
|------|-------------|
|
||||||
| Overview | Status summary, connection count, proxy ports |
|
| Overview | Status summary, stat cards, scrollable lists for all data types, Quick Actions |
|
||||||
| Virtual Hosts | Map `hs://` keys to `*.hole.sail` hostnames |
|
| Virtual Hosts | Map `hs://` keys to hostnames on any private TLD |
|
||||||
| Server Tunnels | Expose local ports as `hs://` keys (TCP/UDP) |
|
| Server Tunnels | Expose local ports as `hs://` keys (TCP/UDP), with optional label |
|
||||||
| Service Tunnels | Forward remote `hs://` peers to local TCP ports |
|
| Service Tunnels | Forward remote `hs://` peers to local TCP ports |
|
||||||
| Proxy & CA | Proxy port settings, CA install/status |
|
| Proxy & CA | Proxy port settings, CA install/status |
|
||||||
| SSH | In-browser SSH terminal over Holesail |
|
| SSH | In-browser SSH terminal over Holesail |
|
||||||
@@ -118,7 +121,7 @@ For a permanent install in regular Firefox, the extension must be signed by Mozi
|
|||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
**`*.hole.sail` sites show a certificate error**
|
**Virtual host sites show a certificate error**
|
||||||
|
|
||||||
The root CA is not trusted, or Chrome was not restarted after installing it. Open the dashboard → Proxy & CA and check the CA status. If it shows "Not Installed", click **Install Root CA**, then fully quit and reopen Chrome (Cmd+Q).
|
The root CA is not trusted, or Chrome was not restarted after installing it. Open the dashboard → Proxy & CA and check the CA status. If it shows "Not Installed", click **Install Root CA**, then fully quit and reopen Chrome (Cmd+Q).
|
||||||
|
|
||||||
@@ -185,6 +188,7 @@ The built extension will be in `releases/Holesail-Browser-*.zip` and `.xpi`. Pla
|
|||||||
- [Architecture](docs/ARCHITECTURE.md) — system design, component overview, message flow
|
- [Architecture](docs/ARCHITECTURE.md) — system design, component overview, message flow
|
||||||
- [Installation](docs/INSTALLATION.md) — detailed install guide for all platforms
|
- [Installation](docs/INSTALLATION.md) — detailed install guide for all platforms
|
||||||
- [Tunneling](docs/TUNNELING.md) — virtual hosts, server tunnels, service tunnels
|
- [Tunneling](docs/TUNNELING.md) — virtual hosts, server tunnels, service tunnels
|
||||||
|
- [Virtual Hosts](docs/VIRTUAL-HOSTS.md) — custom TLDs, certificate generation, deep hostnames
|
||||||
- [SSH](docs/SSH.md) — in-browser SSH terminal
|
- [SSH](docs/SSH.md) — in-browser SSH terminal
|
||||||
- [Remote Desktop](docs/REMOTE-DESKTOP.md) — VNC and RDP over Holesail
|
- [Remote Desktop](docs/REMOTE-DESKTOP.md) — VNC and RDP over Holesail
|
||||||
- [Backups](docs/BACKUP.md) — backup and restore
|
- [Backups](docs/BACKUP.md) — backup and restore
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ Holesail Browser is composed of three parts: a browser extension, a native host
|
|||||||
|------|---------|
|
|------|---------|
|
||||||
| `index.mjs` | Entry point. Bootstraps Bare globals, creates the native messaging `messenger`, wires `handleMessage` from `host.js`. Handles `SIGTERM`/`SIGINT` for graceful shutdown. |
|
| `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. |
|
| `messenger.js` | Chrome/Firefox native messaging framing: 4-byte little-endian length prefix + UTF-8 JSON body. Max 1 MB per message. |
|
||||||
| `host.js` | Central command dispatcher. On startup: loads persisted state, starts both proxies, then restores all persisted tunnels asynchronously. Handles all message types from the extension. |
|
| `host.js` | Central command dispatcher. On startup: loads persisted state, starts both proxies, then restores all persisted tunnels asynchronously. Handles all message types from the extension, including `updateServiceTunnel` (stop + restart a service tunnel in one call). |
|
||||||
| `holesail-manager.js` | Manages all tunnel types (server, virtual host, service tunnel). Owns in-memory maps, port allocator (starting at 19000), and all persistence to `state.json`. |
|
| `holesail-manager.js` | Manages all tunnel types (server, virtual host, service tunnel). Owns in-memory maps, port allocator (starting at 19000), and all persistence to `state.json`. |
|
||||||
| `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. |
|
| `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`. |
|
| `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`. |
|
||||||
@@ -75,7 +75,7 @@ Holesail Browser is composed of three parts: a browser extension, a native host
|
|||||||
| `manifest.json` | Manifest V3. Permissions: `nativeMessaging`, `proxy`, `declarativeNetRequest`, `tabs`, `notifications`. Optional host permissions requested at runtime for custom TLDs. |
|
| `manifest.json` | Manifest V3. Permissions: `nativeMessaging`, `proxy`, `declarativeNetRequest`, `tabs`, `notifications`. Optional host permissions requested at runtime for custom TLDs. |
|
||||||
| `background.js` | Service worker. Connects to native host via `connectNative`. Sets PAC script dynamically — includes `*.hole.sail` plus any custom TLDs from virtual hosts. Reconnects with exponential backoff (100ms → 30s). Routes dashboard requests to native host. Broadcasts tunnel events to subscribed tabs. |
|
| `background.js` | Service worker. Connects to native host via `connectNative`. Sets PAC script dynamically — includes `*.hole.sail` plus any custom TLDs from virtual hosts. Reconnects with exponential backoff (100ms → 30s). Routes dashboard requests to native host. Broadcasts tunnel events to subscribed tabs. |
|
||||||
| `content.js` | Minimal content script. Relays `holesail-host-disconnect` to the page as a `CustomEvent`. |
|
| `content.js` | Minimal content script. Relays `holesail-host-disconnect` to the page as a `CustomEvent`. |
|
||||||
| `dashboard.html/js` | Full management UI. 10 pages: Overview, Virtual Hosts, Server Tunnels, Service Tunnels, Proxy & CA, SSH, Remote Desktop, Backups, Logs, Settings. |
|
| `dashboard.html/js` | Full management UI. 10 pages: Overview, Virtual Hosts, Server Tunnels, Service Tunnels, Proxy & CA, SSH, Remote Desktop, Backups, Logs, Settings. Each page title is shown only in the sticky topbar. The Overview page uses a flex layout: status bar → stat cards → equal-height scrollable list rows (Virtual Hosts + Server Tunnels on row 1; Service Tunnels + SSH + Remote Desktop on row 2) → sticky Quick Actions pinned at the bottom. |
|
||||||
| `wrong-domain.html` | Error page for `*.host.test` (common typo), redirected via `declarativeNetRequest`. |
|
| `wrong-domain.html` | Error page for `*.host.test` (common typo), redirected via `declarativeNetRequest`. |
|
||||||
|
|
||||||
## Proxy architecture
|
## Proxy architecture
|
||||||
@@ -237,7 +237,9 @@ All state is owned by the native host and persisted to `state.json` next to the
|
|||||||
},
|
},
|
||||||
"nextServerId": 0,
|
"nextServerId": 0,
|
||||||
"nextServiceTunnelId": 0,
|
"nextServiceTunnelId": 0,
|
||||||
"servers": [],
|
"servers": [
|
||||||
|
{ "id": "server_1", "port": 3000, "host": "127.0.0.1", "secure": true, "udp": false, "label": "My Web App" }
|
||||||
|
],
|
||||||
"virtualHosts": [
|
"virtualHosts": [
|
||||||
{ "hostname": "myapp.hole.sail", "hsUrl": "hs://abc123..." },
|
{ "hostname": "myapp.hole.sail", "hsUrl": "hs://abc123..." },
|
||||||
{ "hostname": "i.love.hole.sail", "hsUrl": "hs://def456..." },
|
{ "hostname": "i.love.hole.sail", "hsUrl": "hs://def456..." },
|
||||||
@@ -247,10 +249,10 @@ All state is owned by the native host and persisted to `state.json` next to the
|
|||||||
{ "id": "svc-1", "label": "Postgres", "hsUrl": "hs://def456...", "localPort": 5432 }
|
{ "id": "svc-1", "label": "Postgres", "hsUrl": "hs://def456...", "localPort": 5432 }
|
||||||
],
|
],
|
||||||
"sshConnections": [
|
"sshConnections": [
|
||||||
{ "id": "ssh-abc123", "label": "My Server", "hsUrl": "hs://xyz...", "username": "root" }
|
{ "id": "ssh-abc123", "label": "My Server", "hsUrl": "hs://xyz...", "username": "root", "passwordB64": "cGFzc3dvcmQ=" }
|
||||||
],
|
],
|
||||||
"rdpConnections": [
|
"rdpConnections": [
|
||||||
{ "id": "rdp-abc123", "label": "Work PC", "hsUrl": "hs://xyz...", "type": "vnc", "port": 5901, "width": 1280, "height": 720, "username": "" }
|
{ "id": "rdp-abc123", "label": "Work PC", "hsUrl": "hs://xyz...", "type": "vnc", "port": 5901, "width": 1280, "height": 720, "username": "", "passwordB64": "cGFzc3dvcmQ=" }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
+6
-4
@@ -8,13 +8,15 @@ Each backup archive contains:
|
|||||||
|
|
||||||
| Path in archive | Source |
|
| Path in archive | Source |
|
||||||
|-----------------|--------|
|
|-----------------|--------|
|
||||||
| `storage/state.json` | All virtual hosts, server tunnels, service tunnels, SSH connections, RDP connections, and settings |
|
| `storage/state.json` | All virtual hosts, server tunnels (with labels), service tunnels, SSH connections (with `passwordB64`), RDP connections (with `passwordB64`), and settings |
|
||||||
| `certs/ca.key.pem` | Root CA private key |
|
| `certs/ca.key.pem` | Root CA private key |
|
||||||
| `certs/ca.cert.pem` | Root CA certificate |
|
| `certs/ca.cert.pem` | Root CA certificate |
|
||||||
| `certs/wildcard.hole.sail/key.pem` | Wildcard domain private key |
|
| `certs/wildcard.hole.sail/` | Wildcard cert for the default `.hole.sail` TLD |
|
||||||
| `certs/wildcard.hole.sail/cert.pem` | Wildcard domain certificate + CA chain |
|
| `certs/wildcard.<parent>/` | One directory per custom TLD parent (e.g. `wildcard.my.internal/`, `wildcard.haha.wooo/`) |
|
||||||
|
|
||||||
Backups do **not** include the binary itself, the log file, or other backup archives.
|
The entire `holesail-browser-certs/` directory is archived — all wildcard cert directories are included, not just the default one. Backups do **not** include the binary itself, the log file, or other backup archives.
|
||||||
|
|
||||||
|
The default backup retention is **5 backups** (configurable in Settings via `backupRetention`).
|
||||||
|
|
||||||
## Storage location
|
## Storage location
|
||||||
|
|
||||||
|
|||||||
@@ -30,8 +30,9 @@ Or download `install.ps1` from the [latest release](https://git.ssh.surf/snxrave
|
|||||||
6. Extracts the binary to `~/.holesail-browser/holesail-browser-host`
|
6. Extracts the binary to `~/.holesail-browser/holesail-browser-host`
|
||||||
7. **Restores user data** — the preserved storage and certs directories are copied back, so all tunnels, connections, settings, and certificates survive the upgrade
|
7. **Restores user data** — the preserved storage and certs directories are copied back, so all tunnels, connections, settings, and certificates survive the upgrade
|
||||||
8. **macOS only:** removes Gatekeeper quarantine, ad-hoc signs the binary, pre-runs it to extract `.bare` native addons, then signs all extracted addons
|
8. **macOS only:** removes Gatekeeper quarantine, ad-hoc signs the binary, pre-runs it to extract `.bare` native addons, then signs all extracted addons
|
||||||
9. Downloads `Holesail-Browser-1.0.0.zip` and `Holesail-Browser-1.0.0.xpi` to `~/Downloads`
|
9. **Removes old extension files** — any existing `Holesail-Browser-*.zip` and `Holesail-Browser-*.xpi` files in `~/Downloads` are deleted before downloading the new version
|
||||||
10. Writes the native messaging manifest to all browser locations
|
10. Downloads `Holesail-Browser-1.0.0.zip` and `Holesail-Browser-1.0.0.xpi` to `~/Downloads`
|
||||||
|
11. Writes the native messaging manifest to all browser locations
|
||||||
|
|
||||||
### Windows (`install.ps1`)
|
### Windows (`install.ps1`)
|
||||||
|
|
||||||
@@ -40,15 +41,16 @@ Or download `install.ps1` from the [latest release](https://git.ssh.surf/snxrave
|
|||||||
3. Removes the previous installation directory and old registry keys
|
3. Removes the previous installation directory and old registry keys
|
||||||
4. Downloads `holesail-browser-host-win32-x64.zip` and extracts the `.exe`
|
4. Downloads `holesail-browser-host-win32-x64.zip` and extracts the `.exe`
|
||||||
5. **Restores user data** — the preserved directories are copied back into the new installation
|
5. **Restores user data** — the preserved directories are copied back into the new installation
|
||||||
6. Downloads `Holesail-Browser-1.0.0.zip` to `~/Downloads`
|
6. **Removes old extension files** — any existing `Holesail-Browser-*.zip` and `Holesail-Browser-*.xpi` files in `%USERPROFILE%\Downloads` are deleted before downloading the new version
|
||||||
7. Writes the native messaging manifest JSON and creates registry keys for Chrome, Chromium, and Firefox
|
7. Downloads `Holesail-Browser-1.0.0.zip` and `Holesail-Browser-1.0.0.xpi` to `%USERPROFILE%\Downloads`
|
||||||
|
8. Writes the native messaging manifest JSON and creates registry keys for Chrome, Chromium, and Firefox
|
||||||
|
|
||||||
### What is preserved across upgrades
|
### What is preserved across upgrades
|
||||||
|
|
||||||
| Directory | Contents |
|
| Directory | Contents |
|
||||||
|-----------|----------|
|
|-----------|----------|
|
||||||
| `holesail-browser-storage/` | `state.json` (all virtual hosts, tunnels, SSH/RDP connections, settings), backup archives |
|
| `holesail-browser-storage/` | `state.json` (all virtual hosts, tunnels, SSH/RDP connections, settings), backup archives |
|
||||||
| `holesail-browser-certs/` | Root CA key and certificate, wildcard `*.hole.sail` domain cert |
|
| `holesail-browser-certs/` | Root CA key and certificate, all wildcard domain certs (one per custom TLD parent, e.g. `wildcard.hole.sail/`, `wildcard.my.internal/`) |
|
||||||
|
|
||||||
The binary itself is always replaced with the new version. If this is a fresh install (no previous `~/.holesail-browser/` directory), the preserve/restore steps are skipped.
|
The binary itself is always replaced with the new version. If this is a fresh install (no previous `~/.holesail-browser/` directory), the preserve/restore steps are skipped.
|
||||||
|
|
||||||
|
|||||||
+29
-4
@@ -104,13 +104,16 @@ Start a server tunnel (expose a local port as an `hs://` key).
|
|||||||
"port": 3000,
|
"port": 3000,
|
||||||
"host": "127.0.0.1",
|
"host": "127.0.0.1",
|
||||||
"secure": true,
|
"secure": true,
|
||||||
"udp": false
|
"udp": false,
|
||||||
|
"label": "My Web App"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`label` is optional. When provided it is shown in the dashboard and persisted in `state.json`.
|
||||||
|
|
||||||
**Response payload:**
|
**Response payload:**
|
||||||
```json
|
```json
|
||||||
{ "ok": true, "serverId": "server_1", "hsUrl": "hs://abc123...", "state": "ready" }
|
{ "ok": true, "serverId": "server_1", "url": "hs://abc123...", "port": 3000, "host": "127.0.0.1", "secure": true, "udp": false, "label": "My Web App" }
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -168,6 +171,28 @@ Stop a service tunnel.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
### `updateServiceTunnel`
|
||||||
|
|
||||||
|
Stop and restart a service tunnel with the same (or updated) parameters. Used by the dashboard **Reconnect** button for tunnels in error or closed state.
|
||||||
|
|
||||||
|
**Request payload:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"tunnelId": "svc-1",
|
||||||
|
"label": "Postgres",
|
||||||
|
"hsUrl": "hs://abc123...",
|
||||||
|
"localPort": 5432
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response payload:** Same as `startServiceTunnel`.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{ "ok": true, "tunnelId": "svc-1", "localPort": 5432, "state": "ready" }
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
### `lookupTunnel`
|
### `lookupTunnel`
|
||||||
|
|
||||||
Check whether an `hs://` key is reachable on the DHT.
|
Check whether an `hs://` key is reachable on the DHT.
|
||||||
@@ -194,14 +219,14 @@ Start an SSH session.
|
|||||||
"connectionId": "ssh-abc123",
|
"connectionId": "ssh-abc123",
|
||||||
"hsUrl": "hs://abc123...",
|
"hsUrl": "hs://abc123...",
|
||||||
"username": "root",
|
"username": "root",
|
||||||
"password": "",
|
"passwordB64": "cGFzc3dvcmQ=",
|
||||||
"cols": 80,
|
"cols": 80,
|
||||||
"rows": 24,
|
"rows": 24,
|
||||||
"label": "My Server"
|
"label": "My Server"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
`password` is optional. If provided, it is delivered to SSH automatically via `SSH_ASKPASS` and a named FIFO — it is never written to disk or passed on the command line. If omitted, SSH tries public key auth first; if that fails, a password prompt appears in the xterm.js terminal.
|
`passwordB64` is optional. When present it is a base64-encoded UTF-8 password (`btoa(unescape(encodeURIComponent(password)))`). It is delivered to SSH via `SSH_ASKPASS` and a named FIFO — never written to disk separately or passed on the command line. If omitted, SSH tries public key auth first; if that fails, a password prompt appears in the xterm.js terminal.
|
||||||
|
|
||||||
**Response payload:**
|
**Response payload:**
|
||||||
```json
|
```json
|
||||||
|
|||||||
+12
-4
@@ -61,6 +61,7 @@ For RDP, `node-rdpjs-2` handles the RDP protocol in the native host. Bitmap upda
|
|||||||
- **Port** — remote port (default: 5900 for VNC, 3389 for RDP)
|
- **Port** — remote port (default: 5900 for VNC, 3389 for RDP)
|
||||||
- **Width / Height** — initial display resolution
|
- **Width / Height** — initial display resolution
|
||||||
- **Username** — (RDP only) login username
|
- **Username** — (RDP only) login username
|
||||||
|
- **Password** — optional; saved as base64 in `state.json` and used automatically on reconnect
|
||||||
4. Click **Save**
|
4. Click **Save**
|
||||||
|
|
||||||
### Connecting
|
### Connecting
|
||||||
@@ -90,10 +91,13 @@ Start a new VNC or RDP session.
|
|||||||
"port": 5900,
|
"port": 5900,
|
||||||
"width": 1280,
|
"width": 1280,
|
||||||
"height": 720,
|
"height": 720,
|
||||||
"username": ""
|
"username": "",
|
||||||
|
"passwordB64": "cGFzc3dvcmQ="
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`passwordB64` is optional. When present it is a base64-encoded UTF-8 password string used for RDP authentication.
|
||||||
|
|
||||||
**Response payload:**
|
**Response payload:**
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
@@ -148,12 +152,15 @@ Save RDP/VNC connection definitions to `state.json`.
|
|||||||
"port": 5900,
|
"port": 5900,
|
||||||
"width": 1280,
|
"width": 1280,
|
||||||
"height": 720,
|
"height": 720,
|
||||||
"username": ""
|
"username": "",
|
||||||
|
"passwordB64": "cGFzc3dvcmQ="
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`passwordB64` is optional. When present it is a base64-encoded UTF-8 password string (`btoa(unescape(encodeURIComponent(password)))`). It is persisted in `state.json` and used automatically when starting a session.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## WebSocket protocol
|
## WebSocket protocol
|
||||||
@@ -233,13 +240,14 @@ RDP/VNC connections are stored in `state.json` under `rdpConnections`:
|
|||||||
"port": 5900,
|
"port": 5900,
|
||||||
"width": 1280,
|
"width": 1280,
|
||||||
"height": 720,
|
"height": 720,
|
||||||
"username": ""
|
"username": "",
|
||||||
|
"passwordB64": "cGFzc3dvcmQ="
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Connections are restored on startup. Active sessions are not persisted — they must be reconnected after a native host restart.
|
`passwordB64` is omitted when no password was saved. Connections are restored on startup. Active sessions are not persisted — they must be reconnected after a native host restart.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+7
-2
@@ -133,7 +133,7 @@ This prevents accidentally routing real internet traffic through the local proxy
|
|||||||
SSH sessions use the system `ssh` binary. Authentication is tried in order: public key, then password.
|
SSH sessions use the system `ssh` binary. Authentication is tried in order: public key, then password.
|
||||||
|
|
||||||
- **Public key auth** — keys from `~/.ssh/` and `ssh-agent` are used automatically; no password prompt appears if a key is accepted
|
- **Public key auth** — keys from `~/.ssh/` and `ssh-agent` are used automatically; no password prompt appears if a key is accepted
|
||||||
- **Password auth** — passwords are optionally saved as base64 in `state.json` (in memory only during the session); a saved password is delivered to SSH via `SSH_ASKPASS` and a named FIFO pipe (not via the process environment or command line); if no password is saved and key auth fails, an interactive password prompt appears in xterm.js and is delivered the same way
|
- **Password auth** — passwords are optionally saved as `passwordB64` (base64-encoded) in `state.json` and survive native host restarts; a saved password is delivered to SSH via `SSH_ASKPASS` and a named FIFO pipe (not via the process environment or command line); if no password is saved and key auth fails, an interactive password prompt appears in xterm.js and is delivered the same way
|
||||||
- **`StrictHostKeyChecking=no` / `UserKnownHostsFile=/dev/null`** — host key verification is disabled for Holesail tunnels because the remote host's identity is already established by the `hs://` public key (Noise mutual authentication at the tunnel layer); adding known-hosts entries for `127.0.0.1` would be misleading since the port changes per session
|
- **`StrictHostKeyChecking=no` / `UserKnownHostsFile=/dev/null`** — host key verification is disabled for Holesail tunnels because the remote host's identity is already established by the `hs://` public key (Noise mutual authentication at the tunnel layer); adding known-hosts entries for `127.0.0.1` would be misleading since the port changes per session
|
||||||
- The Holesail tunnel provides transport; `ssh`'s own encryption is layered on top, giving double encryption
|
- The Holesail tunnel provides transport; `ssh`'s own encryption is layered on top, giving double encryption
|
||||||
|
|
||||||
@@ -141,7 +141,12 @@ SSH sessions use the system `ssh` binary. Authentication is tried in order: publ
|
|||||||
|
|
||||||
## Tunnel state storage
|
## Tunnel state storage
|
||||||
|
|
||||||
`state.json` contains `hs://` keys for all configured tunnels. These keys are the public keys of remote peers — they are not secret. However, `state.json` also contains the server tunnel keypairs (which allow others to connect to your exposed services) and optionally saved SSH/RDP passwords (base64-encoded). Protect `~/.holesail-browser/` accordingly.
|
`state.json` contains `hs://` keys for all configured tunnels. These keys are the public keys of remote peers — they are not secret. However, `state.json` also contains:
|
||||||
|
|
||||||
|
- **Server tunnel keypairs** — the `hs://` URL for each server tunnel is derived from a keypair that is preserved in state; anyone with this URL can connect to your exposed service
|
||||||
|
- **`passwordB64`** — SSH and RDP connection passwords are stored as base64-encoded strings under `sshConnections[].passwordB64` and `rdpConnections[].passwordB64`; server tunnel `label` fields are also stored but are not sensitive
|
||||||
|
|
||||||
|
Protect `~/.holesail-browser/` accordingly.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+19
-8
@@ -34,7 +34,7 @@ If you have a key in `~/.ssh/` (e.g. `id_rsa`, `id_ed25519`) or loaded in `ssh-a
|
|||||||
|
|
||||||
### 2. Password — saved on the connection
|
### 2. Password — saved on the connection
|
||||||
|
|
||||||
If you enter a password when saving the connection, it is stored in memory for the session (never written to disk). When connecting, the password is delivered to SSH automatically via a secure helper mechanism — no prompt appears.
|
If you enter a password when saving the connection, it is stored as base64 (`passwordB64`) in `state.json` and survives native host restarts. When connecting, the password is delivered to SSH automatically via `SSH_ASKPASS` and a named FIFO — no prompt appears and the password is never passed on the command line or in the environment.
|
||||||
|
|
||||||
### 3. Password — typed interactively
|
### 3. Password — typed interactively
|
||||||
|
|
||||||
@@ -65,7 +65,7 @@ If no public key is available and no password is saved, the terminal displays a
|
|||||||
### Connecting
|
### Connecting
|
||||||
|
|
||||||
1. Click **Connect** next to a saved connection
|
1. Click **Connect** next to a saved connection
|
||||||
2. An xterm.js terminal opens in the dashboard
|
2. An xterm.js terminal opens in the dashboard and is automatically focused — you can start typing immediately
|
||||||
3. The native host starts a Holesail tunnel to the remote peer, then spawns `ssh`
|
3. The native host starts a Holesail tunnel to the remote peer, then spawns `ssh`
|
||||||
4. If authentication requires a password and none is saved, a prompt appears in the terminal
|
4. If authentication requires a password and none is saved, a prompt appears in the terminal
|
||||||
|
|
||||||
@@ -91,14 +91,14 @@ Start a new SSH session.
|
|||||||
"connectionId": "ssh-abc123",
|
"connectionId": "ssh-abc123",
|
||||||
"hsUrl": "hs://abc123...",
|
"hsUrl": "hs://abc123...",
|
||||||
"username": "root",
|
"username": "root",
|
||||||
"password": "",
|
"passwordB64": "cGFzc3dvcmQ=",
|
||||||
"cols": 80,
|
"cols": 80,
|
||||||
"rows": 24,
|
"rows": 24,
|
||||||
"label": "My Server"
|
"label": "My Server"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
The `password` field is optional. If omitted or empty, SSH will use public key auth or prompt interactively.
|
`passwordB64` is optional. When present it is a base64-encoded UTF-8 password (`btoa(unescape(encodeURIComponent(password)))`). If omitted or empty, SSH will use public key auth or prompt interactively in the terminal.
|
||||||
|
|
||||||
**Response payload:**
|
**Response payload:**
|
||||||
```json
|
```json
|
||||||
@@ -154,17 +154,25 @@ List active SSH sessions.
|
|||||||
|
|
||||||
### `setSshConnections`
|
### `setSshConnections`
|
||||||
|
|
||||||
Save SSH connection definitions to `state.json`. Passwords are stripped before saving — they are session-only.
|
Save SSH connection definitions to `state.json`.
|
||||||
|
|
||||||
**Request payload:**
|
**Request payload:**
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"connections": [
|
"connections": [
|
||||||
{ "id": "ssh-abc123", "label": "My Server", "hsUrl": "hs://abc...", "username": "root" }
|
{
|
||||||
|
"id": "ssh-abc123",
|
||||||
|
"label": "My Server",
|
||||||
|
"hsUrl": "hs://abc...",
|
||||||
|
"username": "root",
|
||||||
|
"passwordB64": "cGFzc3dvcmQ="
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`passwordB64` is optional. When present it is a base64-encoded UTF-8 password string (`btoa(unescape(encodeURIComponent(password)))`). It is persisted in `state.json` and delivered to SSH via `SSH_ASKPASS` + a named FIFO on each connection — never via the command line or environment.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## WebSocket protocol
|
## WebSocket protocol
|
||||||
@@ -199,7 +207,7 @@ Ports are allocated sequentially and released when the session ends.
|
|||||||
|
|
||||||
## Saved connections
|
## Saved connections
|
||||||
|
|
||||||
SSH connections are stored in `state.json` under `sshConnections`. Passwords are **never** written to disk — they are held in memory for the current session only and must be re-entered after a native host restart.
|
SSH connections are stored in `state.json` under `sshConnections`. If a password was saved, it is stored as `passwordB64` (base64-encoded) and survives native host restarts.
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
@@ -208,12 +216,15 @@ SSH connections are stored in `state.json` under `sshConnections`. Passwords are
|
|||||||
"id": "ssh-abc123",
|
"id": "ssh-abc123",
|
||||||
"label": "My Server",
|
"label": "My Server",
|
||||||
"hsUrl": "hs://abc123...",
|
"hsUrl": "hs://abc123...",
|
||||||
"username": "root"
|
"username": "root",
|
||||||
|
"passwordB64": "cGFzc3dvcmQ="
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`passwordB64` is omitted when no password was saved for the connection.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|||||||
+17
-9
@@ -9,7 +9,7 @@ When you navigate to `https://myapp.hole.sail/`:
|
|||||||
1. The PAC script (set by the extension) matches `*.hole.sail` and routes the request to `PROXY 127.0.0.1:8442`
|
1. The PAC script (set by the extension) matches `*.hole.sail` and routes the request to `PROXY 127.0.0.1:8442`
|
||||||
2. Chrome sends `CONNECT myapp.hole.sail:443 HTTP/1.1` to the CONNECT proxy
|
2. Chrome sends `CONNECT myapp.hole.sail:443 HTTP/1.1` to the CONNECT proxy
|
||||||
3. The CONNECT proxy replies `200 Connection established` and pipes the stream to `127.0.0.1:8443`
|
3. The CONNECT proxy replies `200 Connection established` and pipes the stream to `127.0.0.1:8443`
|
||||||
4. The HTTPS proxy terminates TLS using the wildcard `*.hole.sail` cert (signed by the local CA)
|
4. The HTTPS proxy reads the TLS ClientHello, extracts the SNI hostname (`myapp.hole.sail`), derives the wildcard parent (`hole.sail`), and presents the `*.hole.sail` wildcard cert signed by the local CA
|
||||||
5. The HTTPS proxy reads the `Host` header, looks up the Holesail tunnel for `myapp.hole.sail`, and proxies the HTTP request to the tunnel's local port
|
5. The HTTPS proxy reads the `Host` header, looks up the Holesail tunnel for `myapp.hole.sail`, and proxies the HTTP request to the tunnel's local port
|
||||||
6. The Holesail client tunnel forwards the request to the remote peer over the DHT
|
6. The Holesail client tunnel forwards the request to the remote peer over the DHT
|
||||||
|
|
||||||
@@ -27,10 +27,12 @@ Virtual hosts map an `hs://` key to a `*.hole.sail` hostname. The browser naviga
|
|||||||
|
|
||||||
### Hostname rules
|
### Hostname rules
|
||||||
|
|
||||||
- Must end in `.hole.sail`
|
- Must have **at least 3 labels** (e.g. `myapp.hole.sail`, not `hole.sail`)
|
||||||
- Must be a valid DNS label (lowercase letters, numbers, hyphens)
|
- The TLD portion must be **two-tier** (e.g. `.hole.sail`, `.my.internal`) — single-label TLDs like `.holesail` are not allowed
|
||||||
- Examples: `myapp.hole.sail`, `dev-server.hole.sail`, `api.hole.sail`
|
- Must **not** use a real public TLD (`.com`, `.net`, `.co.uk`, etc.)
|
||||||
- The bare domain `hole.sail` is also routed through the proxy
|
- Each label may contain only lowercase letters, digits, and hyphens; no leading or trailing hyphens
|
||||||
|
- Any depth is supported: `myapp.hole.sail`, `i.love.hole.sail`, `api.v2.my.internal`
|
||||||
|
- You are not limited to `.hole.sail` — any valid private two-tier TLD works (see [VIRTUAL-HOSTS.md](VIRTUAL-HOSTS.md))
|
||||||
|
|
||||||
### Port allocation
|
### Port allocation
|
||||||
|
|
||||||
@@ -63,10 +65,11 @@ Server tunnels expose a local TCP (or UDP) port as an `hs://` key. Other peers c
|
|||||||
### Creating a server tunnel
|
### Creating a server tunnel
|
||||||
|
|
||||||
1. Open the dashboard → **Server Tunnels**
|
1. Open the dashboard → **Server Tunnels**
|
||||||
2. Enter the local port to expose (e.g. `3000`)
|
2. Optionally enter a **Label** (e.g. `My Web App`) to identify the tunnel
|
||||||
3. Select **TCP** or **UDP**
|
3. Enter the local port to expose (e.g. `3000`)
|
||||||
4. Optionally enable **Secure** mode (requires the connecting peer to have the key)
|
4. Select **TCP** or **UDP**
|
||||||
5. Click **Start** — an `hs://` key is generated and displayed
|
5. Optionally enable **Secure** mode (requires the connecting peer to have the key)
|
||||||
|
6. Click **Start** — an `hs://` key is generated and displayed
|
||||||
|
|
||||||
Share the `hs://` key with anyone who should be able to connect to your service.
|
Share the `hs://` key with anyone who should be able to connect to your service.
|
||||||
|
|
||||||
@@ -74,6 +77,7 @@ Share the `hs://` key with anyone who should be able to connect to your service.
|
|||||||
|
|
||||||
| Parameter | Description |
|
| Parameter | Description |
|
||||||
|-----------|-------------|
|
|-----------|-------------|
|
||||||
|
| `label` | Optional human-readable name shown in the dashboard |
|
||||||
| `port` | Local port to expose |
|
| `port` | Local port to expose |
|
||||||
| `host` | Local host to bind (default: `127.0.0.1`) |
|
| `host` | Local host to bind (default: `127.0.0.1`) |
|
||||||
| `udp` | Use UDP instead of TCP (default: `false`) |
|
| `udp` | Use UDP instead of TCP (default: `false`) |
|
||||||
@@ -116,6 +120,10 @@ Use cases:
|
|||||||
|
|
||||||
Service tunnels are saved to `state.json` and restored on startup.
|
Service tunnels are saved to `state.json` and restored on startup.
|
||||||
|
|
||||||
|
### Reconnecting a service tunnel
|
||||||
|
|
||||||
|
If a service tunnel enters an error or closed state, click **Reconnect** in the dashboard. This sends an `updateServiceTunnel` command to the native host, which stops the old tunnel (if any) and starts a fresh one with the same parameters.
|
||||||
|
|
||||||
### Stopping a service tunnel
|
### Stopping a service tunnel
|
||||||
|
|
||||||
Open the dashboard → **Service Tunnels** → click **Stop**.
|
Open the dashboard → **Service Tunnels** → click **Stop**.
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ The HTTPS proxy uses **JS-layer SNI** to select the right certificate per connec
|
|||||||
4. `certificate-authority.getOrCreateWildcardCert(parent)` returns an existing cert or generates a new one signed by the local root CA
|
4. `certificate-authority.getOrCreateWildcardCert(parent)` returns an existing cert or generates a new one signed by the local root CA
|
||||||
5. A `bare-tls.Socket` is created with that cert and the TLS handshake proceeds
|
5. A `bare-tls.Socket` is created with that cert and the TLS handshake proceeds
|
||||||
|
|
||||||
Certs are stored in `~/.holesail-browser/holesail-browser-certs/wildcard.<parent>/` and reused on subsequent connections. They are valid for 1 year and auto-renewed on expiry.
|
Certs are stored in `~/.holesail-browser/holesail-browser-certs/wildcard.<parent>/` (the `*` in the wildcard is stored literally as `wildcard` in the directory name, e.g. `wildcard.hole.sail/`) and reused on subsequent connections. They are valid for 1 year and auto-renewed on expiry.
|
||||||
|
|
||||||
No proxy restart is needed when adding new TLDs — the cert is generated on the first connection.
|
No proxy restart is needed when adding new TLDs — the cert is generated on the first connection.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user