CE — Deployment Guide
Adding your laptop and desktop to the mesh
This is the two-step process to join your own machines to your CE network.
Step 1: Install CE on each device
macOS / Linux:
curl -sSL https://raw.githubusercontent.com/ce-net/ce/main/install.sh | bashWindows (PowerShell):
irm https://raw.githubusercontent.com/ce-net/ce/main/install.ps1 | iexHomebrew (macOS / Linux):
brew install ce-net/ce/ceStep 2: Start CE on each device
On each machine, run:
ce startThe node auto-joins the public mesh via ce-net.com bootstrap. If your devices are on the same LAN, mDNS will find them automatically — no manual peer config needed.
To verify it is running:
ce status
# prints: node ID, chain height, balanceStep 3: Authorize a peer with a capability
CE's only trust primitive is the capability — a signed, attenuating grant from a node (or a configured root key) to a principal. There is no device allowlist. See docs/capabilities.md.
On the device you want to control remotely, get its node ID:
ce id
# output: 7a3f9b2c... (64 hex chars)On the machine being controlled (the resource owner), issue a capability to the controller:
# desktop authorizes the laptop to exec/sync/tunnel on it, for 90 days
ce grant <laptop-node-id> --can exec,sync,tunnel --expires 90d
# → prints a capability tokenOn the controller, store it under an alias (the capability wallet):
ce wallet add desktop 7a3f9b2c... --cap <token>
ce wallet lsNo IP:port anywhere, on the LAN or behind NAT: device-to-device traffic (exec/sync/deploy/ tunnel) routes through the mesh over libp2p, and the relay handles NAT traversal. Each command talks to your local node (--api-port, default 8844), which signs and forwards to the target; the wallet supplies the capability. Revoke with ce revoke <nonce> (on-chain) or let it expire.
Step 4: Use the authorized peer
# Forward a local port to the peer over the mesh (transport primitive)
ce tunnel desktop 2222:22 # then: ssh -p 2222 you@localhost
# Submit a compute job (any node with capacity picks it up)
ce deploy alpine:latest --cpu 2 --mem 512 --duration 60Remote exec and file sync/mirror are the rdev app (built on CE primitives), not node commands:
rdev exec desktop --image alpine:latest -- echo hello
rdev watch ./code desktop:code # continuous 1:1 folder mirrorSingle node (quick start)
cargo build --release
./target/release/ce startDefaults: P2P on :4001, API on :8844, data in ~/.local/share/ce/.
# Different ports
./target/release/ce start --port 5001 --api-port 9090
# Custom data directory
./target/release/ce --data-dir /data/ce-node startMulti-node (manual)
Node 1 (genesis):
./ce start --port 4001 --api-port 8844
# Note the node ID from the log: "node id: <64 hex>"Node 2 (peer):
# Get node 1's peer ID
N1_ID=$(ssh node1 ce id)
./ce start --port 4001 --api-port 8844 \
--bootstrap /ip4/<node1-ip>/tcp/4001/p2p/$N1_IDNode 2 will discover node 1 via Kademlia and receive a height announcement, triggering chain sync.
Automated Hetzner deployment
Use the shell scripts in deploy/ for quick cluster setup, or the Rust ce-deploy crate for programmatic E2E testing.
Shell scripts
# Set your environment
export HETZNER_API_TOKEN=hcloud-xxxxxxxxxx
export CE_SSH_KEY_NAME=my-key # key name in Hetzner project
export CE_SSH_KEY_PATH=~/.ssh/id_ed25519
# Build the binary
cargo build --release
# Start a 3-node cluster
./deploy/cluster.sh 3
# Run E2E test against the cluster
./deploy/e2e_test.sh
# Tear down (or it times out and tears down automatically)Hetzner E2E test suite
cargo build --release
export HETZNER_API_TOKEN=...
export CE_SSH_KEY_NAME=...
export CE_SSH_KEY_PATH=...
# Run all E2E tests (provisions and destroys servers automatically)
cargo test -p ce-deploy -- --ignored --nocapture
# Run a single E2E test
cargo test -p ce-deploy -- --ignored three_nodes_reach_consensus --nocaptureServer setup (manual install on Ubuntu 22.04)
# On the server
apt-get update && apt-get install -y libssl-dev
# Copy binary from your machine
scp target/release/ce root@<server-ip>:/usr/local/bin/ce
chmod +x /usr/local/bin/ce
# Start
ce start --port 4001 --api-port 8844 --bootstrap <addr>Systemd service
# /etc/systemd/system/ce.service
[Unit]
Description=CE Node
After=network.target
[Service]
ExecStart=/usr/local/bin/ce start --port 4001 --api-port 8844
Restart=always
RestartSec=5
Environment=RUST_LOG=ce=info
[Install]
WantedBy=multi-user.targetsystemctl enable --now ce
journalctl -u ce -fFirewall rules
| Port | Protocol | Direction | Purpose |
|---|---|---|---|
| 4001 | TCP | Inbound | libp2p P2P (Kademlia + Gossipsub) |
| 8844 | TCP | Inbound | HTTP API |
# ufw example
ufw allow 4001/tcp
ufw allow 8844/tcpMonitoring
# Check node status
curl http://localhost:8844/status | jq
# Watch chain height
watch -n5 'curl -s http://localhost:8844/status | jq .height'
# Logs (if using systemd)
journalctl -u ce -f
# Or if started manually
tail -f /var/log/ce.logData directory layout
~/.local/share/ce/
├── identity/
│ └── node.key # 32-byte Ed25519 secret key (chmod 600)
│ # BACK THIS UP — losing it means losing your node identity
└── chain/
└── chain.json # Full blockchain as JSON
# Size grows ~1KB per block
# At 6 blocks/minute: ~8MB/dayThe chain file is rewritten on every new block. For large chains, consider keeping only the last N blocks and a checkpoint. (Not yet implemented.)
Upgrading
CE has no migration system yet. To upgrade:
- Stop the node
- Replace the binary
- Restart
The chain file format is backward-compatible as long as new fields are added with #[serde(default)]. The identity key format (raw 32-byte Ed25519) will never change.