Daily Use

CLI

obserae-cli is the admin client. It talks to the daemon over a Unix socket — no network port, no authentication beyond filesystem permissions on the socket itself.

obserae-cli [--socket PATH] <command> [flags]

Connecting to the daemon

By default, obserae-cli looks for /var/run/obserae.sock. For local development or non-standard paths, pass --socket:

obserae-cli --socket ./data/obserae.sock status

The easiest way to avoid repeating the flag is a shell alias:

alias obserae-cli='obserae-cli --socket /var/lib/obserae/run/obserae.sock'

Getting help

Every command and verb is self-documenting, and help is printed locally — no running daemon required:

obserae-cli                  # list every command
obserae-cli --help           # same (also: -h, help)
obserae-cli rule --help      # a group's verbs (also: rule -h, rule help)
obserae-cli rule add --help  # a verb's flags

A mistyped command or verb prints the relevant usage and exits non-zero, so a typo never fails silently.


Command map

CommandPurpose
statusFull daemon status snapshot (same payload as GET /api/status)
configBulk import / export / validate the WHOLE configuration (one YAML bundle)
masterkeyExport / rotate the at-rest master key (base64)
networkCRUD on networks
hostCRUD on hosts
interfaceCRUD on host interfaces (always --host-scoped)
serviceCRUD on host services (always --host-scoped)
groupCRUD on groups (members are hosts and/or other groups)
ruleCRUD on individual detection rules
matchesRead-only view of detection matches
alertTriage fired detection alerts: ls/show/ack/close/rm
outputManage alert delivery targets (webhook/Gotify): CRUD, test, deliveries
exporterLabel discovered NetFlow exporters and trigger a rescan
enrichmentIP enrichment: master toggle, per-source feeds, manual refresh
userManage GUI users, groups, API tokens; reset the admin password
ldapConfigure LDAP / Active Directory sign-in: show / set / test
queryRun an NFQL pipeline against the daemon
psLive database activity (in-flight ops + writer queue)
retentionShow the data-retention policy + last sweep, or run one now
backupSnapshots: status, run, list, preview and apply a restore

Most read commands accept --json for machine output. Most rm commands accept --yes (skip prompt) and --dry-run (preview only).


status

obserae-cli status [--json]

The full daemon status snapshot. This prints exactly the payload the authenticated GET /api/status endpoint returns — one shared snapshot for both the CLI and the API. Useful as a smoke test or a monitoring datapoint. Add --json for the raw machine-readable form.

version:                         v1.2.0
commit:                          fe2bc84
data version:                    12
started:                         2026-04-29 08:12:33 UTC
uptime:                          1h2m22s

networks:                        5
hosts:                           11
services:                        23
groups:                          6

rules:                           10
expansions:                      1462
alert rules active:              4
coverage 1h:                     98.7%
violations closed 1h:            12

flows:                           1284091
flows/sec:                       42.0
templates received:              2
packets awaiting template:       0

sessions active (RAM):           124
sessions half-open (RAM):        3
sessions closed:                 8945
sessions open (live):            12345 / 500000 (2.5%) [ok]
sessions evicted:                0
sessions dropped (closes/emits): 0 / 0
enrich LRU:                      412345 / 1000000 (41.2%)

alerts total:                    37
alerts 1h / 24h:                 2 / 15
alerts by status:                ack=5, closed=20, new=12
alerts by severity:              high=8, info=20, medium=9

files written:                   9021
records ingested:                1284091
last flush:                      2026-04-29T09:14:51Z
flush ms (avg/max):              12.4 / 88

db size:                         3.1 GB
disk free / total:               812.4 GB / 1.0 TB
buffer dir:                      4.2 MB (3 files)
backup dir:                      1.9 GB (14 files)

heap in-use / sys:               210.5 MB / 512.0 MB
goroutines:                      148
udp drops:                       0
matcher cursor lag:              2s
audit integrity:                 ok (0 breaks)

templates received (templates_received) is the number of NetFlow v9 templates known by obserae. They are restored at startup, so a restart should not interrupt flow decoding while waiting for the exporter’s next template refresh. packets awaiting template (packets_awaiting_template) is the number of packets that could not be decoded yet. A non-zero value with flows: 0 usually means a brand-new exporter has not sent its templates; force the exporter to refresh them, or wait for its next refresh cycle.

The session/enrich rows are live fill gauges: entry counts against their configured limits, not bytes. sessions evicted counts sessions force-closed under capacity pressure, and enrich LRU shows the enrichment lookup cache against its capacity.

When the web GUI is disabled (web.enabled: false), live health sampling is not running, so rows such as flows/sec, coverage, flush timing, runtime metrics and audit integrity read zero. Counts, session/enrich gauges, storage figures and the alert roll-up stay accurate.


config

Bulk import / export / validate of the whole configuration as one YAML bundle — the CLI pendant of the GUI’s Config I/O page (same file). One top-level key per domain: cartography, flow_matrix, alerting, outputs, enrichment, exporters, backup, retention.

obserae-cli config validate FILE     # checks only, no write
obserae-cli config import   FILE     # apply the bundle
obserae-cli config export   [--output FILE]

Use - for FILE to read stdin / write stdout. A section present in the file replaces that domain; an absent section is left untouched. The bundle is validated fully before any write, then applied in dependency order (cartography first). import prints what was applied / kept / warned.

The exported file holds secrets in clear text (output tokens / HMAC keys) — keep it safe. The old per-domain bulk commands (cartography, rules import/export) were folded into this one; per-entity commands below remain.


masterkey

Export or rotate the single at-rest master key (masterkey.bin). One key protects every stored secret (alert credentials, device API secrets, session keys) and the audit-seal authenticity — everything is HKDF-derived from it.

obserae-cli masterkey export [--output FILE]   # print the key as base64
obserae-cli masterkey import FILE              # rotate to a base64 key (- for stdin)

Export it to your secret manager. import rotates the key: every secret is re-encrypted and every audit seal re-signed under the new key, live — no restart. (GUI equivalent: Config I/O → Master key.)

Treat the exported key like a root credential. To restore onto a rebuilt instance instead, place the backed-up masterkey.bin (mode 0400) in the data directory before the first boot.


Per-entity CRUD: network, host, interface, service, group

All five entities share the same five verbs (add, ls, show, update, rm).

Networks

obserae-cli network add prod-vlan20 --cidr 10.20.0.0/16 [--vlan 20] [--description S]
obserae-cli network ls               [--json]
obserae-cli network show NAME        [--json]
obserae-cli network update NAME [--name N] [--cidr X] [--vlan V] [--description S]
obserae-cli network rm   NAME        [--yes] [--dry-run]

Hosts

obserae-cli host add NAME
obserae-cli host update NAME --name NEW
obserae-cli host rm NAME             [--yes] [--dry-run]

Interfaces (scoped under a host)

obserae-cli interface add eth0 --host srv-db --network prod-vlan20 --ip 10.20.0.5
obserae-cli interface ls --host srv-db
obserae-cli interface update NAME --host H [--name N] [--network NET] [--ip IP]
obserae-cli interface rm     NAME --host H [--yes] [--dry-run]

Services (scoped under a host)

obserae-cli service add postgres --host srv-db \
    --protocol TCP --port 5432 --interfaces eth0,eth1 [--description S]

obserae-cli service ls --host srv-db
obserae-cli service update NAME --host H \
    [--name N] [--protocol P] [--port N] [--interfaces a,b] [--description S]
obserae-cli service rm  NAME --host H [--yes] [--dry-run]

Groups

obserae-cli group add backend [--members a,b,c]
obserae-cli group ls    [--json]
obserae-cli group show backend
obserae-cli group update backend [--name N] [--members a,b]        # replace member list
obserae-cli group update backend [--add-member X] [--rm-member Y]  # incremental
obserae-cli group rm     backend [--yes] [--dry-run]

Delete-with-preview

Every rm first asks the daemon what would cascade, prints a summary, then prompts:

$ obserae-cli network rm admin
Deleting network admin will:
  - delete 11 entities
  - modify 0 entities
Proceed? [N/y/I]
  • N (default) aborts.
  • y commits.
  • I lists every affected entity, then re-prompts.
  • --yes skips the prompt entirely (scripts).
  • --dry-run prints the impact and never writes.

rule

Per-entity CRUD on detection rules. Bulk import/export of the whole rule set is part of config (the flow_matrix: section). See Detection Rules for the YAML schema.

obserae-cli rule add NAME \
    --src REF --dst REF \
    [--src-service S] [--dst-service S] \
    [--src-iface IF] [--dst-iface IF] \
    [--protocol P] [--description S] \
    [--enabled=BOOL]

obserae-cli rule ls       [--json]
obserae-cli rule show NAME [--json]
obserae-cli rule update NAME \
    [--name NEW] [--src REF] [--dst REF] \
    [--src-service S] [--dst-service S] \
    [--src-iface IF] [--dst-iface IF] \
    [--protocol P] [--description S] \
    [--enabled=BOOL]
obserae-cli rule rm NAME   [--yes] [--dry-run]

obserae-cli rule enable  NAME   [--json]
obserae-cli rule disable NAME   [--json]
obserae-cli rule bulk [--enable a,b,c] [--disable d,e]

enable / disable are shorthands for update --enabled=BOOL: a disabled rule is kept but stops matching traffic. bulk toggles many rules at once (“disable these twelve false-positives”); a name in both lists resolves to disable, and per-rule failures don’t abort the rest.

The protocol is normally carried by --src-service / --dst-service (*/TCP, 53/UDP, or a catalogued service name). --protocol P is an optional legacy override that is reconciled with the service tokens — it must not contradict a protocol pinned by either side. See Detection Rules.

The reference syntax for --src / --dst is described in Cartography.


matches

Read-only view of detection matches.

obserae-cli matches ls \
    [--rule NAME]               # filter by rule
    [--since DURATION|RFC3339]  # only matches newer than this
    [--limit N]                 # cap (default 50)
    [--json]

Examples:

obserae-cli matches ls --since 1h
obserae-cli matches ls --rule public-https --limit 200
obserae-cli matches ls --since 2026-05-01T12:00:00Z
obserae-cli matches ls --json | jq '.[] | select(.protocol == 6)'

Default text output:

<matched_at>  <rule>  <ip_a:port_a> <-> <ip_b:port_b>  proto=<n>  server=<ip:port> (<method>/<conf>)  session=<uuid>

The <-> reflects the non-orientation of a session — endpoints are stored in canonical IP order; the inferred server=… recovers the operationally-meaningful direction.


alert

Triage fired detection alerts — the CLI image of the Detection page. Alerts are addressed by their UUID (ls prints it). Status: newackclosed.

obserae-cli alert ls [--severity S] [--status new|ack|closed] [--rule N] \
    [--since 1h|RFC3339] [--limit N] [--json]
obserae-cli alert show ID [--json]
obserae-cli alert ack   ID
obserae-cli alert close ID
obserae-cli alert rm ID [ID...] [--yes]

--since accepts a duration (1h) or an RFC3339 timestamp. rm accepts several ids and reports how many were deleted (unknown ids are skipped).


output

Manage alert delivery targets (webhook / Gotify) — the CLI image of the Outputs page. Outputs are addressed by name.

obserae-cli output ls [--json]
obserae-cli output show NAME [--json]

# webhook (secret via stdin keeps it out of shell history)
printf '%s' "$SECRET" | obserae-cli output add alerts-hook \
    --type webhook --url https://hooks.example/obserae --secret - \
    [--header "X-Env: prod"]... [--min-severity high] [--rules a,b]

# gotify
obserae-cli output add pager --type gotify \
    --base-url https://gotify.example --secret APP_TOKEN

obserae-cli output update NAME [--url …] [--min-severity …] [--secret -]
obserae-cli output enable|disable NAME
obserae-cli output rm NAME [--yes]
obserae-cli output test NAME                 # send a test delivery now
obserae-cli output deliveries NAME [--limit N] [--json]

Secrets are write-only — ls / show only show a has_secret flag, never the value. Use --secret - to read the secret from stdin. On update, a config flag (--url, --header, …) merges onto the stored config; the secret changes only when --secret is passed. output test sends a synthetic alert right away and exits non-zero on failure. The full output set is also part of the outputs: config-bundle section.


exporter

Label the NetFlow exporters the daemon has observed, and trigger a rescan.

obserae-cli exporter ls [--json]
obserae-cli exporter show IP [--json]
obserae-cli exporter set IP [--name N] [--type T] [--details D]
obserae-cli exporter rescan

Exporters are discovered, not created — a row appears the first time a device sends flows (keyed by sampler_address), so there is no add/rm. set labels an existing exporter; only name, equipment_type and details are editable (the seen/flow-count columns are daemon-owned). Labels are also carried in the exporters: config-bundle section.


enrichment

Drive IP enrichment — the master toggle and the per-source feeds (cloud CIDR ranges, threat-intel lists, GeoIP, ASN) — the CLI image of the Connectors enrichment pages (Cloud Attribution, Threat Intelligence, GeoIP, ASN).

obserae-cli enrichment status [--json]            # master toggle + last change
obserae-cli enrichment enable | disable           # master switch
obserae-cli enrichment sources [--json]           # every feed + its state
obserae-cli enrichment source NAME --enabled=BOOL # toggle one feed
obserae-cli enrichment refresh NAME               # force a fetch now (async)

sources shows each feed’s enabled flag, fetch status, range count and last fetch. refresh enqueues an immediate fetch (poll sources to watch fetching → idle). The enabled flags are also in the enrichment: config-bundle section; refresh is the operational action with no YAML form.


query

Run an NFQL pipeline. See NFQL for the full language.

obserae-cli query [--json] [--arg VALUE]... NFQL
FlagEffect
--jsonEmit a list of objects (one per row) instead of a table.
--arg VBind one ? placeholder. Repeatable; left-to-right order.

--arg V coerces:

  • s:foo → string "foo" (escape hatch for values that look like numbers).
  • decimal → integer.
  • anything else → string.

Examples:

# Plain table
obserae-cli query 'FROM flows | LIMIT 5'

# Bound parameter
obserae-cli query --arg 443 'FROM flows | WHERE dst_port == ? | LIMIT 5'

# Cartography reference as a string
obserae-cli query --arg "host:proxy:eth1" \
  'FROM flows | WHERE src_addr == ? | LIMIT 20'

# Relative time bound (negative = N seconds before now)
obserae-cli query --arg -3600 'FROM flows | BETWEEN ? AND *'

# JSON output for downstream tooling
obserae-cli query --json 'FROM flows | KEEP src_addr, bytes | SORT bytes DESC | LIMIT 10' | jq

# Pivot cascade: signal in one table, drill in another
obserae-cli query \
  'FROM session_matches | LAST 3600
   > FROM sessions | PIVOT session_id == session_id
                   | KEEP ip_a, ip_b, server_ip, role_method'

The default table output decodes a few well-known integer columns into their canonical names (protocolTCP, tcp_flagsSYN,ACK). --json keeps raw numeric values so downstream filters keep working.


ps

A top-like view of current read/write activity. Use it when ingestion, queries, matching or enrichment feel slow and you want to see which operation is busy right now. activity is an accepted alias for ps.

obserae-cli ps                 # one snapshot
obserae-cli ps --watch 1s      # refresh every second (Ctrl-C to stop)
obserae-cli ps --json          # machine-readable
writer pool: in_use=1 open=1  waits since start (cumulative): count=12 total=4.231s

IN-FLIGHT (2)
  POOL    ELAPSED      STATE  OP
  writer  3.120s       RUNNING sessions.correlator.assign_batch
  reader  12ms         running query.nfql

RECENT (8, slowest first)
  POOL    ELAPSED      ERR    OP
  writer  890ms               flows.ingest_batch
  writer  120ms               enrichment.insert_batch
  • IN-FLIGHT lists operations executing now, longest first. The first writer row is flagged RUNNING; if its ELAPSED value keeps climbing, it is the operation currently delaying writes. reader rows are read-only queries such as NFQL or Cockpit views.
  • The waits since start counters are cumulative since the daemon started. They only ever grow, so a large total is not an alarm by itself. Judge contention from the RUNNING operation’s elapsed time. The Cockpit DB activity pane adds a per-interval wait delta that falls back to zero when pressure clears.
  • RECENT is a ring of the last completed operations, slowest first, so a spike that already finished is still visible. marks a failure.

The same data drives the DB activity pane on the Cockpit page.


retention

Read the live data-retention policy and last-sweep result, or trigger a sweep now.

obserae-cli retention status [--json]   # live policy + last-sweep counters
obserae-cli retention run               # trigger a sweep now (async)

Policy changes are declarative — edit the retention: section and obserae-cli config import, or use the Lifecycle page. There is no retention set.


backup

Inspect the snapshot policy and files, trigger a snapshot, or run a point-in-time restore.

obserae-cli backup status  [--json]                # policy + last snapshot + next action
obserae-cli backup run     [--full]                # snapshot now (async)
obserae-cli backup list [--json]
obserae-cli backup plan    --point-in-time RFC3339 [--json]
obserae-cli backup restore --point-in-time RFC3339 [--dry-run] --confirm [--json]

backup run lets the runner decide full-vs-delta (like the GUI “Backup now”); --full forces a full. The backup policy is changed via the backup: config section or the Lifecycle page, not a set verb.

  • list shows every snapshot directory on disk — daily fulls and the chain of deltas that descend from them — with kind, timestamp, size and parent.
  • plan previews the restore chain that lands closest to --point-in-time (the full chosen as base, the ordered deltas, the cumulative size) without touching anything.
  • restore is destructive and refuses to run without --confirm. With --dry-run every step runs except the final swap, so you can validate a chain safely. On a real restore the daemon swaps the live database and exits — your supervisor (systemd, Docker, …) restarts it on the restored file.
# Typical workflow: list, preview, rehearse, apply.
obserae-cli backup list
obserae-cli backup plan    --point-in-time 2026-05-28T13:00:00Z
obserae-cli backup restore --point-in-time 2026-05-28T13:00:00Z --dry-run --confirm
obserae-cli backup restore --point-in-time 2026-05-28T13:00:00Z --confirm

Backup scheduling and retention are configured on the Lifecycle page; the full restore workflow and its exit-after-swap rationale are documented there too.


User & access management

The user group administers GUI accounts, groups, API tokens and the admin password — the same model as the GUI Users page, usable headless (e.g. to recover access).

Multiple users and RBAC are a planned Enterprise feature — free during the alpha (see Licensing & transparency). Single-admin recovery below is part of the core product.

# Recover access: generate a fresh admin password (printed once).
obserae-cli user reset-admin-password
# Or set a chosen one (also accepts the password on stdin).
obserae-cli user set-admin-password --password 's3cret'

# Users.
obserae-cli user ls
obserae-cli user add --username alice --password 's3cret' --display "Alice" --groups analyst
obserae-cli user rm alice

# Groups (built-in admin/analyst/auditor/monitoring cannot be changed).
obserae-cli user group-ls
obserae-cli user group-add --name soc --perms cartography:read,sessions:read,alerts:read,alerts:ack
obserae-cli user group-rm soc          # only if it has no members

# API tokens (the secret is printed once; use it as a Bearer credential).
obserae-cli user token-add --name ci --user alice
obserae-cli user token-ls
obserae-cli user token-rm <TOKEN_ID>   # revoke

A token authenticates REST calls against the web API:

curl -H "Authorization: Bearer obs_…" http://127.0.0.1:8080/api/query …

The full permission catalogue (cartography:read, rules:write, nfql:execute, users:manage, …) is described on the Users page.

LDAP / Active Directory (ldap)

Configure directory sign-in headless — the same settings as the Settings → Authentication page. Full guide: Authentication.

obserae-cli ldap show     # current config (bind password hidden)
obserae-cli ldap test     # dial the directory and bind the service account
obserae-cli ldap set \
  --enabled --url ldaps://dc.corp:636 \
  --bind-dn 'CN=svc-obserae,OU=Service,DC=corp,DC=local' --bind-password 'REDACTED' \
  --user-base-dn 'OU=Users,DC=corp,DC=local' --user-filter '(sAMAccountName=%s)' \
  --map 'CN=SOC-Admins,OU=Groups,DC=corp,DC=local=>admin' \
  --map 'CN=Analysts,OU=Groups,DC=corp,DC=local=>analyst'

set changes only the flags you pass; --map 'AD_GROUP=>ROLE' is repeatable and replaces the whole mapping list. The local admin stays a break-glass login even when LDAP is on.


Common errors

SymptomCauseFix
dial unix /var/run/obserae.sock: connect: no such file or directoryDaemon not running, or wrong pathStart the daemon, or pass --socket
daemon: rule "X": not foundTypo or rule deletedobserae-cli rule ls for the current set
daemon: name "X" is used by both host and groupCartography name collisionPick distinct names (one global namespace)
sem: 1:18: unknown column "prtocol"NFQL typoLook at line:column for the offending token
sem: 1:32: host "srv-typoo": not foundCartography reference doesn’t resolveFix the name in the query or the cartography

For NFQL-specific errors, see NFQL.