CLI
dwara-cli is the operator command line for working with configs without standing up a gateway.
run
dwara-cli run [ARGS]...Runs the gateway server. The CLI spawns the dwara binary (which must be on PATH) with the given arguments passed through verbatim; the environment (DWARA_CONFIG, etc.) passes through, and the binary's exit status is propagated.
validate
dwara-cli validate path/to/dwara.yamlRuns the same parse → validate → compile pipeline the gateway runs at startup and on reload, without starting a server. Prints every issue found (never stops at the first). Exit 0 on success (prints the route count), exit 1 on any issue.
fmt
dwara-cli fmt path/to/dwara.yamlNormalizes a config file in place: parses it, then re-serializes with stable field order and defaulted-empty collections omitted. The output is guaranteed to parse back to the same typed value. Prints nothing on success; exit 1 on failure.
diff
dwara-cli diff a.yaml b.yamlCompiles both configs and prints route/upstream/consumer deltas:
+ route new-api
- route old-api
~ upstream backend+/-/~ mean added / removed / same-name-but-changed (compared by a hash of the normalized serialization, so reordering keys in the source file never shows up as a change). Exit 1 if either side is invalid.
lint
dwara-cli lint path/to/dwara.yamlAdvisory checks beyond validation — config that compiles and routes traffic but likely doesn't do what the author meant:
| Rule | Flags |
|---|---|
prefix-duplicate | two prefix routes with an identical pattern (the later one can never win) |
regex-shadowed-by-exact | an exact route that fully matches a regex route's pattern |
consumer-unused | referenced by no authorization rule and bound to no JWT provider |
policy-unused | attached to nothing (no consumer, route, service, listener, or global) |
upstream-unreferenced | targeted by no service |
config/version | config version is older or newer than the current schema version |
Exit codes: 0 clean, 2 warnings found, 1 the file didn't even parse/validate (fix that first — linting an invalid config would just be noise).
migrate
dwara-cli migrate path/to/dwara.yaml
dwara-cli migrate path/to/dwara.yaml --in-placeUpgrades a config to the current schema version: parses the config, sets its version field to CURRENT_CONFIG_VERSION, and re-serializes. Without --in-place, the migrated config is printed to stdout; with --in-place, it is written back to the file. Migration notes are printed to stderr. Exit 0 on success, 1 on parse/serialize failure.
explain
dwara-cli explain --config path/to/dwara.yaml --method GET --path /api/v1/users
dwara-cli explain --config dwara.yaml --method POST --path /orders --header "Authorization: Bearer token"
dwara-cli explain --config dwara.yaml --method GET --path /api/users --consumer aliceRenders a human-readable explanation of what the gateway would do for a request matching the given method, path, headers, and consumer: route matching, authorization, rate limiting, transforms, upstream selection, and caching. Reuses the same pure decision logic the gateway runs at request time, so the explanation is always consistent with the gateway's actual behavior. Exit 0 on success, 2 on config parse/compile error.
schema
dwara-cli schemaPrints the JSON Schema (a standard for describing a JSON document's shape) of the gateway config to stdout (deterministic for a given build). This is what generates the committed config-reference.json — see Generating the config schema reference.
upgrade
dwara-cli upgrade
dwara-cli upgrade --pid 12345
dwara-cli upgrade --pid-file /run/dwara.pidSends SIGUSR2 (a Unix signal that triggers a zero-downtime upgrade) to a running gateway to trigger a zero-downtime binary upgrade. The PID is read from --pid, else the PID file (--pid-file or the DWARA_PID_FILE env var). The gateway must have been started with DWARA_PID_FILE set (or the PID supplied explicitly). See Zero-downtime upgrade for the hand-off sequence and the DWARA_UPGRADE_* env vars.
import
dwara-cli import nginx nginx.conf --output dwara.yaml
dwara-cli import kong kong.yaml --output dwara.yaml
dwara-cli import envoy envoy.yaml --output dwara.yaml
dwara-cli import traefik traefik.yml --output dwara.yaml
dwara-cli import haproxy haproxy.cfg --output dwara.yaml
dwara-cli import openapi petstore.yaml --output dwara.yaml
dwara-cli import openapi petstore.yaml --output dwara.yaml --mockScaffolds a Dwara config from an existing NGINX, Kong, Envoy, Traefik, or HAProxy config, or from an OpenAPI 3.x spec. With --mock, the OpenAPI import reads response examples and generates mock route actions instead of proxy actions, producing a fully functional mock API without any backend. Unsupported constructs are appended as YAML-comment warnings. See Config import and OpenAPI import and mock mode.
tf
dwara-cli tf export --admin http://127.0.0.1:2019 --out-state dwara.tfstate --out-hcl dwara.tf
dwara-cli tf plan --admin http://127.0.0.1:2019 --state dwara.tfstate
dwara-cli tf apply --admin http://127.0.0.1:2019 --state dwara.tfstateTerraform-compatible state export/plan/apply over the admin API. See Terraform state tool.
plugin
dwara-cli plugin new my-plugin [--template NAME]
dwara-cli plugin search [query] [--registry URL]
dwara-cli plugin install <name> [--version VERSION] [--registry URL] [--digest HASH] [-o DIR]
dwara-cli plugin keygen [--out-dir DIR]
dwara-cli plugin sign <wasm> --key <plugin.key | HEX>
dwara-cli plugin publish <wasm> --name NAME --version VERSION \
[--url URL] [--key KEY] [--registry URL] [--pr] [--repo OWNER/NAME] [--base BRANCH]plugin new scaffolds a ready-to-build proxy-wasm plugin crate (see Plugin SDK). Pass --template to start from one of the runnable example plugins instead of the hello-world default: static-auth (token gate with a 401 challenge), header-guard (header allow/deny), response-body-redact (card-number and secret scrubbing), or request-tagger (correlation headers). The example's source is vendored with your crate name substituted — its unit tests included, runnable on the host with cargo test and building for wasm32-wasip1 with zero dependencies; an unknown name lists the available templates. plugin search and plugin install work against a plugin registry (default https://shristilabs.github.io/dwara-plugins, override with --registry or the DWARA_PLUGIN_REGISTRY env var; requires curl). When the registry lists several versions of a plugin, install picks the highest semantic version; pass --version to pin an exact one (a missing version errors with the list of available versions). Installs are digest-pinned — pass --digest or take the digest the registry reports. plugin keygen, plugin sign, and plugin publish are the author side: they produce the signed, digest-pinned manifest entries the registry serves. See Plugin registry.
plugin keygen
dwara-cli plugin keygen # prints both hex keys
dwara-cli plugin keygen --out-dir keys/ # writes keys/plugin.pub + keys/plugin.keyGenerates an Ed25519 keypair for signing plugins. Without --out-dir, both keys print to stdout as hex (public_key: / private_key: lines). With --out-dir, plugin.pub (the public key — publish this with your manifest entries) and plugin.key (the private key, written with owner-only 0600 permissions) are written to the directory; existing key files are never overwritten. Keep plugin.key secret: anyone holding it can sign plugins as you.
plugin sign
dwara-cli plugin sign target/wasm32-wasip1/release/my_plugin.wasm --key keys/plugin.keySigns the artifact's bytes with Ed25519 and prints the 128-character hex signature — the same bytes the manifest's SHA-256 digest covers, so digest and signature always bind to the same artifact. --key accepts a path to a key file (as written by keygen --out-dir) or a literal 64-character hex key; prefer the key-file form — a literal --key <hex> lands in your shell history and in process listings while the command runs.
plugin publish
dwara-cli plugin publish target/wasm32-wasip1/release/my_plugin.wasm \
--name my-plugin --version 1.0.0 \
--url https://shristilabs.github.io/dwara-plugins/my-plugin-1.0.0.wasm \
--key keys/plugin.keyReads the built .wasm from disk, computes its SHA-256 digest, signs it when --key is given (self-verifying the signature before it is emitted), and prints a ready-to-paste registry manifest entry (fields in alphabetical key order, exactly as the tool prints them):
{
"digest": "9f2a7c3b...",
"name": "my-plugin",
"public_key": "9f2a7c3b1e8d...",
"signature": "c41d8cd9...",
"url": "https://shristilabs.github.io/dwara-plugins/my-plugin-1.0.0.wasm",
"version": "1.0.0"
}signature and public_key are present only when signing (hex, over the artifact bytes); unsigned entries omit them. Without --url the artifact URL defaults to <registry>/<name>-<version>.wasm (--registry / DWARA_PLUGIN_REGISTRY / https://shristilabs.github.io/dwara-plugins).
With --pr, the CLI opens the pull request itself using the GitHub CLI (gh): it fetches the registry's current manifest.json, merges your entry (replacing a previous entry with the same name and version, appending otherwise), commits it to a plugin/<name>-<version> branch, and opens a PR against --repo (default shristilabs/dwara-plugins, branch main via --base). If you do not have write access to the repository, it forks first and opens the PR from your fork. On any failure it prints the manifest entry anyway — paste it into manifest.json on a manual branch.
Author walkthrough: build to registry PR
# 1. Scaffold and build the plugin (.wasm targets wasm32-wasip1).
# Add --template static-auth to start from a working example.
dwara-cli plugin new my-plugin && cd my-plugin
rustup target add wasm32-wasip1
cargo build --release --target wasm32-wasip1
# 2. Generate a signing keypair once per author (keep plugin.key secret).
dwara-cli plugin keygen --out-dir keys
# 3. Host the artifact at a URL, then publish its manifest entry.
# Without --pr this prints the entry for a manual PR.
dwara-cli plugin publish target/wasm32-wasip1/release/my_plugin.wasm \
--name my-plugin --version 1.0.0 \
--url https://shristilabs.github.io/dwara-plugins/my-plugin-1.0.0.wasm \
--key keys/plugin.key --pr
# 4. After the registry PR merges, gateways install with the same pin:
dwara-cli plugin install my-plugin -o /var/lib/dwara/pluginsThe published digest, signature, and public_key are the fields a gateway's source: block pins; see Plugin registry for the verification flow.
replay
dwara-cli replay capture --duration 60s --output trace.dwara
dwara-cli replay run --trace trace.dwara [--config dwara.yaml] [--diff]Captures a redacted ring buffer of live traffic (max 10 minutes) and replays it offline against a config, reporting a MATCH/DIVERGENCE table (--diff). Read-only with respect to production: capture never blocks the dataplane and secrets are redacted unless --include-secrets is passed. See Replay debugging.
k8s conformance-report
dwara-cli k8s conformance-reportEmits the upstream Gateway API conformance report YAML. It is compiled into the standard dwara-cli build (no cargo feature required). See Kubernetes Gateway API.
status
dwara-cli status
dwara-cli status --admin http://127.0.0.1:2019Prints a one-shot snapshot of the running gateway: version, uptime, readiness, config generation and hash, per-upstream endpoint health, circuit-breaker states, active request count, cache stats, and — when the config declares plugins — a plugins section listing each plugin's lifecycle state (with the load error for a crashed one; see Admin API: plugin status). The admin URL defaults to DWARA_ADMIN or http://127.0.0.1:2019.
top
dwara-cli top
dwara-cli top --interval 2000A live, refreshing view of upstream load-balancer state: algorithm, scheme, connections opened, requests sent, breaker state, and per-endpoint health and inflight counts. Refreshes every second (override with --interval <ms>) until interrupted with Ctrl-C.
completions
dwara-cli completions bash > /etc/bash_completion.d/dwara
dwara-cli completions zsh > ~/.zfunc/_dwara-cli
dwara-cli completions fish > ~/.config/fish/completions/dwara-cli.fish
dwara-cli completions powershell | Out-File dwara-cli.ps1Generates a shell completion script for the dwara-cli command model. Supported shells: bash, zsh, fish, PowerShell.
dwara-loadgen
dwara-loadgen is a separate benchmarking binary shipped alongside the CLI. It drives concurrent load at a target across HTTP/1.1, HTTP/2 (h2c), and HTTP/3 (QUIC), and optionally runs an in-process echo upstream:
dwara-loadgen --url http://127.0.0.1:8080/v1/ --connections 10 \
--duration 30 --rate 500
dwara-loadgen --echo 9000 --echo-only # just the echo upstream
dwara-loadgen --url http://127.0.0.1:8080/ --protocol h2 \
--workload pool-reuse --json # h2 pool-reuse, JSON output--protocol h3 uses the HTTP/3 transport compiled into the default build (cargo build -p dwara-cli --bin dwara-loadgen) and rejects --echo (no in-process QUIC echo — point it at a real h3 listener); --insecure skips certificate verification for loopback targets. The three --workload shapes are throughput (default, back-to-back requests on an owned connection), pool-reuse (a shared pooled client exercising connection/stream reuse), and streaming (chunked echo the client drains fully). With --json, a machine-parseable JSON: line is emitted alongside the RESULT: line for the regression gate (scripts/bench-regression.py).
| Flag | Default | Description |
|---|---|---|
--url | http://127.0.0.1:18080/ | Target URL. |
--protocol | h1 | Wire protocol: h1, h2 (h2c), or h3. |
--workload | throughput | Macro workload: throughput, pool-reuse, or streaming. |
--connections | 10 | Concurrent worker connections. |
--duration | 10 | Run length in seconds. |
--rate | 0 (unbounded) | Target requests/second across all connections. |
--echo <port> | off | Also serve a minimal HTTP/1.1 echo upstream on this port. |
--echo-only | off | Serve only the echo upstream (requires --echo). |
--echo-body | 128 | Echo response body size in bytes (throughput/pool-reuse). |
--stream-chunks | 8 | Chunks the streaming echo emits per response (streaming + --echo). |
--stream-chunk-bytes | 1024 | Bytes per streaming chunk (streaming + --echo). |
--stream-chunk-delay-ms | 0 | Delay between streaming chunks in ms (streaming + --echo). |
--timeout-ms | 10000 | Per-request timeout. |
--json | off | Emit a machine-parseable JSON: line (for the regression gate). |
--insecure | off | (h3 only) Skip server certificate verification; loopback targets only. |
Runnable demo
Run config validation against a live gateway: demos/09-operations/ in the repository (test script: test-03-cli-validate.sh). The demo image ships only the gateway binary, so the script documents dwara-cli validate and proves the config through the gateway's startup validation; the README covers running the CLI on the host.