Files
housecarl/docs/cli.md
T
abrown38andClaude Opus 5.5 92147c8681 Diffs name their chain; history shows the ruleset's diff
The ruleset's diff names the chain each hunk is in on its @@ line, as
git names the function. A history entry shows what it did to the
ruleset (web page, hc history diff, GET /history/{id}/diff), rebuilt
from the current configuration by stepping back through the later
entries. A changed setting is listed by its own fields
(builtins.drop_invalid) instead of the whole object.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 23:39:03 +01:00

281 lines
14 KiB
Markdown

# The hc CLI
`hc` talks to housecarld's REST API. `hc help COMMAND` describes each
command and its flags.
## Connecting
* **On the firewall**, with nothing configured, the CLI uses
housecarld's local socket, `/run/housecarl/housecarl.sock`. Root and
the `housecarl` user are administrators there without a token, and
their changes need no confirmation: `sudo hc status`.
* **From elsewhere**, give the server's URL and an API token:
```sh
hc config set server https://fw.example.com:8070
hc config set token # asks for it
```
The settings are in `~/.config/housecarl/config.yaml` (on macOS
`~/Library/Application Support/housecarl/config.yaml`, on Windows
`%AppData%\housecarl\config.yaml`).
| Flag | Environment | |
|---|---|---|
| `-s, --server URL` | `HOUSECARL_SERVER` | the server; `local` for the local socket |
| `--token TOKEN` or `@FILE` | `HOUSECARL_TOKEN` | the API token |
| `--config FILE` | `HOUSECARL_CLI_CONFIG` | the CLI's configuration file |
| | `HOUSECARL_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, with the ruleset's diff |
| `--no-confirm` | | leave a change on probation instead of confirming it once `hc` reaches Housecarl again |
| `-C, --changeset ID` | `HOUSECARL_CHANGESET` (checked, see [Changesets](#changesets)) | stage configuration changes in this changeset instead of making them |
| `-o, --output FORMAT` | | `table` (default), `json`, `yaml` or `id` |
| `-q, --quiet` | | print only errors (and the IDs of new objects) |
| `--no-names` | | show inside addresses without their names: tables show `192.168.1.20 (laptop)` ([configuration.md](configuration.md#names)) |
Exit codes: 0 ok, 1 error, 3 not authenticated or not allowed, 4 not
found, 5 conflict, 6 invalid or refused by validation or nftables, 7
server error.
## Changes
Every change is checked as a whole, then by nftables, before it is put
in force. A refused change says why, with the problem's code
([validation.md](validation.md)). An applied one says its history entry
and any new warnings. Over the network, it is on probation, and `hc`
confirms it by reaching Housecarl again over a new connection
([operations.md](operations.md#changes-on-probation)):
```
$ hc rule add input --from trusted --service ssh --comment "SSH from home"
created rule input #31 (from trusted · ssh)
applied and confirmed (history 12): this client reached Housecarl again over a new connection
```
Through the socket the change is kept at once:
```
applied and saved (history 12; undo with 'hc history undo 12')
```
`-n` shows what would change without changing it, with the diff of the
ruleset (each `@@` line names the chain the lines below it are in);
nftables checks it too.
## Changesets
To review several changes and apply them together, as one entry in the
history, stage them in a changeset:
```sh
export HOUSECARL_CHANGESET=$(hc changeset create "guest network" -q)
hc zone add guest -i eth1.30
hc rule add forward --in guest --out wan
hc changeset diff # the operations, problems, nftables' check, the ruleset's diff
hc changeset remove 2 # take operation 2 out
hc changeset apply # one change; over the network on probation
unset HOUSECARL_CHANGESET
```
```
$ hc rule add forward --in guest --out wan
changeset 7 (guest network, 1 operation) from HOUSECARL_CHANGESET
staged: create rule forward #52
staged in changeset 7 as operation 2 (2 operations now); review with 'hc changeset diff 7', apply with 'hc changeset apply 7'
```
Each change is checked as it is staged: the changeset with it must stay
valid, and nftables must accept the ruleset, so make a set before the
rule that names it. Applying checks the whole changeset again, on the
configuration as it is then; an operation whose object changed since is
named (`operation 3 (update rule input #12) can't be applied: …`), and
`hc changeset remove` takes it out.
| Command | |
|---|---|
| `changeset create [DESCRIPTION]` | start one; `-q` prints only its ID |
| `changeset list [--status open\|applied\|discarded\|""] [--mine]` | open ones by default |
| `changeset show [ID]` | its operations |
| `changeset diff [ID]` | what applying it would do |
| `changeset apply [ID] [--description D]` | apply it |
| `changeset discard [ID]` | throw it away |
| `changeset remove [ID] OP` | take one operation out |
`show`, `diff`, `apply`, `discard` and `remove` work on the changeset you
name, else the one in `-C`, else the one in `HOUSECARL_CHANGESET`.
A changeset from `HOUSECARL_CHANGESET` is checked before anything uses
it, because an exported ID easily outlives its changeset:
- Staging into it, and `apply`, `discard` and `remove` on it, need it to
be open and yours. Otherwise the command stops and says why (`there is
no changeset 99`, `changeset 42 is applied`, `changeset 57 is sam's`).
Commands that only read, and `hc changeset create`, don't care.
- Each command says on stderr which changeset it uses: `changeset 42
(guest network, 2 operations) from HOUSECARL_CHANGESET` (not with `-q`).
- After `apply` or `discard`, a reminder to `unset HOUSECARL_CHANGESET`.
`-C ID` and an ID argument are used as given (housecarld still refuses a
changeset that isn't open, or another user's unless you are an
administrator). Only configuration changes are staged: dynamic set
entries, `confirm` and `rollback` are made at once; `history undo` is
staged like any change; `import` and `takeover` can't be staged, so they
are refused while a changeset is named. Changesets staged in the web UI show up here too,
and the other way round.
## Commands
Objects are named by ID or, for zones, sets, services and port forwards,
by name.
### status and the ruleset
| Command | |
|---|---|
| `status` | the mode (in shadow mode), the ruleset (in force or not), changes on probation, problems, interfaces, other tables |
| `interfaces` | the machine's interfaces, their addresses and zones |
| `ruleset [--enforcing]` | the nftables ruleset Housecarl builds; in shadow mode, `--enforcing` shows the one loaded once it is off ([shadow.md](shadow.md)) |
| `counters` | the hits of every rule, NAT rule, built-in and policy, by mark (`r12`, `f3`, `b-established`, `p-input`) |
| `quotas` | what each rule's quota used (each address's, for per-address quotas), and when it resets |
| `log [-f] [--search TEXT] [--limit N]` | the packets logged by rules and policies ([operations.md](operations.md#the-log)) |
| `connections [--search TEXT] [--limit N]`, `connections close --search TEXT [-y]` | tracked connections; closing makes the rules check them again ([operations.md](operations.md#connections)); not in shadow mode |
| `stats [--since 24h]` | traffic per zone, drops, the busiest rules, blocked sources, devices; `stats status`, `stats traffic`, `stats top devices\|blocked\|tried\|quota ID`, `stats rules [--unused]`, `stats connections` ([stats.md](stats.md#the-cli)) |
| `matrix [FROM TO]` | what the rules do to new connections between zones (allow, block or partly), and what is let through; with FROM and TO, that pair's rules in order, with their hits ([rules.md](rules.md#the-zone-matrix)) |
| `trace [--address A] [--port N] [--protocol P] [--seconds S]` | follow packets through every table's rules ([operations.md](operations.md#tracing)) |
### zone, set, service
| Command | |
|---|---|
| `zone list`, `zone show ZONE` | |
| `zone add NAME -i IFACE... [--external] [--comment C]` | |
| `zone update ZONE [-i IFACE]... [--add IFACE] [--remove IFACE] [--external[=false]] [--name N] [--comment C]` | `-i` replaces the interfaces; `--name` renames it, and the rules naming it follow |
| `zone delete ZONE` | refused while rules use it |
| `set list`, `set show SET` | |
| `set add NAME [ENTRY...] [--dynamic] [--timeout 24h] [--comment C]` | |
| `set update SET [--entry E]... [--add E] [--remove E] [--timeout] [--name] [--comment]` | `--entry` replaces the entries |
| `set entries SET`, `set entries add SET ADDRESS [--timeout 1h]`, `set entries remove SET ADDRESS` | a dynamic set's entries |
| `set delete SET` | |
| `service list`, `service show SERVICE` | with the built-in ones |
| `service add NAME ENTRY... [--comment C]` | entries `tcp/22`, `udp/1000-2000`, `icmp/echo-request`, `icmpv6/packet-too-big` |
| `service update SERVICE`, `service delete SERVICE` | |
See [rules.md](rules.md).
### rule
| Command | |
|---|---|
| `rule list [CHAIN]` | in order, with their hits |
| `rule show ID` | |
| `rule add CHAIN [MATCH] [-a ACTION] [--first\|--before ID\|--after ID]` | at the end of its chain unless placed |
| `rule update ID [flags]` | only the flags given |
| `rule move ID --before ID\|--after ID\|--first\|--last\|--chain CHAIN` | |
| `rule enable\|disable ID` | |
| `rule reset-quota ID [--address ADDR] [--give 5gbytes]` | start a rule's quota again now, or one address's; `--give` takes an amount off what was used instead, until the next reset |
| `rule delete ID` | |
The match flags (`rule add`, `snat add`, `redirect add`):
| | |
|---|---|
| `--in ZONE`, `--out ZONE` (`--not-in`, `--not-out`) | zones |
| `--from`, `--to` (`--not-from`, `--not-to`) | sets, addresses, prefixes, ranges |
| `--service` | services or entries |
| `--family ipv4\|ipv6` | |
And for rules: `--state`, `--status`, `--days`, `--hours`, `--limit
10/minute|1mbytes/second[,burst=N][,over][,per-source|,per-destination]`,
`--quota 50gbytes[,over][,per-source|,per-destination][,daily|,weekly|,monthly=DAY]`,
`--group-v4 N`, `--group-v6 N`, `--conn-limit N`, `--every-packet`, `-a
accept|drop|reject|log`, `--reject-with tcp-reset`, `--log`,
`--log-prefix`, `--raw RULE`, `--comment`, `--disabled`
([rules.md](rules.md#rules)).
### forward, snat, redirect
| Command | |
|---|---|
| `forward list`, `forward show FORWARD` | port forwards, with their hits |
| `forward add PORTS --to HOST[:PORT] [--proto tcp,udp] [--public ADDR]... [--from SRC]... [--hairpin] [--hairpin-from SRC]... [--to6 HOST] [--name N] [--comment C]` | |
| `forward update FORWARD [flags]`, `forward delete FORWARD` | |
| `snat list`, `snat add [MATCH] [--snat-to ADDR]`, `snat update ID`, `snat delete ID` | source NAT, in order; masquerade without `--snat-to` |
| `redirect list`, `redirect add [MATCH] [--to-port PORT]`, `redirect update ID`, `redirect delete ID` | |
See [nat.md](nat.md).
### banlist, ban
| Command | |
|---|---|
| `banlist list`, `banlist show BANLIST` | ban lists, with their bans' count |
| `banlist add NAME --scope firewall\|hosts\|everything [--host ADDR\|SET]... [--never ADDR\|SET]... [--time 1h] [--max-time 7d\|none] [--max-entries N] [--group-v6 64] [--disabled] [--comment C]` | |
| `banlist update BANLIST [flags]`, `banlist delete BANLIST` | `--host` and `--never` replace the lists; no renaming |
| `bans [BANLIST]` | the active bans |
| `ban BANLIST ADDRESS [--tcp PORT\|SERVICE]... [--udp …]... [--time 1h\|none] [--reason R]` | ban every port, or some; again renews |
| `unban BANLIST ADDRESS\|ID` | lift one ban, or all of an address's |
See [bans.md](bans.md).
### settings
| Command | |
|---|---|
| `settings show` | |
| `settings set [--input-policy\|--forward-policy\|--output-policy accept\|drop] [--log-drops on\|off] [--builtin NAME=on\|off]... [--own-prefix P]... [--antispoof-v4 P]... [--antispoof-v6 P]... [--flowtable DEV]... [--probation 2m] [--auto-confirm on\|off] [--stats-per-device on\|off]` | lists given replace the old ones; `none` empties one (`--flowtable none`: off) |
### probation, history, import
| Command | |
|---|---|
| `confirm` | keep the changes on probation; the ruleset is saved for the boot |
| `rollback` | undo the changes on probation now |
| `history list [--limit N]` | changes, newest first |
| `history show ID` | each object before and after (a changed setting by its own fields: `builtins.drop_invalid: false -> true`) |
| `history diff ID` | what the change did to the ruleset |
| `history undo ID` | apply the change backwards (refused if something it touched has changed since) |
| `import [FILE\|-] [--text FILE] [--defines FILE] [--table T] [--zone IFACE=NAME]... [--replace]` | [import.md](import.md) |
| `takeover TABLE... [-y]` | delete tables Housecarl's ruleset replaces (administrators); not in shadow mode |
### cert
Administrators only ([certificates.md](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 (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](permissions.md) explains roles and grants.
## housecarld
The daemon's own commands work on the database directly, as root or the
`housecarl` user (`sudo -u housecarl housecarld ...`):
| Command | |
|---|---|
| `housecarld serve` | run (the systemd unit does) |
| `housecarld init [-u NAME] [--token]` | create the database and the first administrator (asks for the name on a terminal) |
| `housecarld user add NAME [--admin]`, `user password NAME`, `user token NAME TOKEN-NAME` | when the API can't be used |
| `housecarld check` | check the configuration against this machine's interfaces |
| `housecarld backup [FILE]`, `housecarld restore FILE` | [operations.md](operations.md) |
| `housecarld config [--example]` | the settings in use, or a commented example |
| `housecarld version` | |
All take `-c FILE` for another configuration file.