beamdb 0.17.0

BEAM — distributed graph database syncing over WebSocket, WebRTC, and multicast. Successor to rod.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
# Deploying BEAM

> *Production deployment guide. Every command, port, and configuration option below is verified against the actual source code.*

---

## Building

```bash
# Debug build (faster compile, slower runtime)
cargo build

# Release build (optimized, production-ready)
cargo build --release

# With WebRTC support
cargo build --release --features webrtc

# Binary locations after build:
#   target/release/beam                — main server binary
#   target/release/beam-sea-keygen   — session key generator utility
```

### Cross-Compilation

BEAM uses `ring` (for ECDSA), which requires a C compiler for the target platform. For cross-compilation, ensure you have the appropriate target toolchain installed.

---

## Running a Relay Node

A relay node accepts WebSocket connections from peers and synchronizes data between them.

### Basic Relay

```bash
# Start with default config: redb storage, WebSocket on port 4944
./target/release/beam -- start --port 4944
```

This starts:
- **WebSocket server** on port 4944 (accepts peer connections at `ws://your-host:4944/ws`)
- **Web UI** on port 4945 (serves `/peer_id`)
- **redb persistent storage** at `./beam.redb`

### With WebRTC (Direct P2P)

WebRTC support is a compile-time feature. Build with `--features webrtc`, then run normally:

```bash
# Build with WebRTC support
cargo build --release --features webrtc

# Run (same as basic relay, but now supports WebRTC peer connections)
./target/release/beam -- start --port 4944
```

This adds:
- WebRTC peer connection capability via `str0m`
- STUN discovery using Google's public STUN server (`stun:stun.l.google.com:19302`)
- TURN relay allocation support

### With TLS

```bash
# Enable WSS (WebSocket Secure) and HTTPS web UI
./target/release/beam -- start --port 4944 \
  --cert-path /etc/beam/cert.pem \
  --key-path /etc/beam/key.pem
```

TLS is handled natively by the `WsServer` adapter via `tokio-native-tls`. The certificate must be in PEM/PKCS8 format.

### With Peers (Mesh Joining)

```bash
# Connect to existing relay peers on startup
./target/release/beam -- start --port 4944 \
  --peers wss://relay1.example.com:8443/ws,wss://relay2.example.com:8443/ws
```

The `OutgoingWebsocketManager` connects to each peer URL and maintains the connection with retry. All peers in the `--peers` list become relay peers (`subscribe_to_everything = true`), receiving all messages for forwarding.

### With Multicast (LAN Discovery)

```bash
# Enable UDP multicast for local network peer discovery
./target/release/beam -- start --port 4944 --multicast true
```

Uses multicast group `224.0.0.123:6969`. Peers on the same LAN automatically discover and sync with each other. **Disable on public-facing servers** — multicast is for trusted local networks only.

### In-Memory Only (No Persistence)

```bash
# Use ephemeral in-memory storage (data lost on restart)
./target/release/beam -- start --port 4944 \
  --memory-storage true \
  --redb-storage false
```

### Restricted Mode (Signed Data Only)

```bash
# Reject unsigned writes to public space
# Only user-signed (~{pub}) and content-addressed (#) data accepted
./target/release/beam -- start --port 4944 \
  --allow-public-space false
```

This matches Gun.js `opt.enforce` semantics. Useful for relay nodes that should only propagate authenticated data.

---

## All CLI Flags

| Flag | Env Var | Default | Description |
|------|---------|---------|-------------|
| `--port` | `PORT` | 4944 | WebSocket server port |
| `--ws-server` | `WS_SERVER` | true | Enable WebSocket server |
| `--cert-path` | `CERT_PATH` | — | TLS certificate file (PEM/PKCS8) |
| `--key-path` | `KEY_PATH` | — | TLS private key file |
| `--peers` | `PEERS` | — | Comma-separated WebSocket peer URLs |
| `--multicast` | `MULTICAST` | false | Enable UDP multicast LAN discovery |
| `--memory-storage` | `MEMORY_STORAGE` | false | Enable in-memory storage |
| `--redb-storage` | `REDB_STORAGE` | true | Enable redb persistent storage |
| `--redb-path` | `REDB_PATH` | `beam.redb` | Path to redb database file |
| `--allow-public-space` | `ALLOW_PUBLIC_SPACE` | true | Accept unsigned writes to public nodes |

All flags can be set via environment variables (uppercase, underscore-separated). CLI flags take precedence over env vars.

---

## Ports

| Port | Service | Protocol |
|------|---------|----------|
| 4944 (configurable) | WebSocket server (peer connections) | WS/WSS |
| 4945 (port + 1) | Web UI (peer ID) | HTTP/HTTPS |
| 6969 | Multicast discovery (fixed) | UDP multicast (224.0.0.123) |

---

## Systemd Service

```ini
# /etc/systemd/system/beam.service
[Unit]
Description=BEAM P2P Graph Database Relay
After=network.target

[Service]
Type=simple
User=beam
Group=beam
ExecStart=/usr/local/bin/beam -- start \
  --port 4944 \
  --redb-path /var/lib/beam/data.redb \
  --peers wss://relay1.example.com:8443/ws
WorkingDirectory=/var/lib/beam
Restart=on-failure
RestartSec=5
LimitNOFILE=65536

# Environment variables (alternative to CLI flags)
Environment="RUST_LOG=info"
Environment="ALLOW_PUBLIC_SPACE=false"

[Install]
WantedBy=multi-user.target
```

### Setup

```bash
# Create user and data directory
sudo useradd -r -s /usr/sbin/nologin -d /var/lib/beam beam
sudo mkdir -p /var/lib/beam
sudo chown beam:beam /var/lib/beam

# Install binary
sudo cp target/release/beam /usr/local/bin/beam
sudo cp target/release/beam-sea-keygen /usr/local/bin/

# Install service
sudo cp beam.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable beam
sudo systemctl start beam

# Check status
sudo systemctl status beam
journalctl -u beam -f
```

---

## Docker

The repository includes a [Dockerfile](Dockerfile) using a multi-stage build with a distroless final image.

### docker-compose.yml

```yaml
version: "3.8"
services:
  beam:
    build: .
    ports:
      - "4944:4944"
      - "4945:4945"
    volumes:
      - beam-data:/data
    environment:
      - RUST_LOG=info
      - ALLOW_PUBLIC_SPACE=false
      - PEERS=wss://relay.example.com:8443/ws
      - REDB_PATH=/data/beam.redb
    restart: unless-stopped

volumes:
  beam-data:
```

### Running

```bash
# Build and start
docker-compose up -d

# View logs
docker-compose logs -f
```

---

## Security Considerations

### Network Security

| Concern | Recommendation |
|---------|---------------|
| **WebSocket traffic** | Plaintext by default. Use `--cert-path` / `--key-path` for WSS, or put behind a TLS-terminating reverse proxy (nginx, Caddy, Traefik). |
| **Multicast** | Local network only. Disable on public-facing servers (`--multicast false`). |
| **WebRTC** | DTLS encryption is built-in (via `str0m`). Data channels are always encrypted. |
| **Public space** | Default allows anyone to write to public nodes. Set `--allow-public-space false` for relay nodes that should only propagate signed data. |

### SEA Key Management

```bash
# Generate a session encryption key for EncryptedFileSessionStorage
export BEAM_SEA_SESSION_KEY=$(beam-sea-keygen)

# Store in secrets manager (examples)
# systemd credentials:
echo "$BEAM_SEA_SESSION_KEY" | sudo systemd-creds encrypt - beam-session.key

# Docker secrets:
echo "$BEAM_SEA_SESSION_KEY" | docker secret create beam_session_key -
```

**Session files** contain encrypted private keys. Protect the session directory (`~/.config/beam/sessions/`) with filesystem permissions:

```bash
chmod 700 ~/.config/beam/sessions/
```

### Storage Security

| Storage | Security |
|---------|----------|
| `MemoryStorage` | Ephemeral — data in RAM only, lost on restart. No at-rest encryption needed. |
| `RedbStorage` | Persistent to disk. No built-in encryption at rest. Use OS-level disk encryption (LUKS, ZFS encryption) for sensitive data. |

### Certificate Management

SEA certificates enable delegated trust — an authority can issue time-limited certificates authorizing specific public keys to write to specific paths.

```rust
// Issue a certificate
let cert = beam::sea::certify(
    &["alice_pub_key", "bob_pub_key"],     // authorized certificants
    Some(&json!({"e": 9999999999999.0,     // expiry timestamp
                  "r": ".*",                // read regex
                  "w": "skills/"})),         // write path prefix
    &authority_pair,
).await?;

// Verify a certificate
let payload = beam::sea::verify_certificate(&cert, &authority_pub_key)?;
let is_authorized = beam::sea::is_certified(&payload, &alice_pub_key);
```

---

## Health Checks

```bash
# Check if WebSocket server is accepting connections
curl -s http://localhost:4945/peer_id
# Returns the node's peer ID (16-char random string)

# Check if WebSocket is up
websocat ws://localhost:4944/ws

# Check systemd service
systemctl status beam

# Check logs
journalctl -u beam -f --since "1 hour ago"
```

---

## Logging

BEAM uses `env_logger` (initialized via `env_logger::init()` in `main.rs`). Control verbosity with `RUST_LOG`:

```bash
# Error only
RUST_LOG=error ./beam -- start

# Info (default)
RUST_LOG=info ./beam -- start

# Debug (verbose — includes message routing, dedup decisions, peer management)
RUST_LOG=debug ./beam -- start

# Trace (everything — includes every message received)
RUST_LOG=trace ./beam -- start

# Filter by module
RUST_LOG=beam::router=debug,beam::node=info ./beam -- start
```

---

## Scaling

### Multi-Relay Topology

Multiple relay nodes can be chained. Each peer connects to one relay, and relays forward to each other:

```
Peer A ──→ Relay 1 ←──→ Relay 2 ←──→ Peer B
                       Peer C
```

- The `Dup` table (999 entries, 9s TTL) prevents message loops in multi-relay topologies
- The `peer_hop_list` (`><` field in wire format) tracks which peers have already seen a message
- Relays with `subscribe_to_everything = true` receive all messages for forwarding

### Performance Considerations

| Component | Tuning |
|-----------|--------|
| `broadcast_buffer_size` | Default 4096. Increase for high-throughput scenarios with many subscribers. Decrease to save memory. |
| `redb` cache | The `redb` database uses an in-memory B-tree page cache. Ensure adequate RAM for your data set. |
| `Dup` table | 999 entries / 9s TTL. For high-throughput networks, consider increasing (requires code change in `router.rs`). |
| WebSocket connections | Each connection spawns a `WsConn` actor with its own Tokio task. Use `LimitNOFILE=65536` in systemd for high connection counts. |
| WebRTC | Direct P2P connections reduce relay bandwidth. Enable with `--features webrtc` for peer-to-peer data transfer. |

### Storage Sizing

- `MemoryStorage`: bounded only by available RAM
- `RedbStorage`: the `beam.redb` file grows with data. Monitor disk usage. The file does not auto-shrink on deletes — run `redb` compaction periodically (requires a maintenance window).

---

## Monitoring

### Prometheus / Grafana

BEAM does not have built-in Prometheus metrics. The `msg_counter` atomic in `Router` tracks total messages processed but is not yet exposed. A future implementation could expose this via the web UI server.

For now, use log-based monitoring:
```bash
# Count messages per second
journalctl -u beam -f --since "1 min ago" | grep "incoming message" | wc -l
```

---

## Backup and Recovery

### Redb Storage Backup

```bash
# Stop the node gracefully (flushes pending writes)
systemctl stop beam

# Copy the database file
cp /var/lib/beam/data.redb /backup/beam-$(date +%Y%m%d).redb

# Restart
systemctl start beam
```

### Session Key Backup

The `BEAM_SEA_SESSION_KEY` environment variable encrypts session files. **If this key is lost, all encrypted session files become unrecoverable.** Store it in a secrets manager (Vault, AWS Secrets Manager, systemd credentials).

---

## Troubleshooting

### "Failed to bind" on startup

Port is already in use. Check `lsof -i :4944` and kill the conflicting process, or use `--port` to choose a different port.

### "TableDoesNotExist" on first read

This was a known bug, fixed in commit `979139b` (redb schema warm at startup). If you encounter this, ensure you're running a build from after that commit.

### WebSocket connection refused

- Check firewall rules: ports 4944 (WS) and 4945 (web UI) must be open
- Check `--ws-server true` is not set to `false`
- Check TLS configuration: if `--cert-path` is set, `--key-path` must also be set

### Peers not syncing

1. Check `RUST_LOG=debug` for routing decisions
2. Verify `--peers` URLs are reachable: `websocat wss://peer-url/ws`
3. Check that both peers have the same `allow_public_space` setting — mismatched settings can cause data rejection
4. Verify the `Dup` table isn't too aggressive — messages expire after 9s by default

### WebRTC connection failures

1. Ensure `--features webrtc` was used during build
2. Check STUN server reachability: `stun:stun.l.google.com:19302` must be accessible
3. Check firewall allows UDP — WebRTC uses UDP for data channels
4. Check `RUST_LOG=debug` for ICE candidate exchange and DTLS handshake logs