Skip to main content

Administer accounts from the command line

The WaterWall checkout includes tunnels/APIServer/client/ww_admin.py. It provides account commands using Python 3.10 or newer and the standard library. Keep ww_admin.py, ww_api.py and ww_api_cli.py together when copying the client directory; no package installation is needed. ww_api.py handles TCP and outer envelopes; ww_api_cli.py shares connection options, bounded token loading and uncertain-write advice. AuthenticationServer body/schema checks stay in the CLI.

Start with the local server configuration, then add the separate creator, editor, and password-admin roles from the account workflow example. The commands below run from that example's api-demo directory, with WATERWALL_REPO set to the checkout's absolute path. Permission configuration still belongs to APIServer; the CLI grants no extra access.

Select the server and operator​

A full command looks like this:

python3 "$WATERWALL_REPO/tunnels/APIServer/client/ww_admin.py" \
--host 127.0.0.1 --port 9080 \
--client monitor --token-file monitor-token \
--config central --node auth-db status

All connection and authentication options precede status or users. Defaults are host 127.0.0.1, port 9080, timeout 10 seconds and maximum response envelope 1 MiB. Use --timeout and --max-response-bytes to change those bounds. Each socket read requests at most 64 KiB; all chunks share the original monotonic request deadline. The response cap accepts 1 byte through 64 MiB. The timeout covers one complete request, but Python's blocking hostname lookup can exceed it before control returns; use an IP address when that resolver limitation is unsuitable. --client and --node are required. Choose either --token-file PATH or --token-env NAME; the latter names an environment variable holding the token. There is no plaintext token command-line option.

--config central selects a logical configuration name, not a filename. It may be omitted only when the node name is unique across loaded configurations. This CLI speaks TCP; a local TCP entry can use the existing reliable UDP bridge. It does not construct HTTP requests or add encryption. Remote use needs an appropriately protected transport.

For the remaining Bash examples, define this shortcut:

wwadmin() {
local operator="$1"
shift
python3 "$WATERWALL_REPO/tunnels/APIServer/client/ww_admin.py" \
--client "$operator" --token-file "${operator}-token" \
--config central --node auth-db "$@"
}

The status result always includes audit. enabled identifies whether the AuthenticationServer audit log is configured; healthy, dropped_records, write_failures and last_error describe its health. queued_records is queue depth and written_records counts successful append-and-flush operations. These process-local counters remain decimal strings. Disabled logging is healthy with zero counters. Audit failures do not change an account operation's committed outcome. Configuration and rotation belong to AuthenticationServer; the CLI does not manage log files.

Read an account or traverse its summaries​

wwadmin monitor status
wwadmin monitor users list --limit 50
wwadmin monitor users get 42

get returns not_found until that ID exists. IDs and unsigned account values remain decimal strings, including values above JavaScript's exact integer range. Results are the public redacted API schemas; the CLI does not expose passwords or derived credentials.

users list defaults to 50 summaries and accepts a limit from 1 through 100. To read one subsequent page, pass its exact next_cursor with --cursor. To follow pages automatically:

wwadmin monitor users list --all --limit 50 --max-pages 100 > pages.jsonl

This writes one complete page per JSON line, including page metadata and its users array. It streams pages rather than building one unbounded array. --max-pages defaults to 100 and accepts 1 through 10000. Reaching that cap while another cursor remains is an error, not a complete traversal. A repeated cursor, invalid reply, failed request or stale_cursor also stops the command. Check its exit status: earlier lines are incomplete results when a later page fails.

Configuration edits invalidate cursors. The CLI does not silently restart and append duplicate pages; explicitly start a new traversal and replace the old output after a stale cursor. Usage totals can change between valid pages, so a successful traversal is not a frozen usage snapshot. Summaries may abbreviate names; users get provides full permitted details.

Create an account​

wwadmin creator users create 42 --set name=alice --set enabled=false

The CLI asks for the initial password twice without echoing it. You choose the permanent nonzero numeric ID. Creation requires create-user permission and no edit token; an existing ID is never overwritten.

Repeat --set FIELD=VALUE for the intended initial policy. Omitted --set options use the server's defaults, including enabled with unlimited/unset zero limits. The explicit enabled=false above stages the account. Field names use the public API's snake_case and dotted nesting:

--set limit.traffic.total=1000000000
--set time.expire_after_first_usage_ms=2592000000
--set wireguard_allowed_ips=10.44.0.1/32
--set notes=

Numeric values are canonical unsigned decimal strings; booleans must be true or false. String values are literal, including an empty value to clear a field. Unknown or repeated fields are refused. See the 18 editable configuration leaves for units, ranges, zero meanings and restrictions. Storing a policy does not add enforcement support to other nodes.

Read, review and edit​

Read with the editor's token:

wwadmin editor users get 42 > account.json

Continue only if that command succeeds. Inspect account.json, then extract the token from that same read:

EDIT_TOKEN="$(python3 -c 'import json; print(json.load(open("account.json"))["edit_token"])')"
wwadmin editor users update 42 --if-match "$EDIT_TOKEN" \
--set enabled=true --set limit.traffic.total=1000000000

The CLI sends only the selected fields. Usage, first-use history and credentials cannot be patched. Enable and disable are shortcuts for a one-field update:

wwadmin editor users get 42 > account.json
# After a successful read, inspect it and obtain its current token again.
EDIT_TOKEN="$(python3 -c 'import json; print(json.load(open("account.json"))["edit_token"])')"
wwadmin editor users disable 42 --if-match "$EDIT_TOKEN"

Use users enable in the same way. --if-match is required for these commands and for password changes. The token contains a server-instance identity and the server-wide configuration revision; edits to other accounts can invalidate it. A conflict means this invocation did not commit. Reread and reconsider the change. The CLI never reads a fresh token and replays a write automatically.

Change a password​

Read with the password administrator's own permission and review the account:

wwadmin password-admin users get 42 > account.json
# Continue only after a successful read and review.
EDIT_TOKEN="$(python3 -c 'import json; print(json.load(open("account.json"))["edit_token"])')"
wwadmin password-admin users change-password 42 --if-match "$EDIT_TOKEN"

The default is a hidden confirmation prompt. For automation, creation and password changes accept either --password-file PATH or --password-stdin. Neither accepts a password as a command-line value. File and stdin input must be nonempty UTF-8 without NUL and is preserved exactly: a trailing newline becomes part of the password. Use a secret source that writes the desired bytes; ordinary echo appends a newline. Token files have separate handling: exactly 64 hexadecimal characters, optionally followed by CR, LF or CRLF. Reading is bounded to 66 bytes plus one overflow-detection byte. Non-ASCII/oversized input and empty environment-option names fail before transport use.

Password changes preserve the account's ID, policy, usage and first-use history. They do not forcibly disconnect existing sessions. Consult the password operation reference for derived credential and UUID identity semantics.

Interpret the result before another write​

Successful commands print JSON to stdout. Errors and recovery guidance go to stderr; arbitrary server error messages and supplied secrets are not echoed.

Exit statusMeaning
0Success. For --all, the final page was reached.
1A validated node operation failure, such as conflict or stale_cursor.
2Local validation, a failed read, a definite gateway refusal, or a local pagination bound/consistency failure.
3A write may have committed but its outcome could not be established.

The client makes no automatic retries. If a write's reply is lost, malformed or times out after possible dispatch, keep its intended inputs and reread. Compare the account before explicitly deciding whether another configuration change is needed. For creation, retain the original ID: its existence does not prove who created it or which password it holds. Do not automatically choose another ID.

Gateway busy and shutting_down are definite refusals before the handler is invoked, for create, update and password changes alike. They return exit code 2 without uncertain-outcome advice or retry. A node-operation result with the same code remains a separate operation outcome; post-dispatch gateway errors remain uncertain.

A public read cannot confirm the installed password. After an uncertain password change, explicitly decide whether to resubmit the desired password with a fresh edit token. An acknowledged resubmission establishes that password at its own commit point. Revision changes alone cannot prove that a particular earlier request succeeded.

An acknowledgement confirms an in-memory server change, not a disk save or an AuthenticationClient refresh. Offline clients continue with their cached policy and credentials until a valid refresh. This CLI adds no crash recovery or server retry records. See the wire-level account workflows and protocol delivery rules for the underlying API guarantees.