11 releases (breaking)
Uses new Rust 2024
| 0.11.1 | Apr 2, 2026 |
|---|---|
| 0.11.0 | Mar 30, 2026 |
| 0.10.0 | Mar 27, 2026 |
#783 in WebSocket
1.5MB
28K
SLoC
Subduction CLI
[!CAUTION] This is an early release preview. It has a very unstable API. No guarantees are given. DO NOT use for production use cases at this time. USE AT YOUR OWN RISK.
Overview
The Subduction CLI runs a document sync server with three transport layers:
- WebSocket — browser-compatible, enabled by default
- HTTP Long Poll — fallback for restrictive networks, enabled by default
- Iroh (QUIC) — NAT-traversing P2P via iroh, opt-in
Installation
Using Nix
# Run directly without installing
nix run github:inkandswitch/subduction -- --help
# Install to your profile
nix profile install github:inkandswitch/subduction
# Then run
subduction_cli server --socket 0.0.0.0:8080 --ephemeral-key
Adding to a Flake
{
inputs.subduction.url = "github:inkandswitch/subduction";
outputs = { nixpkgs, subduction, ... }: {
# NixOS
nixosConfigurations.myhost = nixpkgs.lib.nixosSystem {
modules = [{
environment.systemPackages = [
subduction.packages.x86_64-linux.default
];
}];
};
# Home Manager
homeConfigurations.myuser = home-manager.lib.homeManagerConfiguration {
modules = [{
home.packages = [
subduction.packages.x86_64-linux.default
];
}];
};
};
}
Using Cargo
# Build from source
cargo build --release
# Run
./target/release/subduction_cli --help
Key Management
The server requires an Ed25519 signing key seed (32 bytes). The seed is used to deterministically derive both the signing key and the public verifying key. The verifying key becomes the server's peer ID.
Generating a Key
Any source of 32 cryptographically random bytes works. The CLI accepts either 64 hex characters or 32 raw bytes.
# OpenSSL
openssl rand -hex 32
# /dev/urandom
head -c 32 /dev/urandom | xxd -p -c 64
# Python
python3 -c "import secrets; print(secrets.token_hex(32))"
Providing the Key
| Flag | Description |
|---|---|
--key-seed <HEX> |
64 hex characters on the command line |
--key-file <PATH> |
Path to a file containing 64 hex characters or 32 raw bytes |
--ephemeral-key |
Generate a random key (lost on restart) |
These are mutually exclusive. Exactly one must be provided.
--key-file is recommended for production. The file must contain either:
- 64 hex characters (with optional trailing newline), or
- Exactly 32 raw bytes
# Create a persistent key file
openssl rand -hex 32 > /var/lib/subduction/key
chmod 600 /var/lib/subduction/key
# Start with key file
subduction_cli server --key-file /var/lib/subduction/key
[!WARNING] The key seed is equivalent to a private key. Do not commit it to version control or expose it in logs.
Commands
server
Start a Subduction sync node.
subduction_cli server --socket 0.0.0.0:8080 --key-file ./key
General Options
| Flag | Default | Description |
|---|---|---|
-s, --socket <ADDR> |
0.0.0.0:8080 |
Socket address to bind to |
-d, --data-dir <PATH> |
./data |
Data directory for filesystem storage |
-t, --timeout <SECS> |
5 |
Request timeout in seconds |
--handshake-max-drift <SECS> |
600 |
Maximum clock drift allowed during handshake |
--service-name <NAME> |
socket address | Service name for discovery mode handshake |
--max-message-size <BYTES> |
52428800 (50 MB) |
Maximum WebSocket message size |
Transport Options
| Flag | Default | Description |
|---|---|---|
--websocket |
enabled | Enable the WebSocket transport |
--longpoll |
enabled | Enable the HTTP long-poll transport |
--iroh |
disabled | Enable the Iroh (QUIC) transport |
At least one transport must be enabled.
WebSocket Peer Options
| Flag | Description |
|---|---|
--ws-peer <URL> |
WebSocket peer URL to connect to on startup (repeatable) |
Iroh Peer Options
| Flag | Description |
|---|---|
--iroh-peer <NODE_ID> |
Iroh peer node ID to connect to (z32-encoded, repeatable) |
--iroh-peer-addr <IP:PORT> |
Direct address hint for iroh peers (repeatable) |
--iroh-direct-only |
Skip relay servers, direct connections only |
--iroh-relay-url <URL> |
Route through a specific relay instead of the public default |
By default, iroh routes traffic through iroh's public relay infrastructure for NAT traversal. Use --iroh-direct-only for LAN-only deployments, or --iroh-relay-url to point at a self-hosted iroh-relay instance.
Metrics Options
| Flag | Default | Description |
|---|---|---|
--metrics |
disabled | Enable the Prometheus metrics server |
--metrics-port <PORT> |
9090 |
Port for Prometheus metrics endpoint |
--metrics-refresh-interval <SECS> |
60 |
Interval for refreshing storage metrics |
Other Options
| Flag | Description |
|---|---|
--ready-file <PATH> |
Write a file on startup with assigned port, peer ID, and iroh node ID |
purge
Delete all stored data.
subduction_cli purge --data-dir ./data
| Flag | Default | Description |
|---|---|---|
-d, --data-dir <PATH> |
./data |
Data directory to purge |
-y, --yes |
Skip confirmation prompt |
Examples
# Minimal server with an ephemeral key
subduction_cli server --ephemeral-key
# Server with persistent key and custom data directory
subduction_cli server --key-file ./key --data-dir /var/lib/subduction
# Two WebSocket servers syncing bidirectionally
subduction_cli server --key-file ./key1 --socket 0.0.0.0:8080 \
--ws-peer ws://192.168.1.101:8080
subduction_cli server --key-file ./key2 --socket 0.0.0.0:8080 \
--ws-peer ws://192.168.1.100:8080
# Iroh P2P (NAT-traversing, no WebSocket peers needed)
subduction_cli server --key-file ./key --iroh \
--iroh-peer <remote-node-id>
# Iroh direct only (LAN, no relay)
subduction_cli server --key-file ./key --iroh --iroh-direct-only \
--iroh-peer <node-id> --iroh-peer-addr 192.168.1.50:12345
# Discovery mode (clients connect by service name instead of peer ID)
subduction_cli server --key-file ./key --service-name sync.example.com
# Enable Prometheus metrics
subduction_cli server --key-file ./key --metrics --metrics-port 9090
# Debug logging
RUST_LOG=debug subduction_cli server --ephemeral-key
Running as a System Service
The flake provides NixOS and Home Manager modules for running Subduction as a managed service.
NixOS (systemd)
{
inputs.subduction.url = "github:inkandswitch/subduction";
outputs = { nixpkgs, subduction, ... }: {
nixosConfigurations.myhost = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
subduction.nixosModules.default
{
services.subduction = {
server = {
enable = true;
socket = "0.0.0.0:8080";
dataDir = "/var/lib/subduction";
keyFile = "/var/lib/subduction/key";
timeout = 5;
# WebSocket peers for bidirectional sync
wsPeers = [
"ws://192.168.1.100:8080"
"ws://192.168.1.101:8080"
];
# Iroh P2P transport
iroh = {
enable = true;
peers = ["<remote-node-id>"];
# directOnly = true; # LAN only, no relay
# relayUrl = "https://..."; # self-hosted relay
};
# Prometheus metrics
enableMetrics = true;
metricsPort = 9090;
};
# Shared settings
user = "subduction";
group = "subduction";
openFirewall = true;
};
}
];
};
};
}
This creates a systemd service: subduction.service
systemctl status subduction
journalctl -u subduction -f
Home Manager (user service)
Works on both Linux (systemd user service) and macOS (launchd agent):
{
inputs.subduction.url = "github:inkandswitch/subduction";
outputs = { home-manager, subduction, ... }: {
homeConfigurations.myuser = home-manager.lib.homeManagerConfiguration {
modules = [
subduction.homeManagerModules.default
{
services.subduction = {
server = {
enable = true;
socket = "127.0.0.1:8080";
keyFile = "/home/myuser/.config/subduction/key";
# dataDir defaults to ~/.local/share/subduction
wsPeers = ["ws://sync.example.com:8080"];
iroh.enable = true;
};
};
}
];
};
};
}
On Linux:
systemctl --user status subduction
On macOS:
launchctl list | grep subduction
tail -f ~/.cache/subduction/server.log
Behind a Reverse Proxy (Caddy)
When running behind Caddy or another reverse proxy, bind to localhost:
services.subduction.server = {
enable = true;
socket = "127.0.0.1:8080";
keyFile = "/var/lib/subduction/key";
};
services.caddy = {
enable = true;
virtualHosts."sync.example.com".extraConfig = ''
reverse_proxy localhost:8080
'';
};
Caddy automatically handles WebSocket upgrades and TLS certificates.
Monitoring
Prometheus
Enable the metrics endpoint:
subduction_cli server --key-file ./key --metrics --metrics-port 9090
Configure Prometheus to scrape:
# prometheus.yml
scrape_configs:
- job_name: 'subduction'
static_configs:
- targets: ['localhost:9090']
Development Monitoring Stack
With Nix, use the built-in command to launch Loki, Prometheus, and Grafana with pre-configured datasources and dashboards:
nix develop
monitoring:start
This starts:
- Loki at
http://localhost:3100(log aggregation) - Prometheus at
http://localhost:9092(metrics) - Grafana at
http://localhost:3939with pre-configured datasources and dashboards
To ship logs from the Subduction server to Loki, run it with LOKI_URL:
LOKI_URL=http://localhost:3100 cargo run -p subduction_cli -- server
Then query logs in Grafana via the Explore tab with the Loki datasource:
{service="subduction"} |= ""
Grafana Dashboard
Import the dashboard from subduction_cli/monitoring/grafana/provisioning/dashboards/subduction.json. It includes panels for connections, messages, sync operations, and storage.
Environment Variables
| Variable | Description |
|---|---|
RUST_LOG |
Log level filter for console output (e.g. debug, info, subduction_core=trace) |
LOKI_URL |
Grafana Loki endpoint for log shipping (e.g. http://localhost:3100) |
LOKI_LOG |
Log level filter for Loki (default: info). Same syntax as RUST_LOG. |
LOKI_SERVICE_NAME |
Service label for Loki (default: subduction) |
TOKIO_CONSOLE |
Set to any value to enable tokio-console |
Dependencies
~57–100MB
~1.5M SLoC