Files
abrown38andClaude Opus 5.5 68ba2d06c5 Sets from Jarl: sets holding the addresses of names in Jarl
A set can take its addresses from Jarl: every A and AAAA record of its
names (NAME@VIEW), CNAMEs followed within Jarl's zones in the same view.
housecarld reads them every jarl.refresh and after each change, keeps
only the addresses within the zones and networks the set allows, and
keeps what it last read (saved in the database and the boot ruleset)
while Jarl can't be reached. A change of the addresses applies the
ruleset again without a history entry and is audited (set.jarl).

hc set add --jarl --within, hc set jarl; the kind "from Jarl" and
Addresses… on the Sets page; GET /sets/{name}/jarl and /jarl/refresh;
validation codes set_jarl_*, status warnings jarl_*; docs/jarl.md.
e2e phase with scripts/fakejarl.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 13:51:55 +01:00

14 KiB

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:

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

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). 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):

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

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)
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)
connections [--search TEXT] [--limit N], connections close --search TEXT [-y] tracked connections; closing makes the rules check them again (operations.md); 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)
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)
trace [--address A] [--port N] [--protocol P] [--seconds S] follow packets through every table's rules (operations.md)

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 add NAME --jarl NAME[@VIEW]... --within ZONE|NETWORK... a set from Jarl: the names' addresses, only those within the zones and networks (jarl.md)
set update SET [--entry E]... [--add E] [--remove E] [--timeout] [--jarl N]... [--within W]... [--name] [--comment] --entry replaces the entries, --jarl a Jarl set's names, --within where its addresses may be
set entries SET, set entries add SET ADDRESS [--timeout 1h], set entries remove SET ADDRESS a dynamic set's entries
set jarl SET [--refresh] a set from Jarl: each name's addresses in Jarl (CNAMEs followed), used or why not, and when Jarl answered; --refresh reads Jarl first
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.

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

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.

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.

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
takeover TABLE... [-y] delete tables Housecarl's ruleset replaces (administrators); not in shadow mode

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

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
housecarld config [--example] the settings in use, or a commented example
housecarld version

All take -c FILE for another configuration file.