@@ -14,6 +14,9 @@ All notable changes to Holesail Browser are documented here.
|
||||
**Device sync — incremental apply**
|
||||
- When syncing, the native host now applies only changes instead of stopping all tunnels and restarting them. Removed tunnels are stopped, new or changed tunnels are started or restarted, and unchanged tunnels keep running. This avoids unnecessary tunnel restarts when state is synced from another device. See [Device sync](docs/SYNC.md).
|
||||
|
||||
**Device sync — pairing and live updates**
|
||||
- When a device links with an invite, it waits for the master’s state to replicate (retries for a few seconds) and only applies when the snapshot has content, so the master’s state is preserved. All live changes (adds, edits, deletions) are applied; the host only skips applying when the incoming snapshot is empty and local state already has content, so empty state cannot overwrite real state. See [Device sync](docs/SYNC.md).
|
||||
|
||||
### Added
|
||||
|
||||
**Firefox support**
|
||||
|
||||
@@ -81,7 +81,7 @@ Holesail Browser is composed of three parts: a browser extension, a native host
|
||||
|------|---------|
|
||||
| `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. |
|
||||
| `backup-manager.js` | Creates/restores `tar.gz` backups of storage (state, `autopass-identity.json` when linked; **excludes** `autopass/` directory) and all certificates. Quiesces sync before copy/restore; after restore removes `autopass/` so autopass can recreate it from the identity. Supports create, list, restore, delete, and auto-prune by retention count. |
|
||||
| `sync-manager.js` | Syncs state (excluding certs) across linked devices via autopass. One device is the Master (creator of the sync group); any peer can create invites for the same group (e.g. when Master is offline). Handles `getSyncStatus` (includes `isMaster`), `createSyncInvite` (same group when linked, new group when not), `pairWithInvite`; pushes state on save, pulls on autopass `update` and applies incrementally. Exposes `closeSyncForBackup()` for backup-manager. |
|
||||
| `sync-manager.js` | Syncs state (excluding certs) across linked devices via autopass. One device is the Master (creator of the sync group); any peer can create invites for the same group (e.g. when Master is offline). Handles `getSyncStatus` (includes `isMaster`), `createSyncInvite` (same group when linked, new group when not), `pairWithInvite`; pushes state on save, pulls on autopass `update` and applies incrementally. At pair time the joining device retries until it receives a non-empty snapshot so the master’s state is preserved; when applying remote updates, skips only when the incoming snapshot is empty and local state has content (so all live adds, edits, and deletions sync). Exposes `closeSyncForBackup()` for backup-manager. |
|
||||
| `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. |
|
||||
|
||||
|
||||
+2
-2
@@ -535,7 +535,7 @@ Return whether this device is linked for sync, optional invite/last-synced info,
|
||||
}
|
||||
```
|
||||
|
||||
When linked, `deviceId` is a short hex ID for this device’s writer key, `deviceName` is the OS hostname of this device, and `syncGroupId` is a short hex ID for the shared discovery key. `isMaster` is true only for the device that created the sync group. `linkedDevices` is populated from the autopass active writers; each entry includes `name` (hostname) when known from synced state, so other peers’ hostnames appear after they have replicated at least once. It may contain only this device until other peers have replicated.
|
||||
When linked, `deviceId` is a short hex ID for this device’s writer key, `deviceName` is the OS hostname of this device, and `syncGroupId` is a short hex ID for the shared discovery key. `isMaster` is true only for the device that created the sync group. `linkedDevices` is populated from the autopass active writers; each entry includes `name` (hostname) when known from synced state, so other peers’ hostnames appear after they have replicated at least once. It may contain only this device until other peers have replicated. Remote state updates are applied live (adds, edits, deletions); the host only skips applying when the incoming snapshot is empty and local state already has content.
|
||||
|
||||
---
|
||||
|
||||
@@ -556,7 +556,7 @@ On error: `{ "ok": false, "error": "..." }`
|
||||
|
||||
### `pairWithInvite`
|
||||
|
||||
Link this device to another by redeeming an invite. After pairing, state is pulled from the other device and applied; only changed tunnels are started or restarted, so unchanged tunnels stay up. A `syncApplied` event is emitted so the dashboard can refresh.
|
||||
Link this device to another by redeeming an invite. The joining device waits for the master’s state to replicate (retries for a few seconds) and only applies when the snapshot has content, so the master’s state is preserved. After pairing, state is pulled and applied; only changed tunnels are started or restarted. Thereafter all live changes (adds, edits, deletions) sync in both directions. A `syncApplied` event is emitted when state was applied so the dashboard can refresh.
|
||||
|
||||
**Request payload:**
|
||||
```json
|
||||
|
||||
+7
-1
@@ -40,9 +40,13 @@ When a linked device goes offline (e.g. browser closed, machine asleep), it rema
|
||||
## Behaviour after linking
|
||||
|
||||
- **Push:** Any change on a device (add/remove virtual host, edit settings, etc.) is saved to disk and pushed to the sync group. Other linked devices receive the update and apply it (only changed tunnels are started or restarted).
|
||||
- **Pull:** When an update is received from another device, the native host diffs the synced state against the current state and applies only the changes: tunnels that were removed are stopped, new or changed tunnels are started or restarted, and unchanged tunnels keep running. The merged state is written to `state.json`. The dashboard refreshes when it receives the `syncApplied` event.
|
||||
- **Pull:** When an update is received from another device, the native host diffs the synced state against the current state and applies only the changes: tunnels that were removed are stopped, new or changed tunnels are started or restarted, and unchanged tunnels keep running. The merged state is written to `state.json`. The dashboard refreshes when it receives the `syncApplied` event. **All** remote changes are applied (adds, edits, and deletions). The only exception: if the incoming snapshot is **empty** (no servers, virtual hosts, or service tunnels) and this device already has content, the update is skipped so that a bad merge or cold peer cannot overwrite real state with empty.
|
||||
- **Persistence:** The link is stored in `holesail-browser-storage/autopass-identity.json`. After closing the browser or restarting the native host, both devices remain linked and continue syncing when the host runs.
|
||||
|
||||
## At link time (pairing)
|
||||
|
||||
When you **Link device** with an invite, the joining device waits briefly (a few seconds) for the master’s state to replicate. It only applies state when the snapshot has content (servers, virtual hosts, or service tunnels). This ensures the master’s state is preserved and the new device receives it; if replication is slow, the peer retries automatically. After that, live changes on either device sync in both directions.
|
||||
|
||||
## Conflict semantics
|
||||
|
||||
Sync uses a **single state blob** per sync group. Updates are **last-write-wins**: the most recent write overwrites the whole state. If you edit different things on both devices at the same time, one device’s changes can overwrite the other’s. For best results, avoid editing the same lists on both devices simultaneously.
|
||||
@@ -66,4 +70,6 @@ Backups (Dashboard → Backups) **include** `autopass-identity.json` when linked
|
||||
|
||||
**Not linked after restart** — After restarting the browser or native host, the Sync page may show "Not linked" if the sync data could not be reopened (e.g. the host was stopped before sync closed cleanly). You can **Create invite** to start a new sync group (this device will have a fresh identity; share the new invite with other devices to re-link them), or paste an invite from another device to re-join the existing group.
|
||||
|
||||
**State stays at 0 after linking** — The joining device retries for a few seconds to fetch the master’s state. Ensure the device that created the invite (Master) has state (e.g. at least one virtual host or tunnel) before linking. If both devices are online and the master had content, state should sync within a few seconds; if it does not, try creating a fresh invite on the master and linking again.
|
||||
|
||||
**"Invalid Device File, was modified"** — This can appear if you try to create or use sync after the sync storage was replaced or corrupted. Click **Create invite** to clear local sync data and create a new group, or **Link device** with an invite from another device to join that group.
|
||||
|
||||
Reference in New Issue
Block a user