- A subnet is a deny-list subnet (the default: everyone not denied) or
an allow-list subnet (only clients allowed or with a reservation);
the deny list counts in both. Entries are MAC addresses, prefixes
(aa:bb:cc:*) or client IDs. A refused client gets no answer (DENIED
in the activity log, with an Allow button) or a NAK on renewal.
steward access, the subnet's Access tab. Schema migration 7.
- The activity log keeps each message's header fields and options,
decoded into words (dhcp4.Describe): the parameter request list by
name, client IDs, lease times. ▸ on an Activity row, steward
activity show N / --details, GET /events/{id}.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
9.4 KiB
The steward CLI
steward talks to stewardd's REST API. steward help COMMAND describes
each command and its flags.
Connecting
-
On the server, with nothing configured, the CLI uses stewardd's local socket,
/run/steward/steward.sock. Root and thestewarduser are administrators there without a token:sudo steward status. -
From elsewhere, give the server's URL and an API token:
steward config set server https://dhcp.example.com:8067 steward config set token # asks for itThe settings are in
~/.config/steward/config.yaml(on macOS~/Library/Application Support/steward/config.yaml, on Windows%AppData%\steward\config.yaml).
| Flag | Environment | |
|---|---|---|
-s, --server URL |
STEWARD_SERVER |
the server; local for the local socket |
--token TOKEN or @FILE |
STEWARD_TOKEN |
the API token |
--config FILE |
STEWARD_CLI_CONFIG |
the CLI's configuration file |
STEWARD_SOCKET |
the local socket's path | |
--insecure, --ca-cert FILE |
TLS: skip the check, or trust another CA | |
-n, --dry-run |
check and show a change without making it | |
-o, --output FORMAT |
table (default), json, yaml or id |
|
-q, --quiet |
print only errors (and the IDs of new objects) |
Exit codes: 0 ok, 1 error, 3 not authenticated or not allowed, 4 not found, 5 conflict, 6 invalid or refused by validation, 7 server error.
Changes
Every change is checked as a whole first. A refused change says why and gives the problem's code (validation.md). A saved one says its history entry, and any new warnings:
$ steward subnet add 10.0.20.0/24 --interface eth0.20 --name iot --pool 10.0.20.100-10.0.20.200
created subnet iot (10.0.20.0/24)
saved (history 2; undo with 'steward history undo 2')
warning: subnet iot (10.0.20.0/24): this server has no interface eth0.20 (it has eth0, lo); the subnet is served once it appears [interface_unknown]
-n shows what would change without changing it.
Commands
status
steward status: the version, each interface and whether it is served
(and why not), each subnet's pool use, leases by state, DHCP messages
since the start, and problems.
subnet
| Command | |
|---|---|
subnet list |
subnets, with interface, pools, use, reservations, lease time |
subnet show SUBNET |
one subnet; SUBNET is its ID, name or prefix |
subnet add PREFIX --interface IFACE [--name N] [--pool A-B[:ORDER]]... [--pool-order listed|random] [--address-order sequential|reverse|random] [--lease-time 12h] [--option NAME=VALUE]... [--comment C] |
add one |
subnet update SUBNET [--name] [--interface] [--prefix] [--pool]... [--add-pool] [--remove-pool] [--pool-order] [--address-order ...|default] [--lease-time 1h|default] [--option NAME=VALUE]... [--unset-option NAME]... [--comment] |
change what is given; --add-pool of a pool it has changes that pool's order (subnets.md) |
subnet enable|disable SUBNET |
answer its clients, or not |
subnet delete SUBNET [-y] |
delete it and its reservations |
--pool replaces the pools; --add-pool and --remove-pool change one.
--option sets an option and keeps the others; --unset-option removes
one. An option Steward doesn't know: --option 224:string=hello.
reservation
| Command | |
|---|---|
reservation list [--subnet SUBNET] |
reservations, whether each client is online, and when it last answered a ping |
reservation show RES |
RES is its ID, address or MAC address |
reservation add IP MAC [--hostname H] [--client-id HEX] [--lease-time 7d] [--option NAME=VALUE]... [--comment C] |
- for MAC with --client-id; with both, either matches |
reservation update RES [--ip] [--mac] [--client-id] [--hostname] [--lease-time 7d|default] [--option]... [--unset-option]... [--comment] |
|
reservation enable|disable RES |
use a reservation, or keep it but ignore it |
reservation delete RES |
access
| Command | |
|---|---|
access show SUBNET |
the subnet's mode and its allow and deny lists |
access mode SUBNET deny-list|allow-list |
answer every client not denied, or only the allowed (and reserved) |
access allow SUBNET ENTRY... [--comment C] |
add to the allow list (aa:bb:cc:dd:ee:ff, aa:bb:cc:* or id HEX); an entry it has gets the new comment |
access deny SUBNET ENTRY... [--comment C] |
add to the deny list |
access remove SUBNET ENTRY... |
take off both lists |
See subnets.md.
lease
| Command | |
|---|---|
lease list [--subnet S] [--state STATE] [--interface I] [--search TEXT] [-a] |
active and held-back leases; -a all |
lease show IP |
one lease |
lease reserve IP [--hostname H] [--by mac|client-id|both] |
make the address its client's reservation, by its MAC address and client ID (either matches) unless --by says one |
lease delete IP [-y] |
free the address at once |
activity
steward activity [--mac MAC] [--subnet SUBNET] [--interface I] [--limit N] [-d] [-f]:
the latest DHCP messages, received and sent, oldest first. --subnet
takes an ID, name or prefix; a message without an address (a DISCOVER,
"no address") shows for every subnet on its interface. -f keeps
watching, which is useful while a device tries to get an address:
#1042 11:02:13.120 eth0 DHCPDISCOVER 3c:22:fb:11:22:33 - adams-macbook
#1043 11:02:13.622 eth0 DHCPOFFER 3c:22:fb:11:22:33 10.0.10.150 adams-macbook
#1044 11:02:13.630 eth0 DHCPREQUEST 3c:22:fb:11:22:33 10.0.10.150 adams-macbook
#1045 11:02:13.631 eth0 DHCPACK 3c:22:fb:11:22:33 10.0.10.150 adams-macbook
Each line starts with the message's number. -d (--details) adds every
field and option of each message in words, and steward activity show N
shows one: what the client asked for (its parameter request list, by
name), its client identifier and host name, and what Steward sent back
(lease time, routers, DNS servers...):
$ steward activity show 1042
#1042 DHCPDISCOVER on eth0 at 2026-10-18 11:02:13.120, received from the client
transaction ID: 0x3a1c99f0
client hardware address: 3c:22:fb:11:22:33
flags: broadcast (the client wants replies broadcast)
option 53 message-type: DHCPDISCOVER
option 61 client-identifier: 01:3c:22:fb:11:22:33 (type 1: the MAC address 3c:22:fb:11:22:33)
option 55 parameter-request-list: subnet-mask (1), routers (3), domain-name-servers (6), domain-name (15), v4-captive-portal (114)
option 12 host-name: adams-macbook
In the web UI, ▸ on an Activity row opens the same.
NO ADDRESS means the pools on that interface are full, and CONFLICT
an address that answered the ping check. A DHCPNAK says why it was
sent.
settings and options
| Command | |
|---|---|
settings show |
|
settings set [--lease-time 1d] [--ping-check on|off] [--ping-timeout 500ms] [--decline-hold 24h] [--authoritative on|off] [--address-order sequential|reverse|random] [--option NAME=VALUE]... [--unset-option NAME]... |
|
option list |
the options Steward knows (options.md) |
interface list |
the server's interfaces, their addresses and subnets (not those in interfaces.exclude) |
history
| Command | |
|---|---|
history list [--limit N] |
changes, newest first |
history show ID |
each object's fields before and after |
history undo ID |
apply the change backwards (refused if something it touched has changed since) |
import
steward import kea CONFIG [--leases FILE]... [--interface PREFIX=IFACE]... [--replace]:
see kea.md.
cert
Administrators only (certificates.md).
| Command | |
|---|---|
cert |
the certificate: name, CA, validity, renewal, the last check |
cert check |
check that the challenge can be published through Jarl and seen, without the CA (fails with exit code 1 if it can't) |
cert renew [-w] |
get a new certificate now; -w waits and shows the result |
users, tokens, audit
| Command | |
|---|---|
whoami |
who you are to the server, and what you may do |
user list|add|update|delete |
users (administrators); user add NAME --role operator |
token list [--all], token create NAME [--role R] [--grant G]... [--expires 90d] [--user U] [--save], token revoke ID |
API tokens |
token rotate ID|--current [--grace 1h] [--expires 90d|never] [--save] |
a new secret for a token, the old one working for the grace period |
password |
change your password |
audit [--user U] [--action PREFIX] [--search TEXT] |
the audit log |
config show, config set server|token|insecure|ca_cert [VALUE] |
this CLI's settings |
permissions.md explains roles and grants.
stewardd
The daemon's own commands work on the database directly, as root or the
steward user (sudo -u steward stewardd ...):
| Command | |
|---|---|
stewardd serve |
run (the systemd unit does) |
stewardd init [-u NAME] [--token] |
create the database and the first administrator (asks for the name on a terminal) |
stewardd user add NAME [--admin], user password NAME, user token NAME TOKEN-NAME |
when the API can't be used |
stewardd check |
check the configuration against this machine's interfaces |
stewardd backup [FILE], stewardd restore FILE |
operations.md |
stewardd config [--example] |
the settings in use, or a commented example |
stewardd version |
All take -c FILE for another configuration file.