Docs
Release rolling / release (push) Successful in 8m7s

This commit is contained in:
Raven Scott
2026-07-11 20:11:02 -04:00
parent 4d6777e765
commit 9511214382
16 changed files with 2191 additions and 210 deletions
Vendored
BIN
View File
Binary file not shown.
+166 -99
View File
@@ -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]).
+362
View File
@@ -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 connections 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)
+189
View File
@@ -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`).
+346
View File
@@ -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 |
+161
View File
@@ -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
View File
@@ -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 peardocks `SERVER_SEED` for tunnel keypairs — each tunnel gets its own Holesail seed.
- Do **not** reuse PearDocks `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
View File
@@ -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 sockets 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 clients peer id (handshake returns `peerId`) or the key shown after connect.
2. `.env` example:
1. On first connect, note the clients 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: **AccessCreate 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.
+285
View File
@@ -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
View File
@@ -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 v1s 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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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",
+1 -1
View File
@@ -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