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
# Run directly without installing
# Install to your profile
# Then run
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
# Run
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
# /dev/urandom
|
# Python
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
# Start with key file
[!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.
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.
| Flag | Default | Description |
|---|---|---|
-d, --data-dir <PATH> |
./data |
Data directory to purge |
-y, --yes |
Skip confirmation prompt |
Examples
# Minimal server with an ephemeral key
# Server with persistent key and custom data directory
# Two WebSocket servers syncing bidirectionally
# Iroh P2P (NAT-traversing, no WebSocket peers needed)
# Iroh direct only (LAN, no relay)
# Discovery mode (clients connect by service name instead of peer ID)
# Enable Prometheus metrics
# Debug logging
RUST_LOG=debug
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
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:
On macOS:
|
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:
Configure Prometheus to scrape:
# prometheus.yml
scrape_configs:
- job_name: 'subduction'
static_configs:
- targets:
Development Monitoring Stack
With Nix, use the built-in command to launch Prometheus and Grafana with pre-configured dashboards:
This starts:
- Prometheus at
http://localhost:9092 - Grafana at
http://localhost:3939with pre-configured dashboards
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 (e.g. debug, info, subduction_core=trace) |
TOKIO_CONSOLE |
Set to any value to enable tokio-console |
LOKI_URL |
Grafana Loki endpoint for log shipping (e.g. http://localhost:3100) |
LOKI_SERVICE_NAME |
Service label for Loki (default: subduction) |