Usage

Everything goes through the ereshkigal CLI, which talks to the manager socket. The global -s <path> option points it at a non-default socket and works with every subcommand...

ereshkigal -s /var/run/ereshkigal/socket status

Data commands print their result as pretty JSON and exit 0; errors print the server's error text and exit nonzero, so the CLI scripts cleanly.

Every subcommand takes --help (or -h), which explains what it does, what it will do to the firewall, and the shape of its options — ereshkigal help <command> prints the same thing. Read ereshkigal unban --help and ereshkigal clear-retries --help before reaching for either; both can undo a lot of blocking in one go.

Raising and quieting the underworlds

ereshkigal start                 # read the config, daemonize, raise every kur
ereshkigal start --foreground    # same, staying attached (supervisors, testing)
ereshkigal start --config /etc/e.toml
ereshkigal stop                  # stop every kur (tearing down their firewall
                                 # state) and then the manager

A census of who dwells below

ereshkigal status           # manager uptime, each kur's up/down state and restarts
ereshkigal status --all     # the above plus every kur's full status block
ereshkigal status sshd      # one underworld in detail... uptime, stats, ban counts,
                            # sentence defaults, when the tablets were last copied
ereshkigal banned           # the rolls... every banned IP per kur, with the epoch
                            # each sentence ends (0 = eternal), plus any unbans
                            # still owed to the firewall (see below)

Sending IPs below and calling them back

ereshkigal ban 1.2.3.4 5.6.7.8        # banish to EVERY kur
ereshkigal ban --kur sshd 1.2.3.4     # just the sshd underworld
ereshkigal ban --kur gate 1.2.3.4     # a gate (fan_out kur) sends it to
                                      # every member underworld
ereshkigal ban --ban-time 3600 1.2.3.4  # a one hour sentence
ereshkigal ban --ban-time 0 1.2.3.4     # eternal residence

ereshkigal unban 1.2.3.4    # each kur is checked and the IP released wherever it
                            # is actually held... the response reports was_banned
                            # per kur
ereshkigal unban --all      # empty every underworld (flush)

ban and cidr-ban take --kur to aim at one underworld; unban and cidr-unban never needed the option — they ask every kur and release the address wherever it is actually held. ereshkigal unban --kur sshd is an error, not a narrower unban.

Sentences default per the config layering (request > kur > global > 600 seconds). Banning an IP already below just refreshes its sentence. When a sentence is served, the kur's sweeper releases the IP on its own — no cron jobs needed.

Banishing whole ranges

Whole CIDR ranges can be banished too, once CIDR banning is enabled (see configuration) and the kur's backend can carry ranges.

ereshkigal cidr-ban 1.2.3.0/24 10.0.0.0/8   # banish ranges to EVERY kur
ereshkigal cidr-ban --kur sshd 1.2.3.0/24   # just the sshd underworld
ereshkigal cidr-ban --ban-time 3600 1.2.3.0/24  # a one hour sentence

ereshkigal cidr-unban 1.2.3.0/24            # each kur checked, the range
                                            # released wherever it is held

Ranges are reduced to their network address before being sent below, so 1.2.3.4/24 and 1.2.3.0/24 are the same range and either spelling finds it again for an unban. cidr-ban and its sentences behave exactly like ban, sweeper and all.

There is no cidr-unban --allunban --all already empties every underworld, single IPs and ranges alike.

A kur without CIDR banning available for it (not enabled, or a backend that cannot carry ranges) either answers a plain error or, when its cidr_silent_drop is set, quietly drops the command with dropped:1. That keeps a fan-out across a mix of range-capable and range-incapable underworlds from being spoiled by the ones that cannot oblige.

Raising and tearing down underworlds at runtime

ereshkigal add dns --backend pf --ports 53 --protocols tcp,udp \
    --option kill=1 --ban-time 300     # raise a new kur, now
ereshkigal add edge --backend xdp --interfaces eth0,eth1 \
    --enable-cidr 1                    # array valued options ride --interfaces
ereshkigal add gate --fan-out sshd,smtp  # raise a gate onto existing kurs
                                         # (see configuration.md)
ereshkigal remove dns                  # stop it, tear down its firewall state,
                                       # and deregister it

Neither touches the config file — a kur added at runtime vanishes on the next restart unless you also add it to ereshkigal.toml, and a removed one returns unless you delete it from there.

The tablets

ereshkigal checkpoint          # every kur recopies its ban state CSV now
ereshkigal checkpoint sshd     # just the one

Normally you never need this — the tablets are rewritten on every ban/unban, every checkpoint seconds, and at stop — but it is there for taking a consistent snapshot before backups and the like.

Unbans the firewall would not take

When a sentence runs out but the backend refuses the unban, the kur releases the soul from its books and keeps owing the firewall the removal, retrying with a backoff until it lands. Those debts survive a restart, and status counts them while banned names them:

ereshkigal status --all        # unban_retries per kur, plus how long
                               # the longest owed has been outstanding
ereshkigal banned              # unban_retries names each one, with
                               # times_tried and when it is next due

A debt that will never be paid — the rule removed by hand, or a backend that never had it — is forgiven with:

ereshkigal clear-retries                        # all of them, every kur
ereshkigal clear-retries sshd                   # just that kur's
ereshkigal clear-retries --ip 1.2.3.4           # just that one, everywhere
ereshkigal clear-retries blocklist --cidr 1.2.3.0/24

This only stops the kur asking. Nothing is sent to the firewall, so anything genuinely still banished there stays banished — check before forgiving, or you leave a rule nothing is tracking. If the backend is healthy again, an ordinary unban settles the debt honestly instead, and re-init settles the lot.

Rebuilding a trampled setup

When something outside Ereshkigal drops the rules — a shorewall restart, a pf -F all, a firewalld reload, an ipset flushed by hand, an OpenWrt router rebooted — the kur's book still knows who should be down there. re-init tears the setup down and rebuilds it from that book:

ereshkigal re-init             # every kur rebuilds
ereshkigal re-init sshd        # just the one

Bans are not enforced during the rebuild. It is brief, but on a busy edge pick your moment.

Mostly this is a manual convenience: with self_heal on (the default) each kur already checks its setup before every ban and unban and rebuilds it if it has gone missing. Reach for re-init when you want that now rather than at the next ban, or when self_heal is off.

Communing with Ereshkigal directly

Integrations (log watchers, IDS glue) do not need the CLI. The manager socket speaks newline-delimited JSON: send one object, read one back.

{"command":"ban","args":{"ips":["1.2.3.4"],"kur":"sshd","ban_time":3600}}

If kur names a gate (a fan_out kur), the ban fans out to its members — handy for pointing an integration at one stable name and managing which underworlds it reaches from the config side.

A shell one-liner...

printf '%s\n' '{"command":"ban","args":{"ips":["1.2.3.4"]}}' \
    | nc -U /var/run/ereshkigal/socket

From perl, Ereshkigal::Client handles the framing, timeouts, and — when enable_auth is on — the gate challenge, transparently...

use Ereshkigal::Client;

my $client = Ereshkigal::Client->new(
    socket => '/var/run/ereshkigal/socket',
);

# dies on error responses, returns the result
my $result = $client->call_ok( 'ban',
    { ips => ['1.2.3.4'], kur => 'sshd', ban_time => 3600 } );

# or handle the envelope yourself
my $response = $client->call('status');
if ( $response->{status} eq 'ok' ) { ... }

The commands and their args mirror the CLI exactly:

| command | args | |-----------------|----------------------------------------------------------| | status | none | | status_all | none | | status_kur | {"name":...} | | banned | none | | ban | {"ips":[...]}; kur and ban_time optional | | unban | {"ip":...}, or {"all":true} to flush every kur | | cidr_ban | {"cidrs":[...]}; kur and ban_time optional | | cidr_unban | {"cidr":...} | | add_kur | {"name":..., "opts":{...}} | | remove_kur | {"name":...} | | checkpoint | {"kur":...}, or nothing for every kur | | re_init | {"kur":...}, or nothing for every kur | | clear_retries | {"kur":..., "ip":...} or {"kur":..., "cidr":...}, all optional | | stop | none |

Every reply is either {"status":"ok","result":...} or {"status":"error","error":"..."}.

With enable_auth on, a raw nc integration must complete the auth challenge itself (see security) — using Ereshkigal::Client is much less bother.