@@ -1,26 +1,27 @@
|
||||
# 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
|
||||
|
||||
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)
|
||||
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
|
||||
|
||||
All traffic is end-to-end encrypted via the Noise protocol. The local CA is only used for the browser↔proxy TLS leg.
|
||||
|
||||
## Features
|
||||
|
||||
- **Virtual Hosts** — browse any `hs://` URL as `https://name.hole.sail/`
|
||||
- **Server Tunnels** — expose a local port as an `hs://` key (TCP or UDP)
|
||||
- **Virtual Hosts** — browse any `hs://` URL as `https://name.hole.sail/` or any custom private TLD (e.g. `https://api.my.internal/`)
|
||||
- **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
|
||||
- **SSH** — in-browser SSH terminal via xterm.js, over a Holesail tunnel
|
||||
- **Remote Desktop** — VNC (noVNC) and RDP viewer in the browser, 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; passwords saved securely
|
||||
- **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
|
||||
|
||||
## Installation
|
||||
@@ -83,21 +84,23 @@ For a permanent install in regular Firefox, the extension must be signed by Mozi
|
||||
|
||||
## 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**
|
||||
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
|
||||
5. Navigate to `https://your-hostname.hole.sail/`
|
||||
4. Go to **Virtual Hosts** → add a hostname (e.g. `myapp.hole.sail`) and its `hs://` key
|
||||
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
|
||||
|
||||
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 |
|
||||
|------|-------------|
|
||||
| Overview | Status summary, connection count, proxy ports |
|
||||
| Virtual Hosts | Map `hs://` keys to `*.hole.sail` hostnames |
|
||||
| Server Tunnels | Expose local ports as `hs://` keys (TCP/UDP) |
|
||||
| Overview | Status summary, stat cards, scrollable lists for all data types, Quick Actions |
|
||||
| Virtual Hosts | Map `hs://` keys to hostnames on any private TLD |
|
||||
| Server Tunnels | Expose local ports as `hs://` keys (TCP/UDP), with optional label |
|
||||
| Service Tunnels | Forward remote `hs://` peers to local TCP ports |
|
||||
| Proxy & CA | Proxy port settings, CA install/status |
|
||||
| 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
|
||||
|
||||
**`*.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).
|
||||
|
||||
@@ -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
|
||||
- [Installation](docs/INSTALLATION.md) — detailed install guide for all platforms
|
||||
- [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
|
||||
- [Remote Desktop](docs/REMOTE-DESKTOP.md) — VNC and RDP over Holesail
|
||||
- [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. |
|
||||
| `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`. |
|
||||
| `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`. |
|
||||
@@ -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. |
|
||||
| `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`. |
|
||||
| `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`. |
|
||||
|
||||
## Proxy architecture
|
||||
@@ -237,7 +237,9 @@ All state is owned by the native host and persisted to `state.json` next to the
|
||||
},
|
||||
"nextServerId": 0,
|
||||
"nextServiceTunnelId": 0,
|
||||
"servers": [],
|
||||
"servers": [
|
||||
{ "id": "server_1", "port": 3000, "host": "127.0.0.1", "secure": true, "udp": false, "label": "My Web App" }
|
||||
],
|
||||
"virtualHosts": [
|
||||
{ "hostname": "myapp.hole.sail", "hsUrl": "hs://abc123..." },
|
||||
{ "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 }
|
||||
],
|
||||
"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": [
|
||||
{ "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 |
|
||||
|-----------------|--------|
|
||||
| `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.cert.pem` | Root CA certificate |
|
||||
| `certs/wildcard.hole.sail/key.pem` | Wildcard domain private key |
|
||||
| `certs/wildcard.hole.sail/cert.pem` | Wildcard domain certificate + CA chain |
|
||||
| `certs/wildcard.hole.sail/` | Wildcard cert for the default `.hole.sail` TLD |
|
||||
| `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
|
||||
|
||||
|
||||
@@ -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`
|
||||
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
|
||||
9. Downloads `Holesail-Browser-1.0.0.zip` and `Holesail-Browser-1.0.0.xpi` to `~/Downloads`
|
||||
10. Writes the native messaging manifest to all browser locations
|
||||
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. 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`)
|
||||
|
||||
@@ -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
|
||||
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
|
||||
6. Downloads `Holesail-Browser-1.0.0.zip` to `~/Downloads`
|
||||
7. Writes the native messaging manifest JSON and creates registry keys for Chrome, Chromium, and Firefox
|
||||
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. 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
|
||||
|
||||
| Directory | Contents |
|
||||
|-----------|----------|
|
||||
| `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.
|
||||
|
||||
|
||||
+29
-4
@@ -104,13 +104,16 @@ Start a server tunnel (expose a local port as an `hs://` key).
|
||||
"port": 3000,
|
||||
"host": "127.0.0.1",
|
||||
"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:**
|
||||
```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`
|
||||
|
||||
Check whether an `hs://` key is reachable on the DHT.
|
||||
@@ -194,14 +219,14 @@ Start an SSH session.
|
||||
"connectionId": "ssh-abc123",
|
||||
"hsUrl": "hs://abc123...",
|
||||
"username": "root",
|
||||
"password": "",
|
||||
"passwordB64": "cGFzc3dvcmQ=",
|
||||
"cols": 80,
|
||||
"rows": 24,
|
||||
"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:**
|
||||
```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)
|
||||
- **Width / Height** — initial display resolution
|
||||
- **Username** — (RDP only) login username
|
||||
- **Password** — optional; saved as base64 in `state.json` and used automatically on reconnect
|
||||
4. Click **Save**
|
||||
|
||||
### Connecting
|
||||
@@ -90,10 +91,13 @@ Start a new VNC or RDP session.
|
||||
"port": 5900,
|
||||
"width": 1280,
|
||||
"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:**
|
||||
```json
|
||||
{
|
||||
@@ -148,12 +152,15 @@ Save RDP/VNC connection definitions to `state.json`.
|
||||
"port": 5900,
|
||||
"width": 1280,
|
||||
"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
|
||||
@@ -233,13 +240,14 @@ RDP/VNC connections are stored in `state.json` under `rdpConnections`:
|
||||
"port": 5900,
|
||||
"width": 1280,
|
||||
"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.
|
||||
|
||||
- **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
|
||||
- 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
|
||||
|
||||
`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
|
||||
|
||||
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
|
||||
|
||||
@@ -65,7 +65,7 @@ If no public key is available and no password is saved, the terminal displays a
|
||||
### Connecting
|
||||
|
||||
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`
|
||||
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",
|
||||
"hsUrl": "hs://abc123...",
|
||||
"username": "root",
|
||||
"password": "",
|
||||
"passwordB64": "cGFzc3dvcmQ=",
|
||||
"cols": 80,
|
||||
"rows": 24,
|
||||
"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:**
|
||||
```json
|
||||
@@ -154,17 +154,25 @@ List active SSH sessions.
|
||||
|
||||
### `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:**
|
||||
```json
|
||||
{
|
||||
"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
|
||||
@@ -199,7 +207,7 @@ Ports are allocated sequentially and released when the session ends.
|
||||
|
||||
## 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
|
||||
{
|
||||
@@ -208,12 +216,15 @@ SSH connections are stored in `state.json` under `sshConnections`. Passwords are
|
||||
"id": "ssh-abc123",
|
||||
"label": "My Server",
|
||||
"hsUrl": "hs://abc123...",
|
||||
"username": "root"
|
||||
"username": "root",
|
||||
"passwordB64": "cGFzc3dvcmQ="
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
`passwordB64` is omitted when no password was saved for the connection.
|
||||
|
||||
---
|
||||
|
||||
## 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`
|
||||
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`
|
||||
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
|
||||
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
|
||||
|
||||
- Must end in `.hole.sail`
|
||||
- Must be a valid DNS label (lowercase letters, numbers, hyphens)
|
||||
- Examples: `myapp.hole.sail`, `dev-server.hole.sail`, `api.hole.sail`
|
||||
- The bare domain `hole.sail` is also routed through the proxy
|
||||
- Must have **at least 3 labels** (e.g. `myapp.hole.sail`, not `hole.sail`)
|
||||
- The TLD portion must be **two-tier** (e.g. `.hole.sail`, `.my.internal`) — single-label TLDs like `.holesail` are not allowed
|
||||
- Must **not** use a real public TLD (`.com`, `.net`, `.co.uk`, etc.)
|
||||
- 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
|
||||
|
||||
@@ -63,10 +65,11 @@ Server tunnels expose a local TCP (or UDP) port as an `hs://` key. Other peers c
|
||||
### Creating a server tunnel
|
||||
|
||||
1. Open the dashboard → **Server Tunnels**
|
||||
2. Enter the local port to expose (e.g. `3000`)
|
||||
3. Select **TCP** or **UDP**
|
||||
4. Optionally enable **Secure** mode (requires the connecting peer to have the key)
|
||||
5. Click **Start** — an `hs://` key is generated and displayed
|
||||
2. Optionally enter a **Label** (e.g. `My Web App`) to identify the tunnel
|
||||
3. Enter the local port to expose (e.g. `3000`)
|
||||
4. Select **TCP** or **UDP**
|
||||
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.
|
||||
|
||||
@@ -74,6 +77,7 @@ Share the `hs://` key with anyone who should be able to connect to your service.
|
||||
|
||||
| Parameter | Description |
|
||||
|-----------|-------------|
|
||||
| `label` | Optional human-readable name shown in the dashboard |
|
||||
| `port` | Local port to expose |
|
||||
| `host` | Local host to bind (default: `127.0.0.1`) |
|
||||
| `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.
|
||||
|
||||
### 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
|
||||
|
||||
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
|
||||
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.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user