subduction_cli 0.5.0

CLI server for Subduction sync over WebSocket, HTTP long-poll, and Iroh (QUIC)
subduction_cli-0.5.0 is not a library.

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
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

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 Prometheus and Grafana with pre-configured dashboards:

nix develop
monitoring:start

This starts:

  • Prometheus at http://localhost:9092
  • Grafana at http://localhost:3939 with 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)