@@ -4,55 +4,68 @@
|
||||
|
||||
# peardock
|
||||
|
||||
Decentralized Docker management on the modern Holepunch stack.
|
||||
**Decentralized Docker management** on the modern Holepunch stack.
|
||||
|
||||
Share a public key. Manage containers, stacks, Swarm, logs, terminals, and tunnels over encrypted peer-to-peer connections. **No central control plane, no VPN required, and no need to open inbound ports** for Docker or the control UI.
|
||||
|
||||
| Layer | Technology |
|
||||
|-------|------------|
|
||||
| Transport | **HyperDHT** (Noise-encrypted P2P) |
|
||||
| RPC | **protomux-rpc** + **compact-encoding** JSON |
|
||||
| Client | **Pear** desktop app |
|
||||
| Docker | **dockerode** |
|
||||
| Server | **Bare** standalone binary or Node.js (`dockerode`) |
|
||||
| Client | **Electron** desktop (preferred) or **Pear** dev shell |
|
||||
| Port tunnels | **Holesail** (`hs://`, separate data plane) |
|
||||
| License | **[AGPL-3.0](LICENSE)** (same family as Holesail) |
|
||||
|
||||
Site and docs: [peardock.boats](https://peardock.boats) · Source: [git.ssh.surf/snxraven/peardock](https://git.ssh.surf/snxraven/peardock) · Rolling binaries: [releases](https://git.ssh.surf/snxraven/peardock/releases/tag/rolling)
|
||||
|
||||
No central control plane. The server announces a keypair on the DHT; clients connect with its public key.
|
||||
|
||||
---
|
||||
|
||||
## Quick start
|
||||
|
||||
### One-line installer (recommended)
|
||||
|
||||
Interactive installer — detects OS/arch, asks **server** vs **client**, installs a Linux **systemd** service for the server or a desktop client for your platform:
|
||||
Interactive installer detects OS/arch, installs a **Linux systemd server** and/or a **desktop client**, and pulls artifacts from the **`rolling`** release:
|
||||
|
||||
```bash
|
||||
curl -fsSL https://install.peardock.boats | bash
|
||||
```
|
||||
|
||||
Non-interactive examples:
|
||||
Non-interactive:
|
||||
|
||||
```bash
|
||||
# Server (Linux systemd → peardock.service)
|
||||
# Server (Linux → /opt/peardock + peardock.service + docker group)
|
||||
curl -fsSL https://install.peardock.boats | bash -s -- --server --yes
|
||||
|
||||
# Desktop client only
|
||||
# Desktop client (Linux or macOS)
|
||||
curl -fsSL https://install.peardock.boats | bash -s -- --client --yes
|
||||
|
||||
# Both
|
||||
curl -fsSL https://install.peardock.boats | bash -s -- --both --yes
|
||||
```
|
||||
|
||||
Downloads artifacts from the **`rolling`** release (`peardock-server-*` / `peardock-client-*`).
|
||||
|
||||
### 1. Install from source
|
||||
After the server starts, copy the **public key** from the journal:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
# Node.js ≥ 20 required
|
||||
sudo journalctl -u peardock -n 80 --no-pager | grep -i 'public key'
|
||||
# or: sudo systemctl status peardock
|
||||
```
|
||||
|
||||
### 2. Run the server (machine with Docker)
|
||||
Open the desktop client, add a peer, paste the 64-character public key. Clients auto-reconnect if the link drops.
|
||||
|
||||
More detail: [docs/OPERATOR.md](docs/OPERATOR.md) · [peardock.boats/download](https://peardock.boats/download) · [peardock.boats/docs/quickstart](https://peardock.boats/docs/quickstart)
|
||||
|
||||
### Install from source
|
||||
|
||||
```bash
|
||||
npm run server
|
||||
git clone https://git.ssh.surf/snxraven/peardock.git
|
||||
cd peardock
|
||||
npm install # Node.js ≥ 20
|
||||
npm run server # control plane (needs Docker socket)
|
||||
npm run start:client # Electron GUI (builds electron/app.bundle.cjs then launches)
|
||||
```
|
||||
|
||||
You will see:
|
||||
You should see:
|
||||
|
||||
```
|
||||
peardock server ready
|
||||
@@ -60,7 +73,7 @@ You will see:
|
||||
<64 hex characters>
|
||||
```
|
||||
|
||||
Keep this process running. Identity is stored in `.env`:
|
||||
Identity is stored in `.env` (or `/opt/peardock/.env` for package installs):
|
||||
|
||||
| Variable | Meaning |
|
||||
|----------|---------|
|
||||
@@ -68,143 +81,191 @@ Keep this process running. Identity is stored in `.env`:
|
||||
| `SERVER_PUBLIC_KEY` | Derived public key (share with clients) |
|
||||
| `SERVER_KEY` | Legacy alias for the seed (still accepted) |
|
||||
|
||||
### 3. Run the desktop client
|
||||
|
||||
Ensure the Pear binary is on your `PATH` (one-time):
|
||||
### Pear development shell (optional)
|
||||
|
||||
```bash
|
||||
# macOS
|
||||
# macOS: ensure pear is on PATH
|
||||
export PATH="$HOME/Library/Application Support/pear/bin:$PATH"
|
||||
# make permanent in fish:
|
||||
# fish_add_path "$HOME/Library/Application Support/pear/bin"
|
||||
```
|
||||
|
||||
Then:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
# or: pear run -d .
|
||||
```
|
||||
|
||||
Paste the **public key** into the sidebar connection field.
|
||||
Pear may print `DEPRECATED: pear run is deprecated` (platform-wide). For day-to-day operators, prefer the **Electron client** from the rolling release or `npm run start:client`.
|
||||
|
||||
> **Note:** Pear may print `DEPRECATED: pear run is deprecated`. That is a platform-wide message.
|
||||
> This app uses the supported **pear-electron + pear-bridge** entry (`index.js`) so it is not a legacy HTML app.
|
||||
> Long-term OTA packaging uses [hello-pear-electron](https://github.com/holepunchto/hello-pear-electron) / `pear-runtime`.
|
||||
---
|
||||
|
||||
### Production Pear app
|
||||
## What you get
|
||||
|
||||
```bash
|
||||
pear stage .
|
||||
pear release .
|
||||
pear run pear://<your-app-key>
|
||||
```
|
||||
|
||||
### Standalone binaries (CI / release)
|
||||
|
||||
64-bit only: `linux-x64`, `linux-arm64`, `darwin-x64`, `darwin-arm64`, `win32-x64`, `win32-arm64`.
|
||||
|
||||
```bash
|
||||
# Everything CI ships (cross-compile server + client for all hosts)
|
||||
npm run make
|
||||
|
||||
# Or one target:
|
||||
npm run make:server:linux-x64
|
||||
npm run make:client:win32-arm64
|
||||
# → out/peardock-server-<host>/ and out/peardock-<platform>-<arch>/
|
||||
```
|
||||
|
||||
Gitea CI (Ubuntu) cross-builds **all** of those hosts and publishes tag **`rolling`** (secret `RELEASE_TOKEN`). See [docs/RELEASE.md](docs/RELEASE.md).
|
||||
- **Full Engine surface** over P2P: containers, images, volumes, networks, compose stacks, logs, stats, terminals, prune, schedules, registry vault
|
||||
- **Docker Swarm** UI and RPC (services, nodes, tasks, secrets, configs, scale). On by default; set `ENABLE_SWARM=0` to disable
|
||||
- **Holesail tunnels** for published ports without firewall holes. On by default; set `ENABLE_HOLESAIL=0` to disable. See [docs/HOLESAIL.md](docs/HOLESAIL.md)
|
||||
- **Roles**: `viewer` / `operator` / `admin` (default admin for single-operator setups)
|
||||
- **Multi-peer fleet** in one desktop client with reconnect and job tray
|
||||
- **Standalone binaries** for six 64-bit hosts (server Bare + client Electron)
|
||||
|
||||
---
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
shared/ Protocol constants + encodings (both sides)
|
||||
shared/ Protocol constants + encodings (client + server)
|
||||
server/
|
||||
server.js Entry: HyperDHT listen
|
||||
core/ Keys, peer registry
|
||||
rpc/ PeerSession (protomux-rpc), handler registration
|
||||
handlers/ Domain methods (containers, images, volumes, …)
|
||||
services/ Docker client, stats, event stream
|
||||
utils/ Validation, rate limit, logging, compose
|
||||
server.js Entry: HyperDHT listen
|
||||
core/ Keys, peer registry, audit
|
||||
rpc/ PeerSession (protomux-rpc), handler registration
|
||||
handlers/ Domain methods (containers, swarm, tunnels, vault, …)
|
||||
services/ Docker client, stats, metrics, event stream
|
||||
utils/ Validation, rate limit, logging, compose
|
||||
client/
|
||||
connection.js Single HyperDHT + protomux-rpc link
|
||||
manager.js Multi-server connections + persistence
|
||||
api.js Typed RPC helpers
|
||||
app.js + libs/ Pear UI
|
||||
assets/ App logos + favicons (from peardock-branding/)
|
||||
build/icon.* Electron package icons (.png / .icns / .ico)
|
||||
peardock-branding/ Master brand package (source of truth)
|
||||
connection.js Single HyperDHT + protomux-rpc link
|
||||
manager.js Multi-server connections + persistence
|
||||
api.js Typed RPC helpers
|
||||
holesailLocal.js Local Holesail client (data plane)
|
||||
electron/ Electron main / packaged desktop shell
|
||||
ui/ + app.js + libs/ Desktop UI
|
||||
assets/ Logos + favicons
|
||||
peardock-branding/ Master brand package
|
||||
deploy/ systemd unit
|
||||
scripts/ install.sh, make, rolling release, healthcheck
|
||||
```
|
||||
|
||||
### Two planes (do not conflate)
|
||||
|
||||
| Plane | Technology | Purpose |
|
||||
|-------|------------|---------|
|
||||
| **Control** | HyperDHT + protomux-rpc | Docker ops, ACL, logs, deploy, stats |
|
||||
| **Data / tunnels** | Holesail | L4 proxy of `host:port` via `hs://` |
|
||||
|
||||
### RPC model
|
||||
|
||||
**Client → server** methods (examples): `handshake`, `ping`, `listContainers`, `killContainer`, `containerTop`, `deployContainer`, `pruneImages`, `getSystemDf`, `startTerminal`, …
|
||||
**Client → server** (examples): `handshake`, `ping`, `listContainers`, `deployContainer`, `deployStack`, `startTerminal`, `scaleService`, `createTunnel`, …
|
||||
|
||||
**Server → client** pushes: `push:containers`, `push:allStats`, `push:logs`, `push:pullProgress`, `push:buildProgress`, `push:dockerEvent`, `push:terminalOutput`, …
|
||||
|
||||
Defined in `shared/protocol.js` (`PROTOCOL_VERSION` negotiated on connect). See `ROADMAP.md` for coverage and remaining work.
|
||||
Defined in `shared/protocol.js` (`PROTOCOL_VERSION` negotiated on connect).
|
||||
|
||||
---
|
||||
|
||||
## Deployment
|
||||
|
||||
1. **Host** — Linux/macOS with Docker socket access for the server user.
|
||||
2. **Process** — `systemd` / `pm2` / Docker supervising `node server/server.js`.
|
||||
3. **Network** — HyperDHT holepunches; allow UDP when possible. Bootstrap peers are built into `hyperdht`.
|
||||
4. **Secrets** — Back up `SERVER_SEED`. Rotating seed changes the public key; clients must reconnect.
|
||||
5. **Pear** — Stage/release the desktop app separately from the control-plane server.
|
||||
6. **Security** — Connections are E2E encrypted (Noise). Rate limits apply per peer. RPC methods are gated by role (`viewer` / `operator` / `admin`; default admin). Privileged actions append to an audit log. Docker CLI is allow-listed to read-only style commands. Optional env: `PEARDOCK_DEFAULT_ROLE`, `PEARDOCK_ADMIN_KEYS`, `PEARDOCK_BROWSE_ROOTS`, `PEARDOCK_AUDIT`.
|
||||
7. **Holesail tunnels (on by default)** — Expose published container/host ports over [Holesail](https://github.com/holesail/holesail) `hs://` keys (separate from control-plane RPC). Opt out with `ENABLE_HOLESAIL=0`. See [docs/HOLESAIL.md](docs/HOLESAIL.md). PearDock and Holesail are both AGPL-3.0.
|
||||
1. **Host** with Docker socket access for the service user (`peardock` in group `docker`).
|
||||
2. **Process**: installer Bare binary + systemd, or `node server/server.js` under systemd/pm2.
|
||||
3. **Network**: HyperDHT hole punching (UDP when possible). No need to expose Docker TCP or a public control port.
|
||||
4. **Secrets**: back up `SERVER_SEED`. Rotating it changes the public key; clients must reconnect.
|
||||
5. **Client**: rolling Electron package, or `npm run start:client` from source.
|
||||
6. **Security**: Noise E2E, rate limits, roles, optional audit and peer allowlist. See [docs/THREAT_MODEL.md](docs/THREAT_MODEL.md).
|
||||
7. **Feature flags** (defaults): Swarm and Holesail **on**; plugins **off**. Opt out with `ENABLE_SWARM=0` / `ENABLE_HOLESAIL=0`.
|
||||
|
||||
Example systemd unit:
|
||||
Canonical systemd unit: [`deploy/peardock.service`](deploy/peardock.service) (matches what `install.sh` writes).
|
||||
|
||||
```ini
|
||||
[Unit]
|
||||
Description=peardock HyperDHT server
|
||||
After=docker.service
|
||||
Requires=docker.service
|
||||
Description=peardock HyperDHT Docker control plane
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
Wants=docker.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
WorkingDirectory=/opt/peardock
|
||||
ExecStart=/usr/bin/node server/server.js
|
||||
ExecStart=/opt/peardock/peardock-server
|
||||
Restart=on-failure
|
||||
User=peardock
|
||||
Group=docker
|
||||
SupplementaryGroups=docker
|
||||
Environment=NODE_ENV=production
|
||||
EnvironmentFile=-/opt/peardock/.env
|
||||
ReadWritePaths=/opt/peardock
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
Source-install alternative: `ExecStart=/usr/bin/node /opt/peardock/server/server.js`.
|
||||
|
||||
---
|
||||
|
||||
## Standalone binaries
|
||||
|
||||
64-bit only: `linux-x64`, `linux-arm64`, `darwin-x64`, `darwin-arm64`, `win32-x64`, `win32-arm64`.
|
||||
|
||||
```bash
|
||||
npm run make # all server + client hosts (same as CI)
|
||||
npm run make:server:linux-x64
|
||||
npm run make:client:darwin-arm64
|
||||
# → out/peardock-server-<host>/ and out/peardock-<platform>-<arch>/
|
||||
```
|
||||
|
||||
Gitea CI publishes tag **`rolling`** on every `main` build (secret `RELEASE_TOKEN`). See [docs/RELEASE.md](docs/RELEASE.md).
|
||||
|
||||
Asset patterns:
|
||||
|
||||
- `peardock-server-VERSION-HOST.tar.gz` (+ `.sha256`)
|
||||
- `peardock-client-VERSION-HOST.tar.gz` (+ `.sha256`; macOS includes a `.app`)
|
||||
|
||||
macOS clients from Linux CI are ad-hoc signed. If Gatekeeper blocks: right-click → Open, or `xattr -cr ~/Applications/peardock.app`. Re-sign: `npm run sign:macos -- out/peardock-darwin-arm64/peardock.app`.
|
||||
|
||||
---
|
||||
|
||||
## Scripts
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `npm run server` | Start HyperDHT Docker control plane |
|
||||
| `npm run dev` | Pear desktop app (dev) |
|
||||
| `npm test` | Unit + fuzz + load + integration tests |
|
||||
| `npm run server` | HyperDHT Docker control plane (Node) |
|
||||
| `npm run start:client` | Build client bundle + Electron GUI |
|
||||
| `npm run dev` | Pear desktop shell (dev) |
|
||||
| `npm test` | Unit + integration tests (brittle) |
|
||||
| `npm run healthcheck` | Docker socket health probe |
|
||||
| `npm run soak` / `soak:24h` | Certification soak against Docker |
|
||||
| `npm run release:checksums` | Release tarball + SHA-256 (+ optional GPG) |
|
||||
|
||||
See **`ROADMAP.md`** (complete) and **`docs/`** for operators, threat model, and releases.
|
||||
| `npm run make` | Cross-build all server + client hosts |
|
||||
| `npm run make:server` / `make:client` | Server-only or client-only builds |
|
||||
| `npm run sign:macos` | Ad-hoc / identity re-sign for `.app` |
|
||||
| `npm run release:checksums` | Tarball + SHA-256 (+ optional GPG) |
|
||||
| `npm run release:rolling` | Local dry-run helper for rolling publish |
|
||||
|
||||
---
|
||||
|
||||
## Dependencies (current)
|
||||
## Documentation
|
||||
|
||||
- `hyperdht` ^6.33
|
||||
- `protomux-rpc` ^1.10
|
||||
- `protomux` ^3.11
|
||||
- `compact-encoding` ^3.3
|
||||
- `b4a` ^1.8
|
||||
- `hypercore-crypto` ^3.7
|
||||
- `dockerode` ^5
|
||||
- `dotenv` ^17
|
||||
- `graceful-goodbye` ^1.3
|
||||
Full technical docs live under **[`docs/`](docs/README.md)** (architecture diagrams, protocol, codebase map, server/client deep dives).
|
||||
|
||||
| Doc | Contents |
|
||||
|-----|----------|
|
||||
| [docs/README.md](docs/README.md) | **Documentation hub** + system overview |
|
||||
| [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) | Design, two planes, session lifecycle, mermaid charts |
|
||||
| [docs/CODEBASE-MAP.md](docs/CODEBASE-MAP.md) | Directory and module map |
|
||||
| [docs/PROTOCOL.md](docs/PROTOCOL.md) | RPC methods, roles, pushes |
|
||||
| [docs/SERVER.md](docs/SERVER.md) | Server internals |
|
||||
| [docs/CLIENT-UI.md](docs/CLIENT-UI.md) | Client stack and desktop UI |
|
||||
| [docs/FEATURES.md](docs/FEATURES.md) | Feature catalog |
|
||||
| [docs/OPERATOR.md](docs/OPERATOR.md) | Install, systemd, roles, vault, flags, logging, backup |
|
||||
| [docs/HOLESAIL.md](docs/HOLESAIL.md) | Control vs data plane, tunnel RPC, security |
|
||||
| [docs/THREAT_MODEL.md](docs/THREAT_MODEL.md) | Assets, trust boundaries, hardening checklist |
|
||||
| [docs/RELEASE.md](docs/RELEASE.md) | Host matrix, bare/Electron builds, rolling CI |
|
||||
| [docs/SBOM.md](docs/SBOM.md) | SBOM generation, license notes |
|
||||
| [ROADMAP.md](ROADMAP.md) | Feature tracks and status |
|
||||
| [peardock.boats/docs](https://peardock.boats/docs/) | Public operator docs mirror |
|
||||
|
||||
---
|
||||
|
||||
## Configuration (common env)
|
||||
|
||||
| Variable | Default / notes |
|
||||
|----------|-----------------|
|
||||
| `SERVER_SEED` | Generated on first start if missing |
|
||||
| `PEARDOCK_DEFAULT_ROLE` | `admin` (use `viewer`/`operator` for fleets) |
|
||||
| `PEARDOCK_ADMIN_KEYS` | Comma-separated client public keys |
|
||||
| `PEARDOCK_PEER_ALLOWLIST` | `1` to require registered peers |
|
||||
| `PEARDOCK_AUDIT` | `1` to append privileged actions to audit log |
|
||||
| `PEARDOCK_BROWSE_ROOTS` | Allowed host paths for file browse |
|
||||
| `ENABLE_SWARM` | On (`1`); set `0` to disable Swarm RPC |
|
||||
| `ENABLE_HOLESAIL` | On (`1`); set `0` to disable tunnels |
|
||||
| `ENABLE_PLUGINS` | Off unless `1` |
|
||||
| `PEARDOCK_MAX_TUNNELS` | `20` |
|
||||
| `LOG_LEVEL` / `LOG_FORMAT` | `info` / `json` recommended under journald |
|
||||
|
||||
Full tables: [docs/OPERATOR.md](docs/OPERATOR.md).
|
||||
|
||||
---
|
||||
|
||||
@@ -217,8 +278,14 @@ See **`ROADMAP.md`** (complete) and **`docs/`** for operators, threat model, and
|
||||
| Raw JSON on duplex streams | protomux-rpc methods + push channels |
|
||||
| Monolithic `server.js` switch | Modular handlers under `server/handlers/` |
|
||||
|
||||
Clients and servers are not wire-compatible across major versions.
|
||||
|
||||
---
|
||||
|
||||
## License
|
||||
|
||||
[AGPL-3.0](LICENSE) — same family as [Holesail](https://github.com/holesail/holesail).
|
||||
**[GNU Affero General Public License v3.0](LICENSE)** ([AGPL-3.0](https://www.gnu.org/licenses/agpl-3.0.html)).
|
||||
|
||||
PearDock matches [Holesail](https://github.com/holesail/holesail) (also AGPL-3.0). If you modify PearDock and let others interact with it over a network, review AGPL source-offer obligations (AGPL §13). See [docs/SBOM.md](docs/SBOM.md) and [peardock.boats/legal/license](https://peardock.boats/legal/license).
|
||||
|
||||
Owned and engineered by **HoneyPeer, LLC** (DeKalb County, Georgia, USA). Legal: [[email protected]](mailto:[email protected]).
|
||||
|
||||
@@ -0,0 +1,362 @@
|
||||
# Architecture
|
||||
|
||||
Deep design of PearDock v2: control plane, data plane, session model, and process boundaries.
|
||||
|
||||
See also: [CODEBASE-MAP.md](./CODEBASE-MAP.md) · [PROTOCOL.md](./PROTOCOL.md) · [SERVER.md](./SERVER.md) · [CLIENT-UI.md](./CLIENT-UI.md)
|
||||
|
||||
---
|
||||
|
||||
## Design goals
|
||||
|
||||
1. **No central control plane** for Docker ops (no SaaS tenancy of your Engine).
|
||||
2. **Cryptographic identity**: dial a public key, not a shared secret topic.
|
||||
3. **Rich remote console**: parity with a local Docker GUI (lists, deploy, logs, terminals, Swarm).
|
||||
4. **Clear security boundaries**: roles, rate limits, audit, optional allowlist.
|
||||
5. **Orthogonal port access**: Holesail tunnels without conflating them with RPC.
|
||||
|
||||
---
|
||||
|
||||
## System context
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
OP[Operator] --> CLI[Desktop client]
|
||||
CLI <-->|HyperDHT Noise| SRV[peardock-server]
|
||||
SRV --> DOCK[dockerd]
|
||||
CLI -.->|optional hs://| TUN[Holesail on server]
|
||||
TUN --> DOCK
|
||||
SRV <-->|bootstrap / punch| NET[HyperDHT network]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Two planes
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph Control["Control plane"]
|
||||
direction LR
|
||||
A[Client RPC] -->|protomux-rpc| B[Handlers]
|
||||
B --> C[dockerode]
|
||||
end
|
||||
|
||||
subgraph Data["Data plane"]
|
||||
direction LR
|
||||
D[Holesail client] -->|hs:// capability| E[HolesailServer]
|
||||
E --> F[host:port]
|
||||
end
|
||||
|
||||
Control -.->|orthogonal<br/>not a replacement| Data
|
||||
```
|
||||
|
||||
| Concern | Control plane | Data plane |
|
||||
|---------|---------------|------------|
|
||||
| Protocol | protomux-rpc JSON methods + pushes | Raw TCP/UDP bytes |
|
||||
| AuthZ | Roles + MethodRoles + peer policy | Possession of `hs://` URL (capability) |
|
||||
| Persistence | peers, vault, audit, schedules | tunnel defs (`peardock-tunnels.json`) |
|
||||
| Feature flag | always on (core product) | `ENABLE_HOLESAIL` (default on) |
|
||||
|
||||
Holesail cannot replace RPC: it has no method names, roles, or live stats channels.
|
||||
|
||||
---
|
||||
|
||||
## Layered architecture
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph Presentation
|
||||
HTML[index.html]
|
||||
UI[ui/ops-app.js + track-*]
|
||||
LIBS[libs/* templates terminals]
|
||||
end
|
||||
|
||||
subgraph ClientCore
|
||||
API[client/api.js]
|
||||
MGR[client/manager.js]
|
||||
CON[client/connection.js]
|
||||
JOB[client/jobs.js]
|
||||
CACHE[client/peerCache.js]
|
||||
end
|
||||
|
||||
subgraph Wire
|
||||
ENC[shared/encodings.js]
|
||||
PROT[shared/protocol.js]
|
||||
SCH[shared/schema.js]
|
||||
end
|
||||
|
||||
subgraph ServerCore
|
||||
BOOT[server/server.js]
|
||||
SESS[server/rpc/session.js]
|
||||
REG[server/rpc/register.js]
|
||||
HAND[server/handlers/*]
|
||||
CORE[server/core/*]
|
||||
SVC[server/services/*]
|
||||
end
|
||||
|
||||
HTML --> UI --> LIBS
|
||||
UI --> MGR
|
||||
UI --> API
|
||||
API --> CON
|
||||
MGR --> CON
|
||||
MGR --> CACHE
|
||||
CON --> ENC
|
||||
CON --> PROT
|
||||
SESS --> ENC
|
||||
SESS --> PROT
|
||||
SESS --> SCH
|
||||
SESS --> CORE
|
||||
REG --> HAND
|
||||
HAND --> SVC
|
||||
BOOT --> SESS
|
||||
BOOT --> REG
|
||||
CON <-->|HyperDHT stream| SESS
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Server boot sequence
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Main as server/server.js
|
||||
participant Keys as core/keys.js
|
||||
participant DHT as hyperdht
|
||||
participant Dock as services/docker.js
|
||||
participant Ev as services/events.js
|
||||
participant St as services/stats.js
|
||||
participant Hs as services/holesail-tunnels.js
|
||||
participant Sch as services/schedules.js
|
||||
|
||||
Main->>Keys: loadOrCreateKeyPair()
|
||||
Keys-->>Main: keyPair + publicKeyHex
|
||||
Main->>DHT: createServer + listen(keyPair)
|
||||
Main->>Dock: version() probe
|
||||
Main->>Main: logger.banner(public key, flags)
|
||||
Main->>Ev: startDockerEventStream()
|
||||
Main->>St: startStatsBroadcast()
|
||||
Main->>Hs: restoreTunnelsFromDisk() if enabled
|
||||
Main->>Sch: restoreSchedules()
|
||||
Note over Main: On connection: PeerSession + registerAllHandlers
|
||||
```
|
||||
|
||||
On each inbound connection:
|
||||
|
||||
1. Read remote public key → peer id (hex).
|
||||
2. Reject if **revoked**.
|
||||
3. Construct `PeerSession` (protomux-rpc, encodings).
|
||||
4. `registerAllHandlers(session)`.
|
||||
5. Add to `peers` registry; metrics connect counter.
|
||||
6. On close: terminal/logs cleanup, remove peer, metrics disconnect.
|
||||
|
||||
---
|
||||
|
||||
## PeerSession middleware pipeline
|
||||
|
||||
Every non-hot RPC call roughly follows:
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
IN[Incoming method + args] --> RL{Rate limit}
|
||||
RL -->|deny| E1[RATE_LIMIT_EXCEEDED]
|
||||
RL -->|ok| ACL{roleAllows MethodRoles}
|
||||
ACL -->|deny| E2[PERMISSION_DENIED + audit]
|
||||
ACL -->|ok| VAL{validateMethodArgs schema}
|
||||
VAL -->|fail| E3[INVALID_ARGS]
|
||||
VAL -->|ok| H[Domain handler]
|
||||
H --> DOC[dockerode / files / Holesail]
|
||||
DOC --> OK[Result]
|
||||
H --> AUD{shouldAudit?}
|
||||
AUD -->|yes| LOG[audit log append]
|
||||
OK --> MET[recordRpc latency]
|
||||
```
|
||||
|
||||
**Hot path** (terminals, stream chunks): skip schema/metrics noise; still enforce ACL and sanitize errors.
|
||||
|
||||
Implementation: `server/rpc/session.js` → `respond()`.
|
||||
|
||||
---
|
||||
|
||||
## Client connection state machine
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> idle
|
||||
idle --> dialing: connect(publicKey)
|
||||
dialing --> handshaking: stream open
|
||||
dialing --> closed: timeout / error
|
||||
handshaking --> ready: handshake OK
|
||||
handshaking --> closed: handshake fail
|
||||
ready --> degraded: health fail / partial
|
||||
degraded --> ready: recover
|
||||
ready --> closed: stream close
|
||||
closed --> dialing: reconnect timer 5s
|
||||
closed --> [*]: intentional disconnect
|
||||
```
|
||||
|
||||
States live on `PearDockConnection` (`client/connection.js`).
|
||||
`ConnectionManager` owns many connections, active selection, and reconnect timers (`client/manager.js`).
|
||||
|
||||
---
|
||||
|
||||
## Multi-peer fleet
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
UI[Sidebar peer list]
|
||||
M[ConnectionManager]
|
||||
C1[Connection A]
|
||||
C2[Connection B]
|
||||
C3[Connection C]
|
||||
DISK[(peerCache JSON)]
|
||||
|
||||
UI --> M
|
||||
M --> C1
|
||||
M --> C2
|
||||
M --> C3
|
||||
M <--> DISK
|
||||
M -->|active| C1
|
||||
```
|
||||
|
||||
- Disk-backed peer list + last active id.
|
||||
- Switching peers rebinds UI subscriptions to the active connection’s pushes.
|
||||
- Jobs (`client/jobs.js`) track long ops (pull, deploy, build) with step logs.
|
||||
|
||||
---
|
||||
|
||||
## Data plane (Holesail) architecture
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph Server
|
||||
RPC[createTunnel RPC]
|
||||
REG[holesail-tunnels service]
|
||||
HS[HolesailServer]
|
||||
TGT[127.0.0.1:port]
|
||||
RPC --> REG --> HS --> TGT
|
||||
DISK[(peardock-tunnels.json)]
|
||||
REG <--> DISK
|
||||
end
|
||||
|
||||
subgraph ClientApp
|
||||
UI[Tunnels UI]
|
||||
LOC[holesailLocal.js]
|
||||
BARE[Bare control HTTP<br/>pear only]
|
||||
UI --> LOC
|
||||
LOC --> BARE
|
||||
end
|
||||
|
||||
BARE -->|hs://| HS
|
||||
LOC -->|or in-process holesail| HS
|
||||
```
|
||||
|
||||
Under **Pear**, the renderer cannot load native bare-tcp addons; the real `holesail` package runs in the Bare main process (`index.js` + `client/holesailBareControl.cjs`), and the UI talks HTTP on localhost. Under **Electron / Node tests**, `holesailLocal.js` can use in-process require paths.
|
||||
|
||||
Full detail: [HOLESAIL.md](./HOLESAIL.md).
|
||||
|
||||
---
|
||||
|
||||
## Packaging architecture
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
SRC[Source tree]
|
||||
SRC --> BARE[scripts/bare-standalone.cjs]
|
||||
SRC --> ESB[esbuild app.bundle.cjs]
|
||||
BARE --> SERV[peardock-server HOST]
|
||||
ESB --> FORGE[electron-forge package]
|
||||
FORGE --> CLI[peardock-client HOST]
|
||||
SERV --> TGZ1[tar.gz + sha256]
|
||||
CLI --> TGZ2[tar.gz + sha256]
|
||||
TGZ1 --> ROLL[Gitea tag rolling]
|
||||
TGZ2 --> ROLL
|
||||
ROLL --> INST[install.peardock.boats]
|
||||
ROLL --> WEB[peardock.boats/releases]
|
||||
```
|
||||
|
||||
| Artifact | Runtime |
|
||||
|----------|---------|
|
||||
| peardock-server | Bare (Node builtins mapped to bare-* via `imports`) |
|
||||
| peardock-client | Electron + bundled renderer/main |
|
||||
|
||||
See [RELEASE.md](./RELEASE.md).
|
||||
|
||||
---
|
||||
|
||||
## Trust and ACL model
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph Identity
|
||||
SEED[SERVER_SEED]
|
||||
SPK[Server public key]
|
||||
CPK[Client keypair]
|
||||
SEED --> SPK
|
||||
end
|
||||
|
||||
subgraph Policy
|
||||
DEF[PEARDOCK_DEFAULT_ROLE]
|
||||
ADM[PEARDOCK_ADMIN_KEYS]
|
||||
AL[Allowlist + invites]
|
||||
REV[Revocation list]
|
||||
end
|
||||
|
||||
CPK --> ROLE[Resolved role]
|
||||
DEF --> ROLE
|
||||
ADM --> ROLE
|
||||
AL --> ROLE
|
||||
REV -->|block connect| X[Socket destroy]
|
||||
ROLE --> MR[MethodRoles]
|
||||
```
|
||||
|
||||
Files (server data dir / cwd):
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `.env` | `SERVER_SEED`, optional env |
|
||||
| `peardock-peers.json` | Peer roles / registration |
|
||||
| `peardock-vault.json` | Registry credentials (AES-GCM) |
|
||||
| `peardock-audit.log` | Privileged action log |
|
||||
| `peardock-tunnels.json` | Tunnel definitions |
|
||||
| schedules store | Cron-like maintenance jobs |
|
||||
|
||||
---
|
||||
|
||||
## Protocol versioning
|
||||
|
||||
- Constant: `PROTOCOL_VERSION` in `shared/protocol.js` (currently **2**).
|
||||
- Negotiated in `handshake`.
|
||||
- **Bump** when breaking RPC argument/result shapes.
|
||||
- Additive methods may land without a bump if clients ignore unknowns.
|
||||
|
||||
v1 used Hyperswarm topics + ad-hoc JSON streams and is **not** wire-compatible.
|
||||
|
||||
---
|
||||
|
||||
## Failure modes (operational)
|
||||
|
||||
| Symptom | Likely layer | Where to look |
|
||||
|---------|--------------|---------------|
|
||||
| Never connects | DHT / NAT / wrong key | Client timeout; server listening; key hex |
|
||||
| Connect then empty lists | Docker socket / group | `docker.sock` perms; peardock in `docker` group |
|
||||
| Method fails permission | ACL | Role vs `MethodRoles`; allowlist |
|
||||
| Rate limit errors | Middleware | `server/utils/rateLimiter.js` |
|
||||
| Tunnel create fails | Holesail flag / SSRF | `ENABLE_HOLESAIL`; `PEARDOCK_TUNNEL_HOSTS` |
|
||||
| Swarm empty / error | Swarm mode / flag | Engine swarm state; `ENABLE_SWARM` |
|
||||
| macOS “damaged” app | Gatekeeper | `xattr -cr`; ad-hoc sign docs |
|
||||
|
||||
---
|
||||
|
||||
## Non-goals (architecture)
|
||||
|
||||
- Multi-tenant isolation between unrelated Docker users on one host.
|
||||
- Replacing Docker authz plugins.
|
||||
- Making HyperDHT optional (v2 assumes Holepunch transport).
|
||||
- Using Holesail as the sole control channel.
|
||||
|
||||
---
|
||||
|
||||
## Related reading
|
||||
|
||||
- Protocol catalog: [PROTOCOL.md](./PROTOCOL.md)
|
||||
- Feature inventory: [FEATURES.md](./FEATURES.md)
|
||||
- Security: [THREAT_MODEL.md](./THREAT_MODEL.md)
|
||||
@@ -0,0 +1,189 @@
|
||||
# Client and UI
|
||||
|
||||
How the desktop app connects, reconnects, and presents Docker ops.
|
||||
|
||||
---
|
||||
|
||||
## Stack choices
|
||||
|
||||
| Shell | Entry | Use case |
|
||||
|-------|-------|----------|
|
||||
| **Electron** | `electron/main.cjs` + `npm run start:client` | Preferred operator client; rolling packages |
|
||||
| **Pear** | `index.js` + `npm run dev` | Dev / Holepunch-native shell; starts Bare Holesail control |
|
||||
|
||||
Both load the same conceptual UI (`index.html` + `ui/` + `client/`). Electron bundles via esbuild (`electron/app.bundle.cjs`).
|
||||
|
||||
---
|
||||
|
||||
## Connection stack
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
UI[ui/ops-app.js] --> MGR[ConnectionManager]
|
||||
MGR --> CON[PearDockConnection]
|
||||
CON --> DHT[hyperdht.connect publicKey]
|
||||
CON --> RPC[ProtomuxRPC]
|
||||
RPC --> HS[handshake]
|
||||
MGR --> CACHE[peerCache.js]
|
||||
UI --> API[api.js helpers]
|
||||
API --> MGR
|
||||
```
|
||||
|
||||
### `PearDockConnection` (`client/connection.js`)
|
||||
|
||||
- Validates 64-hex public key.
|
||||
- Dials HyperDHT with timeout (default 30s).
|
||||
- Builds protomux-rpc with shared encodings.
|
||||
- Registers push handlers → EventEmitter.
|
||||
- Tracks `state`: idle → dialing → handshaking → ready → degraded/closed.
|
||||
- Exposes `request(method, args)` with error normalization.
|
||||
|
||||
### `ConnectionManager` (`client/manager.js`)
|
||||
|
||||
- Map of connections by peer id prefix.
|
||||
- **Active** connection drives main tables.
|
||||
- **Reconnect**: every **5s** after unexpected drop (unlimited by default).
|
||||
- Persists peers via `peerCache.js`.
|
||||
- Restores last active peer on launch.
|
||||
|
||||
### `client/api.js`
|
||||
|
||||
Thin typed wrappers around `Methods.*` for UI code (e.g. `listContainers`, `createTunnel`, `swarmInspect`).
|
||||
|
||||
---
|
||||
|
||||
## Peer cache
|
||||
|
||||
`client/peerCache.js` stores:
|
||||
|
||||
- Known peers (public key, alias, tags)
|
||||
- Last active peer id
|
||||
|
||||
Location: config directory suitable for the runtime (XDG / Pear storage / Electron userData depending on path helpers).
|
||||
|
||||
---
|
||||
|
||||
## Jobs
|
||||
|
||||
`client/jobs.js` models multi-step operations (pull, deploy, build) for the **job drawer** in the UI: progress, logs, success/failure.
|
||||
|
||||
---
|
||||
|
||||
## UI structure (`index.html` + `ui/`)
|
||||
|
||||
### Navigation model
|
||||
|
||||
Sidebar `data-view="…"` sections, including:
|
||||
|
||||
| View | Purpose |
|
||||
|------|---------|
|
||||
| containers | Table + details, lifecycle actions |
|
||||
| images | Images, pull/build |
|
||||
| networks / volumes | Resource management |
|
||||
| stacks | Compose projects |
|
||||
| deploy | Templates + container configuration form |
|
||||
| swarm | Services, nodes, tasks, secrets, configs |
|
||||
| tunnels | Holesail create/list/close |
|
||||
| host | System info / df / maintenance |
|
||||
| peers / access | Fleet peers, invites, vault |
|
||||
| settings | Density, accent, version, license |
|
||||
|
||||
### Controllers
|
||||
|
||||
| Module | Role |
|
||||
|--------|------|
|
||||
| `ui/ops-app.js` | View loading, Swarm tables, tunnels, settings, connect hooks |
|
||||
| `ui/track-g-ux.js` | Keyboard shortcuts (`g` then key), command palette, go-map |
|
||||
| `ui/track-f-extras.js` | Extra polish features |
|
||||
| `libs/templateDeploy.js` | Deploy form / templates (large) |
|
||||
| `libs/terminal.js` etc. | xterm integration |
|
||||
|
||||
### Auto-refresh
|
||||
|
||||
When a view is active, silent polls refresh lists (e.g. Swarm fingerprint skip if unchanged).
|
||||
|
||||
---
|
||||
|
||||
## Local Holesail client path
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant UI as Tunnels UI
|
||||
participant LOC as holesailLocal.js
|
||||
participant CTRL as Bare HTTP control
|
||||
participant HS as Remote HolesailServer
|
||||
|
||||
UI->>LOC: connect(hs://…)
|
||||
alt Pear runtime
|
||||
LOC->>CTRL: POST /connect
|
||||
CTRL->>HS: holesail client
|
||||
else Node/Electron test path
|
||||
LOC->>HS: in-process holesail
|
||||
end
|
||||
HS-->>LOC: local bound port
|
||||
LOC-->>UI: open localhost URL optional
|
||||
```
|
||||
|
||||
Pear entry `index.js` starts Bare control so native addons work outside the renderer.
|
||||
|
||||
---
|
||||
|
||||
## Electron packaging flow
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
SRC[ui + client + libs] --> ESB[esbuild]
|
||||
ESB --> BUNDLE[electron/app.bundle.cjs]
|
||||
BUNDLE --> MAIN[electron/main.cjs]
|
||||
MAIN --> FORGE[electron-forge package]
|
||||
FORGE --> APP[peardock-client artifact]
|
||||
```
|
||||
|
||||
OTA helpers: `electron/ota.cjs`. Preload isolates privileged APIs.
|
||||
|
||||
---
|
||||
|
||||
## Error presentation
|
||||
|
||||
`client/errors.js` + `libs/uiUtils.js` / notifications:
|
||||
|
||||
- Map RPC codes to human messages.
|
||||
- Background methods may soft-fail without modal spam.
|
||||
- Job tray owns multi-step failure UX.
|
||||
|
||||
---
|
||||
|
||||
## Configuration (`config.js`)
|
||||
|
||||
Client constants such as:
|
||||
|
||||
- Reconnect delay / max attempts
|
||||
- Timeouts
|
||||
- Feature toggles consumed by UI
|
||||
|
||||
Server env flags do not automatically apply to the client; the server enforces feature gates and returns `FEATURE_DISABLED`.
|
||||
|
||||
---
|
||||
|
||||
## Testing touchpoints
|
||||
|
||||
- `test/peer-cache.test.js`
|
||||
- `test/holesail-local.test.js`
|
||||
- `test/visual-structure.test.js` (critical `index.html` ids)
|
||||
- `test/terminal-shells.test.js`
|
||||
- `test/feedback.test.js` / `errors.test.js`
|
||||
|
||||
---
|
||||
|
||||
## Developer loop
|
||||
|
||||
```bash
|
||||
# Terminal A
|
||||
npm run server
|
||||
|
||||
# Terminal B
|
||||
npm run start:client
|
||||
# paste public key from server banner
|
||||
```
|
||||
|
||||
Pear alternative: `npm run dev` (with pear on `PATH`).
|
||||
@@ -0,0 +1,346 @@
|
||||
# Codebase map
|
||||
|
||||
Module-by-module guide to the peardock repository. Paths are relative to the repo root.
|
||||
|
||||
For design narrative see [ARCHITECTURE.md](./ARCHITECTURE.md). For RPC names see [PROTOCOL.md](./PROTOCOL.md).
|
||||
|
||||
---
|
||||
|
||||
## Top-level tree
|
||||
|
||||
```
|
||||
peardock/
|
||||
├── app.js # Legacy / bridge glue used with HTML shell
|
||||
├── index.js # Pear entry (pear-electron + Bare Holesail control)
|
||||
├── index.html # Desktop UI markup (all views)
|
||||
├── config.js # Client-side config constants (reconnect, etc.)
|
||||
├── package.json # scripts, imports map (Bare), license AGPL-3.0
|
||||
├── forge.config.cjs # electron-forge packaging
|
||||
├── shared/ # Protocol contract (server + client)
|
||||
├── server/ # Control-plane process
|
||||
├── client/ # Connection stack for UI
|
||||
├── ui/ # Ops UI logic + CSS
|
||||
├── libs/ # UI helpers (templates, terminals, toasts)
|
||||
├── electron/ # Packaged Electron main/preload/OTA
|
||||
├── scripts/ # Install, make, release, soak
|
||||
├── deploy/ # systemd unit
|
||||
├── test/ # brittle tests
|
||||
├── assets/ # Runtime logos/favicons
|
||||
├── peardock-branding/ # Brand source package
|
||||
├── build/ # Stubs/shims for Bare packaging
|
||||
├── bin/ # peardock-server.mjs / peardock-client.mjs helpers
|
||||
├── workers/ # Worker helpers (e.g. holesail local)
|
||||
└── docs/ # Documentation (this tree)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Entry points
|
||||
|
||||
| Entry | When used | What starts |
|
||||
|-------|-----------|-------------|
|
||||
| `server/server.js` | `npm run server` / source systemd | Full HyperDHT server |
|
||||
| `bin/peardock-server.mjs` | Bare / packaged server bootstrap | Loads bare-node-runtime when under Bare |
|
||||
| `/opt/peardock/peardock-server` | install.sh / rolling | Compiled Bare binary |
|
||||
| `index.js` | `npm run dev` / Pear | pear-electron UI + holesail Bare control |
|
||||
| `electron/main.cjs` | `npm run start:client` / packaged client | Electron window → loads app shell |
|
||||
| `scripts/install.sh` | install.peardock.boats | Downloads rolling assets, systemd, client |
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph ServerEntries
|
||||
S1[server/server.js]
|
||||
S2[bin/peardock-server.mjs]
|
||||
S3[peardock-server binary]
|
||||
end
|
||||
subgraph ClientEntries
|
||||
C1[index.js Pear]
|
||||
C2[electron/main.cjs]
|
||||
end
|
||||
S1 --> CORE[HyperDHT + handlers]
|
||||
S2 --> CORE
|
||||
S3 --> CORE
|
||||
C1 --> UI[index.html + client/]
|
||||
C2 --> UI
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `shared/` — contract between peers
|
||||
|
||||
| File | Role |
|
||||
|------|------|
|
||||
| `protocol.js` | `PROTOCOL`, `PROTOCOL_VERSION`, `Roles`, `MethodRoles`, `Methods`, `Pushes`, `roleAllows()` |
|
||||
| `encodings.js` | compact-encoding setup for protomux-rpc value codec |
|
||||
| `schema.js` | `validateMethodArgs()` lightweight arg validation + `SCHEMA_VERSION` |
|
||||
|
||||
**Rule:** any new RPC method needs:
|
||||
|
||||
1. `Methods.*` name
|
||||
2. `MethodRoles` entry (or it defaults to admin)
|
||||
3. Server `session.respond(...)`
|
||||
4. Optional `client/api.js` helper
|
||||
5. Schema case if args need validation
|
||||
|
||||
---
|
||||
|
||||
## `server/` — control plane
|
||||
|
||||
### `server/server.js`
|
||||
|
||||
Process bootstrap:
|
||||
|
||||
- Load/create keypair
|
||||
- HyperDHT `createServer` + `listen`
|
||||
- On connection → revoke check → `PeerSession` → `registerAllHandlers`
|
||||
- Start Docker event stream, stats broadcast, tunnel restore, schedules
|
||||
- Graceful shutdown (close tunnels, stop streams)
|
||||
|
||||
### `server/core/`
|
||||
|
||||
| File | Role |
|
||||
|------|------|
|
||||
| `keys.js` | Load/generate `SERVER_SEED`, derive keypair, write `.env` |
|
||||
| `acl.js` | Resolve peer role; `assertAllowed` |
|
||||
| `audit.js` | Privileged method audit log append |
|
||||
| `peer-policy.js` | Invites, allowlist, revoke, redeem invite |
|
||||
| `peer-registry.js` | Live connected sessions map |
|
||||
| `registry-vault.js` | AES-GCM credential crypto helpers |
|
||||
|
||||
### `server/rpc/`
|
||||
|
||||
| File | Role |
|
||||
|------|------|
|
||||
| `session.js` | `PeerSession`: protomux-rpc, rate limit, ACL, schema, audit, metrics, pushes |
|
||||
| `register.js` | Wires all domain handlers; session cleanup |
|
||||
| `binary-stream.js` | Chunked binary transfer methods for large payloads |
|
||||
|
||||
### `server/handlers/` (domain RPC)
|
||||
|
||||
| File | Domain |
|
||||
|------|--------|
|
||||
| `containers.js` | list/start/stop/inspect/exec/… |
|
||||
| `images.js` | pull/push/build/prune/search/load/save |
|
||||
| `networks.js` | network CRUD + connect/disconnect |
|
||||
| `volumes.js` | volume CRUD + browse |
|
||||
| `stacks.js` | compose stacks, ps, logs, git sync |
|
||||
| `deploy.js` | high-level `deployContainer` / deploy flows |
|
||||
| `terminal.js` | interactive shells (multi-shell probe) |
|
||||
| `logs.js` | log streams |
|
||||
| `docker-cli.js` | allow-listed docker CLI passthrough |
|
||||
| `system.js` | info, df, prune, metrics, schedules, browse host |
|
||||
| `swarm.js` | Swarm mode APIs (`ENABLE_SWARM`) |
|
||||
| `plugins.js` | Engine plugins (`ENABLE_PLUGINS`) |
|
||||
| `peers.js` | list/invite/revoke/role |
|
||||
| `vault.js` | registry credential vault RPC |
|
||||
| `tunnels.js` | Holesail tunnel RPC |
|
||||
| `suggestions.js` | smart defaults for deploy UI |
|
||||
|
||||
### `server/services/`
|
||||
|
||||
| File | Role |
|
||||
|------|------|
|
||||
| `docker.js` | Shared dockerode instance / socket |
|
||||
| `events.js` | Docker event stream → pushes |
|
||||
| `stats.js` | Periodic stats broadcast |
|
||||
| `stats-history.js` | In-memory stats history for charts |
|
||||
| `metrics.js` | Process + RPC metrics |
|
||||
| `holesail-tunnels.js` | Create/list/close/restore Holesail servers |
|
||||
| `schedules.js` | Persisted scheduled maintenance jobs |
|
||||
|
||||
### `server/utils/`
|
||||
|
||||
| File | Role |
|
||||
|------|------|
|
||||
| `logger.js` | Structured pretty/json logger + banner |
|
||||
| `rateLimiter.js` | Per-peer method rate limits; stream hot methods |
|
||||
| `validation.js` | Shared validation helpers |
|
||||
| `dockerErrors.js` | Sanitize Docker errors for clients |
|
||||
| `composeManager.js` | Compose file deploy orchestration |
|
||||
| `containerConfig.js` | Normalize create/deploy options |
|
||||
| `engine-capabilities.js` | Probe what Engine supports |
|
||||
| `gitops.js` | Git pull for stack sync |
|
||||
| `suggestions.js` | Server-side suggestion logic |
|
||||
|
||||
---
|
||||
|
||||
## `client/` — connection stack
|
||||
|
||||
| File | Role |
|
||||
|------|------|
|
||||
| `connection.js` | Single DHT dial + protomux-rpc + push fan-in |
|
||||
| `manager.js` | Multi-peer manager, active peer, reconnect loop |
|
||||
| `api.js` | Typed `request` wrappers for Methods |
|
||||
| `peerCache.js` | Persist peers + last active to disk |
|
||||
| `jobs.js` | Long-running job tray model |
|
||||
| `errors.js` | Normalize RPC errors for UI |
|
||||
| `holesailLocal.js` | Local hs:// client (HTTP to Bare control or in-process) |
|
||||
| `holesailBareControl.cjs` | Bare-side real `holesail` control HTTP API |
|
||||
| `snapshot.js` | UI snapshot helpers |
|
||||
| `templateLists.js` | Template catalog lists |
|
||||
| `index.js` | Small re-export surface |
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
UI[UI code] --> API[api.js]
|
||||
UI --> MGR[manager.js]
|
||||
API --> MGR
|
||||
MGR --> CON[connection.js]
|
||||
CON --> DHT[hyperdht]
|
||||
CON --> RPC[protomux-rpc]
|
||||
MGR --> CACHE[peerCache.js]
|
||||
UI --> JOBS[jobs.js]
|
||||
UI --> HS[holesailLocal.js]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `ui/` and `libs/` — presentation
|
||||
|
||||
### `ui/`
|
||||
|
||||
| File | Role |
|
||||
|------|------|
|
||||
| `ops-app.js` | Main ops controller: views, Swarm, tunnels, host, settings hooks |
|
||||
| `track-g-ux.js` | Keyboard shortcuts, go-to view map, command palette pieces |
|
||||
| `track-f-extras.js` | Additional UI extras from roadmap track F |
|
||||
| `components.js` | Shared UI components |
|
||||
| `modern.css` / `ops.css` / `styles.css` | Styling layers |
|
||||
|
||||
### `libs/`
|
||||
|
||||
| File | Role |
|
||||
|------|------|
|
||||
| `templateDeploy.js` | Large deploy/template wizard logic |
|
||||
| `templateStorage.js` | Template persistence |
|
||||
| `terminal.js` / `dockerTerminal.js` / `xtermUtils.js` / `termInput.js` | Terminal UX |
|
||||
| `notifications.js` | Toasts / activity |
|
||||
| `uiUtils.js` | DOM helpers, job stepper integration |
|
||||
| `loadingStates.js` | Loading UI states |
|
||||
|
||||
### `index.html`
|
||||
|
||||
Single-page app shell: sidebar views (containers, images, networks, volumes, stacks, deploy, Swarm, tunnels, host, peers, settings, …), modals, job drawer, toast stack.
|
||||
|
||||
---
|
||||
|
||||
## `electron/` — packaged desktop
|
||||
|
||||
| File | Role |
|
||||
|------|------|
|
||||
| `main.cjs` | BrowserWindow, IPC, load UI |
|
||||
| `preload.cjs` | Safe bridge to renderer |
|
||||
| `ota.cjs` | Update-related helpers |
|
||||
| `app.bundle.cjs` | **Built artifact** from esbuild (do not hand-edit) |
|
||||
|
||||
Build: `npm run build:client-bundle` → `scripts/build-client-bundle.cjs`.
|
||||
|
||||
---
|
||||
|
||||
## `scripts/` — tooling
|
||||
|
||||
| Script | Role |
|
||||
|--------|------|
|
||||
| `install.sh` | End-user installer (served at install.peardock.boats) |
|
||||
| `make.cjs` | Orchestrate server/client multi-host builds |
|
||||
| `bare-standalone.cjs` | Pack Bare server binary |
|
||||
| `build-client-bundle.cjs` | esbuild client bundle |
|
||||
| `gitea-rolling-release.sh` | CI publish to `rolling` |
|
||||
| `hosts.cjs` | Canonical 64-bit host list |
|
||||
| `sign-macos-app.cjs` | Ad-hoc / identity sign |
|
||||
| `healthcheck.js` | Docker socket probe |
|
||||
| `soak.js` | Long-run certification |
|
||||
| `release-checksums.sh` | Checksums / optional GPG |
|
||||
| `predownload-electron.cjs` | Prefetch Electron for forge |
|
||||
|
||||
---
|
||||
|
||||
## `deploy/`
|
||||
|
||||
| File | Role |
|
||||
|------|------|
|
||||
| `peardock.service` | systemd unit for Bare binary + docker group |
|
||||
|
||||
---
|
||||
|
||||
## `test/`
|
||||
|
||||
Brittle-based tests covering ACL, protocol, vault, peer policy, holesail, terminal shells, visual structure of `index.html`, RPC integration, fuzz/load, etc.
|
||||
|
||||
Run: `npm test`.
|
||||
|
||||
---
|
||||
|
||||
## `build/` and `bin/`
|
||||
|
||||
| Path | Role |
|
||||
|------|------|
|
||||
| `build/stubs/*` | Stub modules for optional natives during Bare pack |
|
||||
| `build/shims/*` | Node API shims |
|
||||
| `bin/peardock-server.mjs` | Server launcher with Bare global when needed |
|
||||
| `bin/peardock-client.mjs` | Client helper entry |
|
||||
|
||||
---
|
||||
|
||||
## Config and identity files (runtime)
|
||||
|
||||
Created on server host (cwd or `/opt/peardock`):
|
||||
|
||||
| File | Creator |
|
||||
|------|---------|
|
||||
| `.env` | `server/core/keys.js` |
|
||||
| `peardock-peers.json` | peer-policy |
|
||||
| `peardock-vault.json` | vault |
|
||||
| `peardock-audit.log` | audit |
|
||||
| `peardock-tunnels.json` | holesail-tunnels |
|
||||
| schedule store | schedules service |
|
||||
|
||||
Client peer cache: see `client/peerCache.js` (XDG-style config dir).
|
||||
|
||||
---
|
||||
|
||||
## Dependency map (major)
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph Transport
|
||||
HD[hyperdht]
|
||||
PM[protomux / protomux-rpc]
|
||||
CE[compact-encoding / b4a]
|
||||
end
|
||||
subgraph Engine
|
||||
DO[dockerode]
|
||||
YAML[js-yaml]
|
||||
end
|
||||
subgraph Tunnels
|
||||
HS[holesail]
|
||||
end
|
||||
subgraph Desktop
|
||||
PE[pear-electron / pear-bridge]
|
||||
EL[electron via forge]
|
||||
end
|
||||
SRV[server] --> HD
|
||||
SRV --> PM
|
||||
SRV --> DO
|
||||
SRV --> HS
|
||||
CLI[client] --> HD
|
||||
CLI --> PM
|
||||
UI[ui] --> CLI
|
||||
UI --> EL
|
||||
UI --> PE
|
||||
```
|
||||
|
||||
Full license notes: [SBOM.md](./SBOM.md).
|
||||
|
||||
---
|
||||
|
||||
## Where to change common things
|
||||
|
||||
| Goal | Primary touch points |
|
||||
|------|----------------------|
|
||||
| New RPC method | `shared/protocol.js` → handler → `register.js` → `client/api.js` → UI |
|
||||
| New sidebar view | `index.html` + `ui/ops-app.js` + `ui/track-g-ux.js` go-map |
|
||||
| Tighten security | `MethodRoles`, `acl.js`, env defaults, THREAT_MODEL |
|
||||
| Install experience | `scripts/install.sh`, `deploy/peardock.service`, website download page |
|
||||
| Release matrix | `scripts/hosts.cjs`, `make.cjs`, `gitea-rolling-release.sh` |
|
||||
| Tunnel policy | `services/holesail-tunnels.js`, `handlers/tunnels.js`, HOLESAIL.md |
|
||||
@@ -0,0 +1,161 @@
|
||||
# Feature catalog
|
||||
|
||||
What PearDock can do today, mapped to code and protocol surfaces.
|
||||
|
||||
---
|
||||
|
||||
## Feature matrix
|
||||
|
||||
| Area | Status | Default | Primary code |
|
||||
|------|--------|---------|--------------|
|
||||
| Container lifecycle | shipped | on | `handlers/containers.js` |
|
||||
| Image pull/build/load/save | shipped | on | `handlers/images.js` |
|
||||
| Networks / volumes | shipped | on | `handlers/networks.js`, `volumes.js` |
|
||||
| Compose stacks + GitOps | shipped | on | `handlers/stacks.js`, `utils/gitops.js` |
|
||||
| Deploy templates UI | shipped | on | `libs/templateDeploy.js` |
|
||||
| Logs follow | shipped | on | `handlers/logs.js` |
|
||||
| Interactive terminals | shipped | on | `handlers/terminal.js` |
|
||||
| Multi-shell probe | shipped | on | terminal handler + tests |
|
||||
| Stats + history | shipped | on | `services/stats*.js` |
|
||||
| Docker events push | shipped | on | `services/events.js` |
|
||||
| System prune / df / info | shipped | on | `handlers/system.js` |
|
||||
| Schedules | shipped | on | `services/schedules.js` |
|
||||
| Registry vault | shipped | on | `handlers/vault.js`, `core/registry-vault.js` |
|
||||
| Peer invites / revoke / roles | shipped | on | `handlers/peers.js`, `core/peer-policy.js` |
|
||||
| Audit log | shipped | opt-in env | `core/audit.js` |
|
||||
| Holesail tunnels | shipped | **on** | `handlers/tunnels.js`, `services/holesail-tunnels.js` |
|
||||
| Docker Swarm | shipped | **on** | `handlers/swarm.js` |
|
||||
| Engine plugins | shipped | **off** | `handlers/plugins.js` |
|
||||
| Suggestions / smart defaults | shipped | on | `handlers/suggestions.js` |
|
||||
| Binary streams | shipped | on | `rpc/binary-stream.js` |
|
||||
| Multi-peer fleet UI | shipped | on | `client/manager.js` |
|
||||
| Auto-reconnect 5s | shipped | on | `client/manager.js` |
|
||||
| Job tray | shipped | on | `client/jobs.js` |
|
||||
| Keyboard UX | shipped | on | `ui/track-g-ux.js` |
|
||||
| Rolling binaries | shipped | CI | `scripts/gitea-rolling-release.sh` |
|
||||
| One-line installer | shipped | public | `scripts/install.sh` |
|
||||
|
||||
---
|
||||
|
||||
## Containers
|
||||
|
||||
**UI:** Containers view + detail pane.
|
||||
**RPC:** list/inspect/start/stop/restart/kill/pause/unpause/remove/recreate/rename/update/deploy/bulk/top/stats/logs/exec/attach/commit/export/archive/duplicate/prune.
|
||||
|
||||
Deploy form collects name, image, ports, env, volumes, restart policy, resources, security options (single-page form; no multi-step wizard chrome).
|
||||
|
||||
---
|
||||
|
||||
## Images
|
||||
|
||||
Pull with progress pushes; build; Hub search; tag; prune; load/save chunked transfer for large artifacts.
|
||||
|
||||
---
|
||||
|
||||
## Networks and volumes
|
||||
|
||||
Full CRUD for operators/admins per role table. Volume browse for inspecting files inside volumes (role-gated). Host directory browse restricted by `PEARDOCK_BROWSE_ROOTS`.
|
||||
|
||||
---
|
||||
|
||||
## Stacks / Compose
|
||||
|
||||
- Deploy compose content (+ override, env file, profiles, build).
|
||||
- List stacks, `ps`, logs, pull.
|
||||
- `syncStackFromGit` for GitOps-style refresh (`utils/gitops.js`).
|
||||
|
||||
---
|
||||
|
||||
## Docker Swarm
|
||||
|
||||
When Engine is swarm-active and `ENABLE_SWARM` is on:
|
||||
|
||||
| Tab | Data |
|
||||
|-----|------|
|
||||
| Services | Name, image, replicas, scale |
|
||||
| Nodes | Hostname, role, status, availability |
|
||||
| Tasks | Service/node/state |
|
||||
| Secrets / Configs | Inventory (+ create RPC) |
|
||||
|
||||
Also: swarm init/join/leave/update for admins; service logs.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
UI[Swarm view] --> INS[swarmInspect]
|
||||
UI --> LS[listServices]
|
||||
UI --> LN[listNodes]
|
||||
UI --> LT[listTasks]
|
||||
UI --> SC[scaleService]
|
||||
INS --> DK[dockerd Swarm APIs]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Holesail tunnels
|
||||
|
||||
- Create from host:port or container published port.
|
||||
- Secure mode default; SSRF allowlist; max tunnels.
|
||||
- List/copy `hs://`; close; restore on boot.
|
||||
- Local client connect from UI (Pear Bare control or Node path).
|
||||
|
||||
See [HOLESAIL.md](./HOLESAIL.md).
|
||||
|
||||
---
|
||||
|
||||
## Access control and vault
|
||||
|
||||
- Roles viewer/operator/admin.
|
||||
- Invites with TTL/max uses; revoke/unrevoke; set role.
|
||||
- Optional allowlist mode.
|
||||
- Registry credentials vault encrypted with material from server seed.
|
||||
|
||||
---
|
||||
|
||||
## Observability
|
||||
|
||||
- Live stats push for container CPU/memory.
|
||||
- Stats history for sparklines/charts in UI.
|
||||
- `getMetrics` for server process + RPC percentiles + feature flags.
|
||||
- Optional audit log for privileged methods.
|
||||
- Structured logs (json) for journald.
|
||||
|
||||
---
|
||||
|
||||
## Terminals
|
||||
|
||||
- Start interactive shell in container.
|
||||
- Multi-shell probe (bash/sh/…; handles missing shells).
|
||||
- Resize + input streaming; hot-path RPC for low latency.
|
||||
- Separate docker CLI allow-list terminal for constrained CLI use.
|
||||
|
||||
---
|
||||
|
||||
## Schedules
|
||||
|
||||
Admin-defined recurring maintenance (e.g. prune) persisted and restored at boot.
|
||||
|
||||
---
|
||||
|
||||
## Packaging features
|
||||
|
||||
| Feature | Notes |
|
||||
|---------|-------|
|
||||
| 6×64-bit server hosts | Bare standalone |
|
||||
| 6×64-bit client hosts | Electron forge |
|
||||
| rolling tag | Continuous release channel |
|
||||
| install.sh | Server systemd + client install |
|
||||
| macOS ad-hoc sign | Gatekeeper-friendly CI builds |
|
||||
|
||||
---
|
||||
|
||||
## Explicitly not product goals
|
||||
|
||||
- Hosted multi-tenant PearDock cloud.
|
||||
- Replacing Kubernetes.
|
||||
- Guaranteeing connectivity on all symmetric NAT without relay (Holepunch stack best-effort + relay fallback where applicable).
|
||||
|
||||
---
|
||||
|
||||
## Roadmap pointer
|
||||
|
||||
Completed and remaining UX/engine tracks: [../ROADMAP.md](../ROADMAP.md).
|
||||
+25
-21
@@ -1,16 +1,16 @@
|
||||
# Holesail integration
|
||||
|
||||
[Holesail](https://github.com/holesail/holesail) is a peer-to-peer **TCP/UDP reverse proxy** on HyperDHT. peardock uses HyperDHT + protomux-rpc for the **Docker control plane**; Holesail is integrated **beside** that path so operators can share **published ports / local services** via `hs://` keys without opening firewall ports.
|
||||
[Holesail](https://github.com/holesail/holesail) is a peer-to-peer **TCP/UDP reverse proxy** on HyperDHT. PearDock uses HyperDHT + protomux-rpc for the **Docker control plane**. Holesail sits **beside** that path so operators can share **published ports / local services** via `hs://` keys without opening firewall ports.
|
||||
|
||||
## Architecture (do not conflate the two planes)
|
||||
|
||||
| Plane | Technology | Purpose |
|
||||
|-------|------------|---------|
|
||||
| **Control** | HyperDHT + protomux-rpc | peardock RPC: containers, deploy, logs, ACL |
|
||||
| **Control** | HyperDHT + protomux-rpc | PearDock RPC: containers, deploy, logs, ACL |
|
||||
| **Data / tunnels** | Holesail (`holesail` package) | L4 proxy of `host:port` ↔ remote peer via `hs://` |
|
||||
|
||||
```
|
||||
[Pear client] --protomux-rpc / HyperDHT--> [peardock server] --dockerode--> dockerd
|
||||
[Desktop client] --protomux-rpc / HyperDHT--> [peardock server] --dockerode--> dockerd
|
||||
|
|
||||
+-- HolesailServer instances (per tunnel)
|
||||
|
|
||||
@@ -19,15 +19,15 @@
|
||||
[remote user] --holesail client--> local bind --P2P--> that tunnel
|
||||
```
|
||||
|
||||
**Why not replace peardock RPC with Holesail?**
|
||||
Holesail is not an RPC framework. It tunnels bytes between TCP/UDP sockets. peardock needs structured methods, roles, audit, and pushes (stats, logs). Keep both.
|
||||
**Why not replace PearDock RPC with Holesail?**
|
||||
Holesail is not an RPC framework. It tunnels bytes between TCP/UDP sockets. PearDock needs structured methods, roles, audit, and pushes (stats, logs). Keep both.
|
||||
|
||||
## Enable / disable
|
||||
|
||||
Holesail tunnels are **enabled by default**. The `holesail` package is a required dependency.
|
||||
|
||||
```bash
|
||||
# default — tunnels on
|
||||
# default: tunnels on
|
||||
npm run server
|
||||
|
||||
# opt out
|
||||
@@ -53,13 +53,13 @@ Optional env:
|
||||
| `createTunnel` | operator | Start tunnel (`host`/`port` or `containerId`+`containerPort`) |
|
||||
| `closeTunnel` | operator | Stop tunnel |
|
||||
|
||||
`createTunnel` always uses **secure** mode by default (`secure: true`) so the DHT capability is not the raw listen key.
|
||||
`createTunnel` uses **secure** mode by default (`secure: true`) so the DHT capability is not the raw listen key.
|
||||
|
||||
## UI
|
||||
|
||||
Sidebar → **Tunnels**: create by host/port, list active tunnels, copy `hs://` URL, close.
|
||||
Sidebar → **Tunnels**: create by host/port, list active tunnels, copy `hs://` URL, close. Container details can offer “Tunnel this port” on published ports.
|
||||
|
||||
Remote connect (outside peardock):
|
||||
Remote connect (outside PearDock):
|
||||
|
||||
```bash
|
||||
npx holesail 'hs://s000…'
|
||||
@@ -68,7 +68,7 @@ npx holesail 'hs://s000…'
|
||||
|
||||
## License note
|
||||
|
||||
`holesail` is **AGPL-3.0** and is a **required** peardock dependency. PearDock itself is also **AGPL-3.0** (aligned with Holesail). Operators distributing modified server builds that users interact with over a network should review AGPL source-offer obligations. See `docs/SBOM.md`. Set `ENABLE_HOLESAIL=0` if you must run without opening tunnels (package still installed).
|
||||
`holesail` is **AGPL-3.0** and is a **required** PearDock dependency. PearDock itself is also **AGPL-3.0** (aligned with Holesail). Operators distributing modified server builds that users interact with over a network should review AGPL source-offer obligations. See [SBOM.md](./SBOM.md). Set `ENABLE_HOLESAIL=0` if you must run without opening tunnels (package still installed).
|
||||
|
||||
## Security
|
||||
|
||||
@@ -76,19 +76,23 @@ npx holesail 'hs://s000…'
|
||||
- Default targets are loopback / published Docker binds only.
|
||||
- Create/close are audited when `PEARDOCK_AUDIT` is on.
|
||||
- Max tunnel count limits resource exhaustion.
|
||||
- Do **not** reuse peardock’s `SERVER_SEED` for tunnel keypairs — each tunnel gets its own Holesail seed.
|
||||
- Do **not** reuse PearDock’s `SERVER_SEED` for tunnel keypairs. Each tunnel gets its own Holesail seed.
|
||||
|
||||
## Implemented extensions
|
||||
## Client-side local proxy
|
||||
|
||||
1. **Container UI action** — “Tunnel this port” on container details port table.
|
||||
2. **Client-side local proxy** — Tunnels → plug icon runs the **real `holesail` package**.
|
||||
Under pear-electron the UI renderer cannot load bare-tcp (`require.addon is not a function`). Real holesail runs in the **Bare main process**:
|
||||
- Bare entry `index.js` starts `client/holesailBareControl.cjs` (`require('holesail')`) on `http://127.0.0.1:<port>`
|
||||
- Control code uses **Bare modules** (`bare-crypto`, `bare-fs`, `bare-http1`, `bare-net`, …) via `package.json` `imports` maps + `which-runtime`
|
||||
- Endpoint + token written to `<Pear.config.storage>/peardock-holesail-local.json`
|
||||
- UI `client/holesailLocal.js` POSTs `/connect` / `/disconnect` (never loads bare-tcp)
|
||||
- Node tests / non-Pear: in-process `require('holesail')`
|
||||
3. **Persist tunnel definitions** — `peardock-tunnels.json` (mode 600); restored on server boot with the same `hs://` keys when possible.
|
||||
Tunnels → plug icon runs the real `holesail` package.
|
||||
|
||||
Under pear-electron the UI renderer cannot load bare-tcp (`require.addon is not a function`). Real Holesail runs in the **Bare main process**:
|
||||
|
||||
- Bare entry `index.js` starts `client/holesailBareControl.cjs` (`require('holesail')`) on `http://127.0.0.1:<port>`
|
||||
- Control code uses Bare modules (`bare-crypto`, `bare-fs`, `bare-http1`, `bare-net`, …) via `package.json` `imports` maps + `which-runtime`
|
||||
- Endpoint + token written to `<Pear.config.storage>/peardock-holesail-local.json`
|
||||
- UI `client/holesailLocal.js` POSTs `/connect` / `/disconnect` (never loads bare-tcp)
|
||||
- Node tests / non-Pear: in-process `require('holesail')`
|
||||
|
||||
## Persistence
|
||||
|
||||
Tunnel definitions are stored in `peardock-tunnels.json` (mode 600) and restored on server boot with the same `hs://` keys when possible.
|
||||
|
||||
## Optional later
|
||||
|
||||
|
||||
+116
-55
@@ -1,38 +1,88 @@
|
||||
# peardock operator guide
|
||||
|
||||
## Install (server host)
|
||||
Production install, multi-operator ACL, feature flags, logging, compose stacks, and backup.
|
||||
|
||||
Architecture and code docs: [README.md](./README.md) (hub) · [ARCHITECTURE.md](./ARCHITECTURE.md) · [PROTOCOL.md](./PROTOCOL.md).
|
||||
|
||||
## Recommended: one-line installer
|
||||
|
||||
On a Linux host with Docker already installed:
|
||||
|
||||
```bash
|
||||
git clone <repo> /opt/peardock
|
||||
curl -fsSL https://install.peardock.boats | bash -s -- --server --yes
|
||||
```
|
||||
|
||||
This downloads the Bare server binary from the **`rolling`** release, creates user `peardock`, adds it to the **docker** group, writes `/etc/systemd/system/peardock.service`, and can enable the service.
|
||||
|
||||
```bash
|
||||
sudo systemctl status peardock
|
||||
sudo journalctl -u peardock -f
|
||||
# copy public key from logs into the desktop client
|
||||
```
|
||||
|
||||
Desktop client (Linux or macOS):
|
||||
|
||||
```bash
|
||||
curl -fsSL https://install.peardock.boats | bash -s -- --client --yes
|
||||
```
|
||||
|
||||
Flags and Windows notes: [peardock.boats/download](https://peardock.boats/download).
|
||||
|
||||
### Installer defaults
|
||||
|
||||
| Path | Role |
|
||||
|------|------|
|
||||
| `/opt/peardock/peardock-server` | Server binary |
|
||||
| `/opt/peardock/.env` | Identity / env (mode 600) |
|
||||
| `/etc/systemd/system/peardock.service` | systemd unit |
|
||||
| `journalctl -u peardock` | Logs |
|
||||
|
||||
Unit source of truth: [`deploy/peardock.service`](../deploy/peardock.service).
|
||||
|
||||
## Docker group / socket access
|
||||
|
||||
Engine sockets are typically `root:docker` mode `660`. The service **must** run as a user in group `docker` (or the socket’s group).
|
||||
|
||||
```bash
|
||||
# Installer does this; manual recovery:
|
||||
sudo usermod -aG docker peardock
|
||||
# if docker group was just created, restart Docker once
|
||||
sudo systemctl restart docker
|
||||
sudo systemctl restart peardock
|
||||
```
|
||||
|
||||
Symptoms of a missing group: empty container lists, start failures, “permission denied” on the socket.
|
||||
|
||||
## Manual install from source
|
||||
|
||||
```bash
|
||||
git clone https://git.ssh.surf/snxraven/peardock.git /opt/peardock
|
||||
cd /opt/peardock
|
||||
npm ci --omit=dev
|
||||
# Node.js ≥ 20
|
||||
npm ci --omit=dev # Node.js ≥ 20
|
||||
|
||||
sudo useradd --system --home /opt/peardock --shell /usr/sbin/nologin peardock
|
||||
sudo usermod -aG docker peardock
|
||||
sudo cp deploy/peardock.service /etc/systemd/system/
|
||||
# edit ExecStart if using node instead of Bare binary:
|
||||
# ExecStart=/usr/bin/node /opt/peardock/server/server.js
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable --now peardock
|
||||
sudo journalctl -u peardock -f
|
||||
```
|
||||
|
||||
Create user and systemd unit:
|
||||
|
||||
```bash
|
||||
useradd --system --home /opt/peardock --shell /usr/sbin/nologin peardock
|
||||
usermod -aG docker peardock
|
||||
cp deploy/peardock.service /etc/systemd/system/
|
||||
systemctl daemon-reload
|
||||
systemctl enable --now peardock
|
||||
journalctl -u peardock -f
|
||||
```
|
||||
|
||||
Copy the **public key** printed in logs into the Pear client.
|
||||
Copy the **public key** printed in logs into the desktop client.
|
||||
|
||||
## Healthcheck
|
||||
|
||||
```bash
|
||||
node scripts/healthcheck.js
|
||||
# exit 0 if Docker socket answers ping
|
||||
# exit 0 if Docker socket answers
|
||||
```
|
||||
|
||||
## Multi-operator setup
|
||||
|
||||
1. On first boot, note your client’s peer id (handshake returns `peerId`) or the key shown after connect.
|
||||
2. `.env` example:
|
||||
1. On first connect, note the client’s peer id (handshake returns `peerId`).
|
||||
2. Configure `/opt/peardock/.env` (or `Environment=` in the unit):
|
||||
|
||||
```bash
|
||||
PEARDOCK_DEFAULT_ROLE=viewer
|
||||
@@ -42,27 +92,28 @@ PEARDOCK_BROWSE_ROOTS=/var/lib/docker/volumes
|
||||
PEARDOCK_AUDIT=1
|
||||
```
|
||||
|
||||
3. As admin, open **Access** in the UI → **Create invite** → send token to operator.
|
||||
4. Operator connects with public key; paste invite token when supported, or admin registers their peer id.
|
||||
3. As admin: **Access → Create invite** → send token to operator.
|
||||
4. Operator connects with server public key + invite token as supported.
|
||||
5. **Revoke** lost devices from Access.
|
||||
|
||||
See [THREAT_MODEL.md](./THREAT_MODEL.md) for hardening rationale.
|
||||
|
||||
## Registry vault
|
||||
|
||||
Store credentials encrypted (AES-GCM derived from `SERVER_SEED`):
|
||||
Credentials encrypted at rest (AES-GCM derived from `SERVER_SEED`):
|
||||
|
||||
- UI: **Access → Registry vault**
|
||||
- RPC: `vaultStoreCredential`, `vaultUseCredential`, `listVaultCredentials`
|
||||
|
||||
Vault file: `peardock-vault.json` (mode 600). Override path with `PEARDOCK_VAULT_PATH`.
|
||||
- File: `peardock-vault.json` (mode 600). Override with `PEARDOCK_VAULT_PATH`.
|
||||
|
||||
## Feature flags
|
||||
|
||||
| Env | Effect |
|
||||
|-----|--------|
|
||||
| `ENABLE_SWARM=0` | Disable Swarm / services / secrets / configs RPC (on by default) |
|
||||
| `ENABLE_PLUGINS=1` | Plugin install/enable/remove |
|
||||
| `ENABLE_HOLESAIL=0` | Disable Holesail P2P port tunnels (on by default) — see [HOLESAIL.md](./HOLESAIL.md) |
|
||||
| `PEARDOCK_MAX_TUNNELS` | Max concurrent Holesail tunnels (default 20) |
|
||||
| `ENABLE_SWARM=0` | Disable Swarm / services / secrets / configs RPC (**on by default**) |
|
||||
| `ENABLE_PLUGINS=1` | Plugin install/enable/remove (**off** unless set) |
|
||||
| `ENABLE_HOLESAIL=0` | Disable Holesail tunnels (**on by default**). See [HOLESAIL.md](./HOLESAIL.md) |
|
||||
| `PEARDOCK_MAX_TUNNELS` | Max concurrent tunnels (default 20) |
|
||||
| `PEARDOCK_TUNNEL_HOSTS` | Extra allowed tunnel target hosts (comma-separated) |
|
||||
| `PEARDOCK_UNRESTRICTED_CLI=1` | Broader `docker` CLI for **admin** |
|
||||
| `PEARDOCK_BROWSE_OPEN=1` | Legacy open host FS browse (discouraged) |
|
||||
@@ -71,21 +122,18 @@ Vault file: `peardock-vault.json` (mode 600). Override path with `PEARDOCK_VAULT
|
||||
|
||||
| Env | Effect |
|
||||
|-----|--------|
|
||||
| `LOG_LEVEL` | `error` · `warn` · `info` (default in production) · `debug` (default in dev) |
|
||||
| `LOG_FORMAT` | `pretty` (TTY default) · `json` (default when non-TTY / good for journald) |
|
||||
| `ENABLE_FILE_LOGGING=1` | Append JSON lines under `LOG_DIR` (default `./logs`) with size rotation |
|
||||
| `LOG_DIR` | Directory for file logs |
|
||||
| `NO_COLOR=1` | Disable ANSI colors in pretty mode |
|
||||
| `LOG_LEVEL` | `error` · `warn` · `info` (default production) · `debug` (default dev) |
|
||||
| `LOG_FORMAT` | `pretty` (TTY) · `json` (non-TTY / journald) |
|
||||
| `ENABLE_FILE_LOGGING=1` | JSON lines under `LOG_DIR` with size rotation |
|
||||
| `LOG_DIR` | Directory for file logs (default `./logs`) |
|
||||
| `NO_COLOR=1` | Disable ANSI in pretty mode |
|
||||
| `LOG_STACK=1` | Include error stacks in structured fields |
|
||||
|
||||
Examples:
|
||||
|
||||
```bash
|
||||
# systemd / journald
|
||||
LOG_FORMAT=json LOG_LEVEL=info npm run server
|
||||
|
||||
# local development
|
||||
LOG_LEVEL=debug npm run server
|
||||
# Prefer journald JSON in production
|
||||
# Environment=LOG_FORMAT=json
|
||||
# Environment=LOG_LEVEL=info
|
||||
sudo journalctl -u peardock -f
|
||||
```
|
||||
|
||||
## Compose stacks
|
||||
@@ -93,40 +141,53 @@ LOG_LEVEL=debug npm run server
|
||||
`deployStack` accepts:
|
||||
|
||||
- `composeContent` (required)
|
||||
- `overrideContent` — second compose file
|
||||
- `envFileContent` — written as `.env` for compose CLI
|
||||
- `profiles` — array or comma string
|
||||
- `build: true` — `docker compose up --build`
|
||||
- `overrideContent`: second compose file
|
||||
- `envFileContent`: written as `.env` for compose CLI
|
||||
- `profiles`: array or comma string
|
||||
- `build: true`: runs `docker compose up --build`
|
||||
|
||||
## Swarm
|
||||
|
||||
When Docker is in Swarm mode, the client **Swarm** view lists services, nodes, tasks, secrets, and configs, and can scale replicated services. Server RPC covers init/join/leave, service CRUD, scale, logs, nodes, tasks, secrets, and configs. Disable the whole surface with `ENABLE_SWARM=0`.
|
||||
|
||||
## Metrics
|
||||
|
||||
RPC `getMetrics` returns process memory, RPC counters, latency percentiles, feature flags.
|
||||
RPC `getMetrics` returns process memory, RPC counters, latency percentiles, and feature flags.
|
||||
|
||||
## Client (Pear)
|
||||
## Client
|
||||
|
||||
```bash
|
||||
npm run dev # development
|
||||
pear stage . && pear release .
|
||||
# Packaged (from installer / rolling release)
|
||||
# Linux: peardock-client on PATH · macOS: ~/Applications/peardock.app
|
||||
|
||||
# From source
|
||||
npm run start:client # Electron (recommended for operators)
|
||||
npm run dev # Pear shell (development)
|
||||
```
|
||||
|
||||
## Updating the server
|
||||
|
||||
1. Re-run `install.sh --server --yes`, or replace `/opt/peardock/peardock-server` from a newer rolling tarball.
|
||||
2. **Keep** `/opt/peardock/.env` (seed) and vault/peer files.
|
||||
3. `sudo systemctl restart peardock`
|
||||
|
||||
## Certification / soak
|
||||
|
||||
```bash
|
||||
npm run soak # 60s Docker health loop
|
||||
npm run soak:24h # 24h certification (see docs/RELEASE.md)
|
||||
npm run soak # short Docker health loop
|
||||
npm run soak:24h # 24h certification
|
||||
```
|
||||
|
||||
## Releases
|
||||
|
||||
See `docs/RELEASE.md` for tarball checksums, GPG signing, and Pear stage/release.
|
||||
See [RELEASE.md](./RELEASE.md) for release packaging.
|
||||
|
||||
## Backup
|
||||
|
||||
Back up atomically:
|
||||
Back up atomically from the server data dir (default `/opt/peardock`):
|
||||
|
||||
- `.env` (`SERVER_SEED`)
|
||||
- `peardock-vault.json`
|
||||
- `peardock-peers.json`
|
||||
- `peardock-tunnels.json` (if tunnels used)
|
||||
- `peardock-audit.log` (optional)
|
||||
|
||||
Rotating `SERVER_SEED` changes the public key; all clients must reconnect and vault must be re-keyed (re-store credentials).
|
||||
Rotating `SERVER_SEED` changes the public key; all clients must reconnect and vault credentials must be re-stored.
|
||||
|
||||
@@ -0,0 +1,285 @@
|
||||
# Protocol reference
|
||||
|
||||
Wire protocol for PearDock v2 control plane.
|
||||
|
||||
**Source of truth:** [`shared/protocol.js`](../shared/protocol.js), [`shared/encodings.js`](../shared/encodings.js), [`shared/schema.js`](../shared/schema.js).
|
||||
|
||||
---
|
||||
|
||||
## Constants
|
||||
|
||||
| Name | Value | Meaning |
|
||||
|------|-------|---------|
|
||||
| `PROTOCOL` | `peardock/rpc` | protomux-rpc protocol id |
|
||||
| `PROTOCOL_VERSION` | `2` | Negotiated in `handshake` |
|
||||
| Encoding | compact-encoding JSON-ish values | Via `shared/encodings.js` |
|
||||
|
||||
Bump `PROTOCOL_VERSION` when breaking request/response shapes.
|
||||
|
||||
---
|
||||
|
||||
## Roles
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
V[viewer] --> O[operator] --> A[admin]
|
||||
```
|
||||
|
||||
| Role | Intent |
|
||||
|------|--------|
|
||||
| `viewer` | Observe: lists, inspect, logs follow, metrics, tunnel list |
|
||||
| `operator` | Day-to-day mutate: start/stop, deploy, pull, scale, create tunnel |
|
||||
| `admin` | Destructive / policy: remove, prune, vault store, swarm init/leave, invites |
|
||||
|
||||
Rank: viewer (1) < operator (2) < admin (3).
|
||||
`roleAllows(role, method)` requires rank ≥ `MethodRoles[method]` (default **admin** if method missing).
|
||||
|
||||
Default peer role when unconfigured: **admin** (`resolveRole` / `PEARDOCK_DEFAULT_ROLE`).
|
||||
|
||||
---
|
||||
|
||||
## Session flow
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant C as Client
|
||||
participant S as Server PeerSession
|
||||
|
||||
C->>S: stream open (Noise)
|
||||
Note over S: role = resolveRole(peerId)
|
||||
C->>S: handshake
|
||||
S-->>C: peerId, role, protocolVersion, docker status
|
||||
loop
|
||||
C->>S: method request
|
||||
S-->>C: result or error { code, message }
|
||||
end
|
||||
par pushes
|
||||
S-->>C: push:containers / push:allStats / …
|
||||
end
|
||||
```
|
||||
|
||||
### Handshake
|
||||
|
||||
- Client may send invite token for peer registration.
|
||||
- Server validates protocol version compatibility.
|
||||
- Returns assigned **role** and **peerId** (client public key hex).
|
||||
|
||||
### Errors
|
||||
|
||||
Normalized codes (non-exhaustive):
|
||||
|
||||
| Code | Meaning |
|
||||
|------|---------|
|
||||
| `PERMISSION_DENIED` | Role too low |
|
||||
| `RATE_LIMIT_EXCEEDED` | Per-peer limiter |
|
||||
| `INVALID_ARGS` | Schema validation failed |
|
||||
| `FEATURE_DISABLED` | Swarm/Holesail/plugins gated off |
|
||||
| Docker-derived | Sanitized via `dockerErrors.js` |
|
||||
|
||||
---
|
||||
|
||||
## Middleware (server)
|
||||
|
||||
Order for normal methods:
|
||||
|
||||
1. Rate limit
|
||||
2. ACL (`assertAllowed`)
|
||||
3. `validateMethodArgs`
|
||||
4. Handler
|
||||
5. Audit (if privileged)
|
||||
6. Metrics latency
|
||||
|
||||
**Hot methods** (terminal I/O, stream chunks): ACL + handler only (performance).
|
||||
|
||||
---
|
||||
|
||||
## Methods by domain
|
||||
|
||||
Below is a **catalog** of `Methods` names. Minimum roles are in `MethodRoles` (same file). Prefer reading the source when adding methods.
|
||||
|
||||
### Session
|
||||
|
||||
| Method | Min role | Notes |
|
||||
|--------|----------|-------|
|
||||
| `handshake` | viewer | Version + invite |
|
||||
| `ping` | viewer | Liveness / latency |
|
||||
|
||||
### Containers
|
||||
|
||||
| Method | Min role |
|
||||
|--------|----------|
|
||||
| `listContainers`, `inspectContainer`, `containerTop`, `containerStats`, `getStatsHistory`, `getContainerLogs` | viewer |
|
||||
| `startContainer`, `stopContainer`, `restartContainer`, `killContainer`, `pauseContainer`, `unpauseContainer`, `renameContainer`, `updateContainer`, `createContainer`, `deployContainer`, `bulkContainerOperation`, `waitContainer`, `attachContainer`, `attachInput`, `execContainer`, `execInput` | operator |
|
||||
| `removeContainer`, `recreateContainer`, `commitContainer`, `exportContainer`, `duplicateContainer`, `pruneContainers`, `archiveContainerGet`, `archiveContainerPut` | admin |
|
||||
|
||||
### Images
|
||||
|
||||
| Method | Min role |
|
||||
|--------|----------|
|
||||
| `listImages`, `inspectImage`, `imageHistory`, `searchImages` | viewer |
|
||||
| `pullImage`, `pushImage`, `tagImage`, `saveImage*`, `loadImage*`, `importImage` | operator |
|
||||
| `removeImage`, `buildImage`, `pruneImages`, `pruneBuilder` | admin |
|
||||
|
||||
### Networks / volumes
|
||||
|
||||
| Method | Min role |
|
||||
|--------|----------|
|
||||
| `list*`, `inspect*` | viewer |
|
||||
| `connectNetwork`, `disconnectNetwork`, `browseVolume` | operator / viewer (browseVolume viewer) |
|
||||
| `create*`, `remove*`, `prune*` | admin (create network/volume admin; connect operator) |
|
||||
|
||||
*(Exact ranks: see `MethodRoles` — create network/volume are admin; connect/disconnect operator.)*
|
||||
|
||||
### Stacks / GitOps
|
||||
|
||||
| Method | Min role |
|
||||
|--------|----------|
|
||||
| `listStacks`, `stackPs`, `stackLogs` | viewer |
|
||||
| `deployStack`, `stackPull`, `syncStackFromGit` | operator |
|
||||
| `removeStack` | admin |
|
||||
|
||||
### System / host
|
||||
|
||||
| Method | Min role |
|
||||
|--------|----------|
|
||||
| `getSystemInfo`, `getSystemDf`, `getDockerEvents`, `getMetrics`, `listSchedules`, `getAuthStatus` | viewer |
|
||||
| `dockerCommand`, `browseDirectory`, `registryLogin` | operator / admin per MethodRoles |
|
||||
| `systemPrune`, schedule upsert/delete | admin |
|
||||
|
||||
### Vault
|
||||
|
||||
| Method | Min role |
|
||||
|--------|----------|
|
||||
| `listVaultCredentials` | viewer |
|
||||
| `vaultUseCredential` | operator |
|
||||
| `vaultStoreCredential`, `vaultDeleteCredential` | admin |
|
||||
|
||||
### Peers / ACL
|
||||
|
||||
| Method | Min role |
|
||||
|--------|----------|
|
||||
| `listPeers`, `listInvites` | viewer |
|
||||
| `invitePeer`, `revokePeer`, `unrevokePeer`, `setPeerRole` | admin |
|
||||
|
||||
### Terminals / logs streams
|
||||
|
||||
| Method | Min role |
|
||||
|--------|----------|
|
||||
| `startLogs`, `stopLogs`, `logs` | viewer |
|
||||
| `startTerminal`, `killTerminal`, `terminalInput`, `terminalResize`, `dockerTerminalResize` | operator |
|
||||
|
||||
### Binary streams
|
||||
|
||||
| Method | Min role |
|
||||
|--------|----------|
|
||||
| `binaryStreamOpen`, `binaryStreamChunk`, `binaryStreamClose` | operator |
|
||||
|
||||
### Suggestions / host snapshot
|
||||
|
||||
| Method | Min role |
|
||||
|--------|----------|
|
||||
| `getHostSnapshot`, `suggestNetworkIPAM`, `listUsedHostPorts`, `suggestResourceName`, `suggestFromImage`, `suggestDefaults` | viewer |
|
||||
| `validateCreateOptions` | operator |
|
||||
|
||||
### Swarm (`ENABLE_SWARM`, default on)
|
||||
|
||||
| Method | Min role |
|
||||
|--------|----------|
|
||||
| `swarmInspect`, `listServices`, `inspectService`, `listNodes`, `inspectNode`, `listTasks`, `inspectTask`, `listSecrets`, `inspectSecret`, `listConfigs`, `inspectConfig`, `serviceLogs` | viewer |
|
||||
| `createService`, `updateService`, `scaleService`, `swarmJoin` | operator |
|
||||
| `removeService`, `swarmInit`, `swarmLeave`, `swarmUpdate`, `updateNode`, `removeNode`, `createSecret`, `removeSecret`, `createConfig`, `removeConfig` | admin |
|
||||
|
||||
### Plugins (`ENABLE_PLUGINS`, default off)
|
||||
|
||||
| Method | Min role |
|
||||
|--------|----------|
|
||||
| `listPlugins`, `inspectPlugin` | viewer |
|
||||
| install/enable/disable/remove/configure | admin |
|
||||
|
||||
### Holesail tunnels (`ENABLE_HOLESAIL`, default on)
|
||||
|
||||
| Method | Min role |
|
||||
|--------|----------|
|
||||
| `listTunnels`, `getTunnel`, `getHolesailStatus` | viewer |
|
||||
| `createTunnel`, `closeTunnel` | operator |
|
||||
|
||||
---
|
||||
|
||||
## Server → client pushes
|
||||
|
||||
| Push channel | Purpose |
|
||||
|--------------|---------|
|
||||
| `push:containers` | Container list refresh |
|
||||
| `push:images` | Image list updates |
|
||||
| `push:volumes` | Volume list updates |
|
||||
| `push:allStats` | Fleet CPU/mem stats |
|
||||
| `push:logs` | Followed container logs |
|
||||
| `push:terminalOutput` / `push:terminalErrorOutput` | Shell I/O |
|
||||
| `push:execOutput` / `push:execErrorOutput` | Exec session I/O |
|
||||
| `push:dockerOutput` | CLI passthrough output |
|
||||
| `push:pullProgress` / `push:buildProgress` / `push:pushProgress` | Long image ops |
|
||||
| `push:dockerEvent` | Raw/engine events |
|
||||
| `push:binaryChunk` | Binary transfer |
|
||||
| `push:attachOutput` | Attach stream |
|
||||
| `push:metrics` | Optional metrics push |
|
||||
| `push:error` | Async error signaling |
|
||||
|
||||
Client `connection.js` maps pushes to EventEmitter events for the UI.
|
||||
|
||||
---
|
||||
|
||||
## Encoding
|
||||
|
||||
`shared/encodings.js` configures protomux-rpc value encoding (JSON-compatible structures over compact-encoding). Both peers must use the same encodings object when constructing ProtomuxRPC.
|
||||
|
||||
---
|
||||
|
||||
## Schema validation
|
||||
|
||||
`shared/schema.js` implements `validateMethodArgs(method, args)`:
|
||||
|
||||
- Returns `{ ok: true, args }` or `{ ok: false, error }`.
|
||||
- Not every method has a strict schema; handlers still validate critically.
|
||||
- Bump `SCHEMA_VERSION` when validation rules break compatibility.
|
||||
|
||||
---
|
||||
|
||||
## Capability diagram
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph Viewer
|
||||
L[Lists + inspect]
|
||||
LG[Logs follow]
|
||||
M[Metrics]
|
||||
T[List tunnels]
|
||||
end
|
||||
subgraph Operator
|
||||
S[Start/stop/deploy]
|
||||
P[Pull/push tag]
|
||||
SC[Scale service]
|
||||
CT[Create tunnel]
|
||||
TERM[Terminals]
|
||||
end
|
||||
subgraph Admin
|
||||
RM[Remove/prune]
|
||||
SW[Swarm init/leave]
|
||||
V[Vault store]
|
||||
INV[Invites/revoke]
|
||||
end
|
||||
Viewer --> Operator --> Admin
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Testing
|
||||
|
||||
Protocol and ACL coverage lives under `test/`:
|
||||
|
||||
- `protocol.coverage.test.js`
|
||||
- `acl.test.js`
|
||||
- `rpc.integration.test.js`
|
||||
- `session.integration.test.js`
|
||||
- `schema.test.js`
|
||||
|
||||
Run: `npm test`.
|
||||
+262
@@ -0,0 +1,262 @@
|
||||
# PearDock documentation
|
||||
|
||||
**Complete technical documentation for the peardock codebase (v2.x / protocol version 2).**
|
||||
|
||||
PearDock is **decentralized Docker management**: a HyperDHT control plane, structured protomux-rpc, a desktop client (Electron or Pear), and optional Holesail L4 tunnels. There is no SaaS broker. You share a **public key**; peers hole-punch and talk end-to-end encrypted.
|
||||
|
||||
| | |
|
||||
|--|--|
|
||||
| **License** | [AGPL-3.0](../LICENSE) |
|
||||
| **Package** | `peardock` **2.0.1** (`package.json`) |
|
||||
| **Protocol** | `peardock/rpc` · `PROTOCOL_VERSION = 2` (`shared/protocol.js`) |
|
||||
| **Site** | [peardock.boats](https://peardock.boats) |
|
||||
| **Install** | `curl -fsSL https://install.peardock.boats \| bash` |
|
||||
| **Source** | [git.ssh.surf/snxraven/peardock](https://git.ssh.surf/snxraven/peardock) |
|
||||
|
||||
---
|
||||
|
||||
## Documentation map
|
||||
|
||||
### Understanding the system
|
||||
|
||||
| Document | What you will learn |
|
||||
|----------|---------------------|
|
||||
| **[ARCHITECTURE.md](./ARCHITECTURE.md)** | End-to-end design, two planes, connection lifecycle, mermaid diagrams |
|
||||
| **[CODEBASE-MAP.md](./CODEBASE-MAP.md)** | Every top-level directory and major module, responsibilities, entry points |
|
||||
| **[PROTOCOL.md](./PROTOCOL.md)** | RPC methods, pushes, roles (`MethodRoles`), encodings, handshake |
|
||||
| **[SERVER.md](./SERVER.md)** | Server boot, PeerSession middleware, handlers, services, persistence |
|
||||
| **[CLIENT-UI.md](./CLIENT-UI.md)** | ConnectionManager, Electron vs Pear, UI views, jobs, local Holesail |
|
||||
| **[FEATURES.md](./FEATURES.md)** | Feature catalog by domain (containers, Swarm, tunnels, vault, …) |
|
||||
|
||||
### Operating and shipping
|
||||
|
||||
| Document | What you will learn |
|
||||
|----------|---------------------|
|
||||
| **[OPERATOR.md](./OPERATOR.md)** | Install, systemd, ACL, env flags, logging, backup |
|
||||
| **[HOLESAIL.md](./HOLESAIL.md)** | Data-plane tunnels in depth |
|
||||
| **[THREAT_MODEL.md](./THREAT_MODEL.md)** | Assets, adversaries, controls, hardening checklist |
|
||||
| **[RELEASE.md](./RELEASE.md)** | Host matrix, Bare + Electron builds, rolling CI |
|
||||
| **[SBOM.md](./SBOM.md)** | SBOM generation and license notes |
|
||||
|
||||
### Product planning
|
||||
|
||||
| Document | What you will learn |
|
||||
|----------|---------------------|
|
||||
| **[../ROADMAP.md](../ROADMAP.md)** | Feature tracks and status |
|
||||
| **[../README.md](../README.md)** | Project overview and quick start |
|
||||
|
||||
---
|
||||
|
||||
## One-picture overview
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph ClientHost["Operator machine"]
|
||||
UI["Desktop UI<br/>index.html + ui/ + libs/"]
|
||||
CM["ConnectionManager<br/>client/manager.js"]
|
||||
CONN["PearDockConnection<br/>client/connection.js"]
|
||||
HL["Holesail local client<br/>client/holesailLocal.js"]
|
||||
UI --> CM --> CONN
|
||||
UI --> HL
|
||||
end
|
||||
|
||||
subgraph Network["Holepunch network"]
|
||||
DHT["HyperDHT<br/>discovery + Noise stream"]
|
||||
end
|
||||
|
||||
subgraph ServerHost["Docker host"]
|
||||
SRV["server/server.js<br/>DHT createServer"]
|
||||
PS["PeerSession<br/>protomux-rpc"]
|
||||
H["handlers/*"]
|
||||
DK["dockerode → dockerd"]
|
||||
HS["HolesailServer<br/>services/holesail-tunnels.js"]
|
||||
SRV --> PS --> H --> DK
|
||||
H --> HS
|
||||
end
|
||||
|
||||
CONN -->|"dial public key"| DHT
|
||||
DHT --> SRV
|
||||
HL -.->|"hs:// data plane"| HS
|
||||
HS --> PORT["127.0.0.1:published-port"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Mental model in five minutes
|
||||
|
||||
### 1. Identity is a keypair, not a topic
|
||||
|
||||
The server derives a HyperDHT keypair from `SERVER_SEED` (persisted in `.env`). Clients dial the **64-character public key**. This replaced v1’s Hyperswarm “topic = seed” model.
|
||||
|
||||
### 2. Control plane is structured RPC
|
||||
|
||||
Once the Noise-encrypted stream is up, both sides speak **protomux-rpc** with protocol id `peardock/rpc`. The client calls methods (`listContainers`, `deployStack`, …). The server may **push** channels (`push:containers`, `push:allStats`, `push:logs`, …).
|
||||
|
||||
### 3. Docker stays on the server host
|
||||
|
||||
All Engine access goes through **dockerode** on the machine running peardock-server (typically the Unix socket). The client never holds the Docker socket.
|
||||
|
||||
### 4. Two planes
|
||||
|
||||
| Plane | Path | Purpose |
|
||||
|-------|------|---------|
|
||||
| **Control** | HyperDHT + protomux-rpc | Manage Docker, ACL, audit, live UI |
|
||||
| **Data** | Holesail `hs://` | Tunnel published TCP/UDP ports without firewall holes |
|
||||
|
||||
### 5. Capabilities are gated by role
|
||||
|
||||
Every method has a minimum role in `MethodRoles` (`viewer` < `operator` < `admin`). Unknown methods default to **admin**. Default peer role is **admin** for single-operator convenience; harden for fleets.
|
||||
|
||||
---
|
||||
|
||||
## Connection lifecycle
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant C as Desktop client
|
||||
participant DHT as HyperDHT
|
||||
participant S as peardock-server
|
||||
participant D as dockerd
|
||||
|
||||
S->>DHT: listen(keyPair)
|
||||
Note over S: Print SERVER_PUBLIC_KEY
|
||||
C->>DHT: connect(publicKey)
|
||||
DHT-->>C: secret stream (Noise)
|
||||
C->>S: protomux-rpc open
|
||||
C->>S: handshake { protocolVersion, inviteToken? }
|
||||
S-->>C: { peerId, role, protocolVersion, docker ok }
|
||||
C->>S: listContainers / deploy / …
|
||||
S->>D: dockerode API
|
||||
D-->>S: result
|
||||
S-->>C: RPC response
|
||||
S-->>C: push:containers / push:allStats / …
|
||||
```
|
||||
|
||||
**Client reconnect:** `ConnectionManager` retries every **5 seconds** (configurable) after unexpected drops until success or intentional disconnect. Last active peer is restored from disk cache (`~/.config/peardock` style paths via `peerCache.js`).
|
||||
|
||||
---
|
||||
|
||||
## Repository layout (summary)
|
||||
|
||||
```
|
||||
peardock/
|
||||
├── shared/ # Protocol + encodings + arg schema (both sides)
|
||||
├── server/ # HyperDHT control plane + dockerode
|
||||
│ ├── server.js # Process entry
|
||||
│ ├── core/ # Keys, ACL, audit, peer policy, vault crypto
|
||||
│ ├── rpc/ # PeerSession, handler registration, binary streams
|
||||
│ ├── handlers/ # One module per domain
|
||||
│ ├── services/ # Docker client, events, stats, tunnels, schedules
|
||||
│ └── utils/ # Logger, rate limit, compose, validation
|
||||
├── client/ # Connection stack used by the UI
|
||||
├── ui/ + libs/ # Desktop presentation + helpers
|
||||
├── electron/ # Packaged Electron main / preload / OTA
|
||||
├── index.js # Pear entry (pear-electron + holesail Bare control)
|
||||
├── index.html # App shell markup
|
||||
├── scripts/ # install.sh, make, rolling release, soak
|
||||
├── deploy/ # systemd unit
|
||||
├── test/ # brittle tests
|
||||
└── docs/ # You are here
|
||||
```
|
||||
|
||||
Full module-by-module guide: **[CODEBASE-MAP.md](./CODEBASE-MAP.md)**.
|
||||
|
||||
---
|
||||
|
||||
## Runtime topologies
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph Dev["Development"]
|
||||
N1["node server/server.js"]
|
||||
E1["npm run start:client<br/>Electron"]
|
||||
P1["npm run dev<br/>Pear"]
|
||||
end
|
||||
|
||||
subgraph Prod["Production package"]
|
||||
B["peardock-server<br/>Bare binary"]
|
||||
EC["peardock-client<br/>Electron app"]
|
||||
SYS["systemd peardock.service"]
|
||||
SYS --> B
|
||||
end
|
||||
|
||||
E1 --> N1
|
||||
P1 --> N1
|
||||
EC --> B
|
||||
```
|
||||
|
||||
| Mode | Server | Client |
|
||||
|------|--------|--------|
|
||||
| Dev | `npm run server` | `npm run start:client` or `npm run dev` |
|
||||
| Package | `/opt/peardock/peardock-server` | Rolling Electron tarball / `.app` |
|
||||
| Installer | `install.peardock.boats` → systemd + binary | same script `--client` |
|
||||
|
||||
---
|
||||
|
||||
## Feature flags at a glance
|
||||
|
||||
| Flag | Default | Surface |
|
||||
|------|---------|---------|
|
||||
| `ENABLE_HOLESAIL` | **on** | Tunnel RPC + Holesail servers |
|
||||
| `ENABLE_SWARM` | **on** | Swarm services/nodes/tasks/secrets/configs |
|
||||
| `ENABLE_PLUGINS` | **off** | Plugin install/enable/remove |
|
||||
| `PEARDOCK_DEFAULT_ROLE` | `admin` | Role for unknown peers |
|
||||
| `PEARDOCK_PEER_ALLOWLIST` | off | Require registered peers |
|
||||
| `PEARDOCK_AUDIT` | off | Append-only privileged audit log |
|
||||
|
||||
Details: [OPERATOR.md](./OPERATOR.md), [THREAT_MODEL.md](./THREAT_MODEL.md).
|
||||
|
||||
---
|
||||
|
||||
## Security snapshot
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
PK["Knows public key"] --> DIAL["Can dial DHT"]
|
||||
DIAL --> HS["handshake"]
|
||||
HS --> ROLE{"Role resolution"}
|
||||
ROLE -->|default| ADMIN["admin if unconfigured"]
|
||||
ROLE -->|hardened| VIEW["viewer / operator / admin keys"]
|
||||
VIEW --> MR["MethodRoles check"]
|
||||
ADMIN --> MR
|
||||
MR --> RL["Rate limiter"]
|
||||
RL --> SCH["schema validateMethodArgs"]
|
||||
SCH --> HND["Handler → dockerode"]
|
||||
HND --> AUD["Audit if privileged"]
|
||||
```
|
||||
|
||||
Hardening checklist and residual risks: **[THREAT_MODEL.md](./THREAT_MODEL.md)**.
|
||||
|
||||
---
|
||||
|
||||
## How to read this docs set
|
||||
|
||||
1. **New to PearDock?** Start with this page + [ARCHITECTURE.md](./ARCHITECTURE.md), then [OPERATOR.md](./OPERATOR.md).
|
||||
2. **Implementing a feature?** [CODEBASE-MAP.md](./CODEBASE-MAP.md) → domain handler under `server/handlers/` → method in `shared/protocol.js` → client helper in `client/api.js` → UI in `ui/` or `libs/`.
|
||||
3. **Debugging connectivity?** [PROTOCOL.md](./PROTOCOL.md) handshake + [CLIENT-UI.md](./CLIENT-UI.md) reconnect + server logs (`LOG_FORMAT=json`).
|
||||
4. **Tunnels?** [HOLESAIL.md](./HOLESAIL.md) + [FEATURES.md](./FEATURES.md#holesail-tunnels).
|
||||
5. **Shipping binaries?** [RELEASE.md](./RELEASE.md).
|
||||
|
||||
---
|
||||
|
||||
## Conventions used in these docs
|
||||
|
||||
- Paths are repo-relative unless noted.
|
||||
- “Server” means the HyperDHT control-plane process (`server/server.js` or Bare `peardock-server`).
|
||||
- “Client” means the desktop app stack (`client/` + UI), packaged as Electron or run under Pear.
|
||||
- Mermaid diagrams render on GitHub, Gitea, and many IDEs; if not, the surrounding text still stands alone.
|
||||
- Public website HTML under peardock-website is a **mirror of operator docs**, not the source of truth for code architecture. **This `docs/` tree is the code documentation.**
|
||||
|
||||
---
|
||||
|
||||
## Keeping docs honest
|
||||
|
||||
When you change behavior, update:
|
||||
|
||||
1. `shared/protocol.js` (`Methods`, `MethodRoles`, `Pushes`, `PROTOCOL_VERSION`)
|
||||
2. The matching handler + any client API wrapper
|
||||
3. The relevant file in `docs/` (especially PROTOCOL, FEATURES, SERVER, CLIENT-UI)
|
||||
4. Root `README.md` if install/flags/scripts changed
|
||||
5. Website legal/docs only if product claims change
|
||||
|
||||
Last aligned with codebase layout and protocol as of peardock **2.0.1** / `PROTOCOL_VERSION` **2**.
|
||||
+19
-8
@@ -6,12 +6,12 @@ No 32-bit (`ia32` / `armv7`) builds.
|
||||
|
||||
| Host | Server (Bare) | Client (Electron) |
|
||||
|------|---------------|-------------------|
|
||||
| `linux-x64` | ✅ cross from any OS | ✅ cross via electron-forge |
|
||||
| `linux-arm64` | ✅ | ✅ |
|
||||
| `darwin-x64` | ✅ | ✅ (unsigned from Linux CI) |
|
||||
| `darwin-arm64` | ✅ | ✅ (unsigned from Linux CI) |
|
||||
| `win32-x64` | ✅ | ✅ |
|
||||
| `win32-arm64` | ✅ | ✅ |
|
||||
| `linux-x64` | yes (cross) | yes (cross via electron-forge) |
|
||||
| `linux-arm64` | yes | yes |
|
||||
| `darwin-x64` | yes | yes (unsigned / ad-hoc from Linux CI) |
|
||||
| `darwin-arm64` | yes | yes (unsigned / ad-hoc from Linux CI) |
|
||||
| `win32-x64` | yes | yes |
|
||||
| `win32-arm64` | yes | yes |
|
||||
|
||||
Canonical list: `scripts/hosts.cjs` → `ALL_64`.
|
||||
|
||||
@@ -21,7 +21,7 @@ Canonical list: `scripts/hosts.cjs` → `ALL_64`.
|
||||
|
||||
| Artifact | Tooling |
|
||||
|----------|---------|
|
||||
| **peardock-server** | `scripts/bare-standalone.cjs` — `bare-pack` with **bare-node-runtime** `imports` + stubs, then embed **bare-runtime** prebuild (`bare-build` platform hooks) |
|
||||
| **peardock-server** | `scripts/bare-standalone.cjs`: `bare-pack` with bare-node-runtime imports + stubs, then embed bare-runtime prebuild |
|
||||
| **peardock-client** | `esbuild` → `electron/app.bundle.cjs`, then `electron-forge package --platform … --arch …` |
|
||||
|
||||
Server packing uses `package.json` `imports` so Node builtins resolve to `bare-*` under Bare. Optional natives (`ssh2`, `@grpc/*`) map to `build/stubs/*`.
|
||||
@@ -34,7 +34,7 @@ Server packing uses `package.json` `imports` so Node builtins resolve to `bare-*
|
||||
npm ci
|
||||
|
||||
# All 64-bit server + client hosts (same as CI)
|
||||
npm run make # or: node scripts/make.cjs all
|
||||
npm run make # or: node scripts/make.cjs both
|
||||
|
||||
# Server only / client only
|
||||
npm run make:server
|
||||
@@ -88,10 +88,17 @@ Dry-run locally:
|
||||
DRY_RUN=1 bash scripts/gitea-rolling-release.sh
|
||||
```
|
||||
|
||||
Published assets are consumed by:
|
||||
|
||||
- `https://install.peardock.boats` (`scripts/install.sh`)
|
||||
- [peardock.boats/releases](https://peardock.boats/releases) (Gitea API proxy)
|
||||
- Manual download from Gitea
|
||||
|
||||
### Notes
|
||||
|
||||
- **macOS clients** are **ad-hoc codesigned** after package (`scripts/sign-macos-app.cjs`) so Gatekeeper does not show “damaged / move to Trash”. CI installs `rcodesign` to ad-hoc sign darwin `.app`s built on Linux. For Developer ID + notarization, set `MAC_CODESIGN_IDENTITY` / `CSC_NAME` on a Mac (or import certs in CI).
|
||||
- Re-sign a local/downloaded build: `npm run sign:macos -- out/peardock-darwin-arm64/peardock.app`
|
||||
- If the app is still blocked: `xattr -cr ~/Applications/peardock.app` or right-click → Open.
|
||||
- **Native modules** must ship prebuilds for each target (Holepunch stack does). Rebuild-from-source is disabled for cross packages (`npm_config_build_from_source=false`).
|
||||
- **Server** cross-compile uses bare-runtime platform prebuilds (no Docker-in-Docker required to *build*; runtime still needs a Docker socket).
|
||||
|
||||
@@ -108,3 +115,7 @@ DRY_RUN=1 bash scripts/gitea-rolling-release.sh
|
||||
- `package.json` `imports` follow [bare-node-runtime](https://github.com/holepunchto/bare-node-runtime).
|
||||
- Entry `bin/peardock-server.mjs` loads `bare-node-runtime/global` only when `globalThis.Bare` is set.
|
||||
- References: `hello-pear-bare-worker`, `bare-build`, `bare-runtime`, `bare-pack --imports`.
|
||||
|
||||
## Version
|
||||
|
||||
Package version lives in root `package.json` (currently **2.0.1**). Rolling assets embed that version in filenames.
|
||||
|
||||
+11
-6
@@ -1,6 +1,6 @@
|
||||
# Software bill of materials notes
|
||||
|
||||
peardock is a Node.js application. For production releases:
|
||||
peardock is a Node.js / Bare application. License: **AGPL-3.0**. For production releases, generate an SBOM from the lockfile.
|
||||
|
||||
## Generate SBOM
|
||||
|
||||
@@ -18,7 +18,7 @@ See `package.json` / `package-lock.json`. Major surface:
|
||||
|
||||
| Package | Role |
|
||||
|---------|------|
|
||||
| hyperdht | P2P transport |
|
||||
| hyperdht | P2P transport (Noise / DHT) |
|
||||
| protomux / protomux-rpc | Multiplexed RPC |
|
||||
| compact-encoding / b4a | Codecs |
|
||||
| dockerode | Docker Engine API |
|
||||
@@ -26,13 +26,18 @@ See `package.json` / `package-lock.json`. Major surface:
|
||||
| dotenv | Config |
|
||||
| graceful-goodbye | Shutdown |
|
||||
| hypercore-crypto | Key material |
|
||||
| pear-electron / pear-bridge | Desktop shell |
|
||||
| holesail | Required P2P TCP/UDP tunnels (on by default; `ENABLE_HOLESAIL=0` to disable) — **AGPL-3.0** |
|
||||
| holesail | Required P2P TCP/UDP tunnels (on by default; `ENABLE_HOLESAIL=0` to disable). **AGPL-3.0** |
|
||||
| pear-electron / pear-bridge | Pear desktop shell |
|
||||
| which-runtime | Bare vs Node branching |
|
||||
|
||||
Electron packaging (client) pulls additional deps via electron-forge / esbuild at build time; those are not all listed as runtime `dependencies` for the server.
|
||||
|
||||
## Known-sensitive native deps
|
||||
|
||||
- `sodium-native` / `udx-native` (via hyperdht tree) — audit on upgrades
|
||||
- `sodium-native` / `udx-native` (via hyperdht tree): audit on upgrades
|
||||
|
||||
## License
|
||||
|
||||
**AGPL-3.0** (project, same as Holesail). Holesail integration is also **AGPL-3.0** (`holesail` / `holesail-server` / `holesail-client`). Review transitive licenses before redistribution; network operators of modified builds should document AGPL source-offer obligations.
|
||||
**AGPL-3.0** (project, same family as Holesail). Holesail integration is also **AGPL-3.0** (`holesail` / related packages). Review transitive licenses before redistribution. Network operators of **modified** builds should document AGPL source-offer obligations (AGPL §13).
|
||||
|
||||
Public notices: [peardock.boats/legal/license](https://peardock.boats/legal/license).
|
||||
|
||||
+222
@@ -0,0 +1,222 @@
|
||||
# Server deep dive
|
||||
|
||||
Implementation guide for the PearDock **control-plane** process.
|
||||
|
||||
Primary entry: [`server/server.js`](../server/server.js).
|
||||
|
||||
---
|
||||
|
||||
## Responsibilities
|
||||
|
||||
1. Listen on a HyperDHT keypair identity.
|
||||
2. Accept Noise-encrypted streams from clients.
|
||||
3. Expose Docker (and related) operations via protomux-rpc.
|
||||
4. Push live updates (lists, stats, logs, events).
|
||||
5. Optionally run Holesail servers for published ports.
|
||||
6. Enforce ACL, rate limits, audit, and peer policy.
|
||||
|
||||
---
|
||||
|
||||
## Process model
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
MAIN[server/server.js]
|
||||
MAIN --> KEYS[core/keys.js]
|
||||
MAIN --> DHT[hyperdht createServer]
|
||||
DHT -->|connection| PS[rpc/session PeerSession]
|
||||
PS --> REG[rpc/registerAllHandlers]
|
||||
REG --> H[handlers/*]
|
||||
H --> DOCK[services/docker.js]
|
||||
H --> TUN[services/holesail-tunnels.js]
|
||||
H --> POL[core/peer-policy vault audit]
|
||||
MAIN --> EV[services/events.js]
|
||||
MAIN --> ST[services/stats.js]
|
||||
EV -->|push| PS
|
||||
ST -->|push| PS
|
||||
```
|
||||
|
||||
Single Node (or Bare) process. No worker multi-process cluster required for v2.
|
||||
|
||||
---
|
||||
|
||||
## Keypair and `.env`
|
||||
|
||||
`server/core/keys.js`:
|
||||
|
||||
- Reads `SERVER_SEED` / `SERVER_KEY` from environment or `.env`.
|
||||
- Generates a random 32-byte seed on first run and persists it.
|
||||
- Derives HyperDHT keypair; exposes `SERVER_PUBLIC_KEY` hex.
|
||||
|
||||
**Rotating the seed changes the public key.** All clients must re-add the peer. Vault ciphertext is keyed from seed material; re-store credentials after rotation.
|
||||
|
||||
---
|
||||
|
||||
## PeerSession
|
||||
|
||||
File: `server/rpc/session.js`.
|
||||
|
||||
| Property | Meaning |
|
||||
|----------|---------|
|
||||
| `id` | Remote public key hex (peer id) |
|
||||
| `role` | Resolved ACL role |
|
||||
| `rpc` | ProtomuxRPC instance |
|
||||
| `state` | Map for terminals, exec streams, etc. |
|
||||
|
||||
### `respond(method, handler, opts?)`
|
||||
|
||||
Wraps every method with middleware (see [PROTOCOL.md](./PROTOCOL.md#middleware-server)).
|
||||
|
||||
### Pushes
|
||||
|
||||
Handlers/services call into the session to `rpc.event` / push helpers so all connected peers (or the owning peer) receive `push:*` channels.
|
||||
|
||||
---
|
||||
|
||||
## Handler registration
|
||||
|
||||
`server/rpc/register.js` installs:
|
||||
|
||||
```
|
||||
handshake, system, containers, images, networks, volumes,
|
||||
stacks, deploy, terminal, logs, docker-cli, swarm, plugins,
|
||||
peers, vault, binary-stream, suggestions, tunnels
|
||||
```
|
||||
|
||||
On session close: kill terminals, stop log follows, end exec streams, clear `session.state`.
|
||||
|
||||
---
|
||||
|
||||
## Docker access
|
||||
|
||||
`server/services/docker.js` exports a shared **dockerode** client (default socket).
|
||||
|
||||
Operational requirement: process user must access the socket (installer uses user `peardock` + group `docker`).
|
||||
|
||||
---
|
||||
|
||||
## Background services
|
||||
|
||||
### Docker events (`services/events.js`)
|
||||
|
||||
Subscribes to Engine events; fans out `push:dockerEvent` and may trigger list refreshes.
|
||||
|
||||
### Stats (`services/stats.js` + `stats-history.js`)
|
||||
|
||||
Periodic container stats → `push:allStats`; history buffer for charts.
|
||||
|
||||
### Metrics (`services/metrics.js`)
|
||||
|
||||
Process memory, RPC counters, latency percentiles, feature flags. Exposed via `getMetrics`.
|
||||
|
||||
### Schedules (`services/schedules.js`)
|
||||
|
||||
Persisted jobs (prune, etc.) restored at boot; admin upsert/delete via RPC.
|
||||
|
||||
### Holesail tunnels (`services/holesail-tunnels.js`)
|
||||
|
||||
- Feature flag `ENABLE_HOLESAIL`
|
||||
- Create/list/close servers
|
||||
- SSRF allowlist for targets
|
||||
- Max concurrent tunnels
|
||||
- Disk restore of `peardock-tunnels.json`
|
||||
|
||||
Handlers in `handlers/tunnels.js` are thin RPC adapters over this service.
|
||||
|
||||
---
|
||||
|
||||
## Core security modules
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
CONN[New connection] --> REV{Revoked?}
|
||||
REV -->|yes| DROP[destroy socket]
|
||||
REV -->|no| SESS[PeerSession]
|
||||
SESS --> ROLE[acl.resolveRole]
|
||||
ROLE --> MR[MethodRoles]
|
||||
MR --> AUD[audit]
|
||||
ROLE --> POL[peer-policy invites]
|
||||
```
|
||||
|
||||
| Module | Behavior |
|
||||
|--------|----------|
|
||||
| `acl.js` | Role resolution + assert |
|
||||
| `peer-policy.js` | Invites, allowlist, revoke lists on disk |
|
||||
| `audit.js` | Append-only log for sensitive methods |
|
||||
| `registry-vault.js` | Encrypt registry passwords at rest |
|
||||
|
||||
---
|
||||
|
||||
## Domain handlers (quick reference)
|
||||
|
||||
| Handler | Typical Engine surface |
|
||||
|---------|------------------------|
|
||||
| containers | ContainerLifecycle + exec/attach |
|
||||
| images | Image pull/build/load/save |
|
||||
| networks | Network API |
|
||||
| volumes | Volume API + browse |
|
||||
| stacks | Compose project ops + gitops |
|
||||
| deploy | Opinionated container deploy |
|
||||
| terminal | Multiplexed interactive shells |
|
||||
| logs | Log follow |
|
||||
| docker-cli | Allow-listed CLI |
|
||||
| system | info/df/prune/schedules/browse |
|
||||
| swarm | Swarmkit APIs |
|
||||
| plugins | Plugin APIs |
|
||||
| peers | ACL administration |
|
||||
| vault | Credential CRUD + use |
|
||||
| tunnels | Holesail |
|
||||
| suggestions | Deploy UX helpers |
|
||||
|
||||
---
|
||||
|
||||
## Rate limiting
|
||||
|
||||
`server/utils/rateLimiter.js`:
|
||||
|
||||
- Per-peer token/window limits.
|
||||
- Stream/hot methods classified separately to avoid blocking terminal I/O.
|
||||
|
||||
---
|
||||
|
||||
## Logging
|
||||
|
||||
`server/utils/logger.js`:
|
||||
|
||||
| Env | Effect |
|
||||
|-----|--------|
|
||||
| `LOG_LEVEL` | error/warn/info/debug |
|
||||
| `LOG_FORMAT` | pretty / json |
|
||||
| `ENABLE_FILE_LOGGING` | File rotation under `LOG_DIR` |
|
||||
|
||||
Boot uses `logger.banner()` for the human-readable public key block.
|
||||
|
||||
---
|
||||
|
||||
## Shutdown
|
||||
|
||||
On signal / graceful-goodbye:
|
||||
|
||||
1. Stop stats + event streams
|
||||
2. Close all Holesail tunnels
|
||||
3. Destroy peer sessions / DHT
|
||||
4. Exit
|
||||
|
||||
---
|
||||
|
||||
## Bare packaging notes
|
||||
|
||||
Packaged server uses `package.json` `imports` so Node builtins resolve to `bare-*` under Bare. Stubs in `build/stubs` avoid optional native deps. See [RELEASE.md](./RELEASE.md).
|
||||
|
||||
---
|
||||
|
||||
## Local development
|
||||
|
||||
```bash
|
||||
npm run server
|
||||
# Public key printed once
|
||||
# Docker must be reachable
|
||||
```
|
||||
|
||||
Health: `npm run healthcheck`
|
||||
Soak: `npm run soak`
|
||||
+25
-19
@@ -1,7 +1,7 @@
|
||||
# peardock threat model
|
||||
|
||||
**Audience:** operators deploying peardock in multi-operator or semi-trusted environments.
|
||||
**Scope:** HyperDHT P2P control plane + local Docker Engine socket.
|
||||
**Scope:** HyperDHT P2P control plane + local Docker Engine socket (+ optional Holesail data plane).
|
||||
|
||||
---
|
||||
|
||||
@@ -9,41 +9,45 @@
|
||||
|
||||
| Asset | Sensitivity |
|
||||
|-------|-------------|
|
||||
| `SERVER_SEED` | Critical — identity + vault key derivation |
|
||||
| Docker socket access | Critical — full host container control |
|
||||
| Registry passwords (vault) | High — encrypted at rest |
|
||||
| Peer invite tokens | Medium — short-lived capability grants |
|
||||
| Audit log | Medium — forensic integrity |
|
||||
| Container data / env secrets | High — via inspect, logs, exec, archive |
|
||||
| `SERVER_SEED` | Critical: identity + vault key derivation |
|
||||
| Docker socket access | Critical: full host container control |
|
||||
| Registry passwords (vault) | High: encrypted at rest |
|
||||
| Peer invite tokens | Medium: short-lived capability grants |
|
||||
| Audit log | Medium: forensic integrity |
|
||||
| Container data / env secrets | High: via inspect, logs, exec, archive |
|
||||
| Holesail `hs://` URLs | High: capability to a published port |
|
||||
|
||||
---
|
||||
|
||||
## 2. Trust boundaries
|
||||
|
||||
```
|
||||
[Pear client] --Noise/HyperDHT--> [peardock server] --unix socket--> [dockerd]
|
||||
[Desktop client] --Noise/HyperDHT--> [peardock server] --unix socket--> [dockerd]
|
||||
|
|
||||
+-- peardock-vault.json (AES-GCM)
|
||||
+-- peardock-peers.json
|
||||
+-- peardock-audit.log
|
||||
+-- peardock-tunnels.json (optional)
|
||||
```
|
||||
|
||||
- **Anyone with the server public key** can *attempt* a DHT connection.
|
||||
- **Anyone with the server public key** can attempt a DHT connection.
|
||||
- **Default role is admin** unless `PEARDOCK_DEFAULT_ROLE` / `PEARDOCK_ADMIN_KEYS` / peer policy tighten it.
|
||||
- **Plugins** are off unless `ENABLE_PLUGINS=1`.
|
||||
- **Swarm** APIs are on by default (`ENABLE_SWARM=0` to disable).
|
||||
- **Holesail tunnels** are on by default (`ENABLE_HOLESAIL=0` to disable) — each `hs://` URL is a capability to the target port.
|
||||
- **Holesail tunnels** are on by default (`ENABLE_HOLESAIL=0` to disable). Each `hs://` URL is a capability to the target port.
|
||||
- **Host FS browse** is **default-deny** unless `PEARDOCK_BROWSE_ROOTS` or `PEARDOCK_BROWSE_OPEN=1`.
|
||||
- PearDock does **not** require open inbound firewall ports for the control path; HyperDHT hole-punches. That does not remove the need to secure keys and the Docker socket.
|
||||
|
||||
---
|
||||
|
||||
## 3. Adversaries
|
||||
|
||||
1. **Remote internet peer** with public key only — should not get Docker control if allowlist + non-admin default are set.
|
||||
2. **Stolen invite token** — limited by TTL / max uses; should be rotated after use.
|
||||
3. **Compromised client machine** — can use any role the peer holds until revoke.
|
||||
4. **Local host attacker with filesystem** — can steal `SERVER_SEED` and vault if file perms wrong.
|
||||
5. **Malicious container** — not in scope for peardock; Engine isolation applies.
|
||||
1. **Remote peer with public key only:** should not get Docker control if allowlist + non-admin default are set.
|
||||
2. **Stolen invite token:** limited by TTL / max uses; rotate after use.
|
||||
3. **Compromised client machine:** can use any role the peer holds until revoke.
|
||||
4. **Local host attacker with filesystem:** can steal `SERVER_SEED` and vault if file perms are wrong.
|
||||
5. **Holder of an `hs://` URL:** can reach the tunneled service until the tunnel is closed / key rotated.
|
||||
6. **Malicious container:** not in scope for peardock; Engine isolation applies.
|
||||
|
||||
---
|
||||
|
||||
@@ -56,7 +60,7 @@
|
||||
| Peer policy | Invite, register, revoke, optional allowlist |
|
||||
| Audit | Append-only log for privileged methods |
|
||||
| Rate limit | Per-peer limiter on RPC |
|
||||
| Registry secrets | AES-256-GCM vault (`registry-vault.js`) |
|
||||
| Registry secrets | AES-256-GCM vault |
|
||||
| Browse FS | Root allowlist / default deny |
|
||||
| Feature gates | `ENABLE_SWARM` / `ENABLE_HOLESAIL` (default on; set `0` to disable), `ENABLE_PLUGINS`, `PEARDOCK_UNRESTRICTED_CLI` |
|
||||
| Tunnel targets | Loopback / allowlisted hosts only (`PEARDOCK_TUNNEL_HOSTS`) |
|
||||
@@ -65,7 +69,7 @@
|
||||
|
||||
## 5. Residual risks
|
||||
|
||||
- **Default admin** is intentional for single-operator setup — **must** change for multi-tenant.
|
||||
- **Default admin** is intentional for single-operator setup. Change it for multi-tenant fleets.
|
||||
- Binary image/export streams are size-capped but still large; DoS via memory if many concurrent transfers.
|
||||
- JSON-over-RPC is not hyperschema-validated end-to-end; malformed args rely on handler validation.
|
||||
- Swarm secrets/configs once enabled are highly privileged.
|
||||
@@ -80,9 +84,10 @@
|
||||
- [ ] Enable `PEARDOCK_PEER_ALLOWLIST=1` after registering operators
|
||||
- [ ] Set `PEARDOCK_BROWSE_ROOTS` only if host path pickers are needed
|
||||
- [ ] Set `ENABLE_SWARM=0` if Swarm APIs are not needed; leave `ENABLE_PLUGINS` off unless required
|
||||
- [ ] File mode `600` on vault, peer policy, audit, `.env`
|
||||
- [ ] Treat `hs://` tunnel URLs as secrets; set `ENABLE_HOLESAIL=0` if unused
|
||||
- [ ] File mode `600` on vault, peer policy, tunnels, audit, `.env`
|
||||
- [ ] Run server as non-root in `docker` group (see `deploy/peardock.service`)
|
||||
- [ ] Review `peardock-audit.log` periodically
|
||||
- [ ] Enable `PEARDOCK_AUDIT=1` and review `peardock-audit.log` periodically
|
||||
- [ ] Revoke lost client keys immediately (`revokePeer` / Access UI)
|
||||
|
||||
---
|
||||
@@ -95,6 +100,7 @@
|
||||
4. Command injection on docker CLI allow-list bypass
|
||||
5. Role escalation via handshake args (must be ignored)
|
||||
6. Vault decryption with wrong seed / key rotation story
|
||||
7. Tunnel target SSRF / capability leakage via `hs://` sharing
|
||||
|
||||
---
|
||||
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "peardock",
|
||||
"version": "2.0.1",
|
||||
"description": "Decentralized Docker management over HyperDHT + protomux-rpc",
|
||||
"description": "Decentralized Docker management over HyperDHT + protomux-rpc (AGPL-3.0)",
|
||||
"type": "module",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "index.js",
|
||||
|
||||
@@ -88,5 +88,5 @@ If any size ever looks soft, regenerate from `logo/peardock-icon-master.png` or
|
||||
|
||||
---
|
||||
|
||||
© 2026 snxraven — AGPL-3.0
|
||||
© 2026 snxraven · AGPL-3.0
|
||||
Repo: https://git.ssh.surf/snxraven/peardock
|
||||
|
||||
Reference in New Issue
Block a user