CLI commands
l2trace ships one binary, l2trace, with several subcommands. The Makefile
wraps the most common ones behind targets so you don’t usually invoke it
directly — but here’s the full surface.
All commands run inside the reconciler container by default:
docker compose run --rm --no-deps reconciler l2trace <subcommand> ...l2trace migrate
Section titled “l2trace migrate”Apply alembic migrations to head.
docker compose run --rm migrate alembic upgrade head# or, equivalently:make migrateThe Make target uses the migrate compose service which is configured
for one-shot alembic runs. Direct invocation of l2trace migrate works
too but goes through the longer-running reconciler image.
l2trace reconcile
Section titled “l2trace reconcile”Start the reconciler and the compactor and the collector
orchestrator with a shared in-memory LiveSet. Long-running. The
default make up starts this as a daemon in the reconciler container.
l2trace reconcileCo-running is not optional: the compactor must invalidate LiveSet entries when it closes valid_during ranges. Splitting them across two processes would silently corrupt the bitemporal log. See the compactor invariant for why.
The orchestrator picks up rows from the device_collector table on
boot and on a ~30s reconfig pass — so anything added/disabled via
l2trace device … takes effect without a process restart.
l2trace device …
Section titled “l2trace device …”Register and manage per-device collector configurations. The
orchestrator inside l2trace reconcile reads this table and (re)spawns
gNMI/SNMP/SSH workers to match. Five subcommands:
device add
Section titled “device add”l2trace device add --hostname HOST --mgmt-ip IP --source SRC \ [--community COMMUNITY | --community-uri URI] [--snmp-port 161] \ [--username USER | --username-uri URI] \ [--password PW | --password-uri URI] \ [--enable-secret PW | --enable-secret-uri URI] [--ssh-port 22] \ [--poll-interval 60] [--vendor VENDOR]| Flag | Default | Description |
|---|---|---|
--hostname | (required) | Switch hostname — unique per install |
--mgmt-ip | (required) | Management IP the collector connects to |
--source | snmp | One of gnmi, snmp, ssh, netconf |
--community / --community-uri | — | SNMPv2c community as cleartext, or as a secrets URI (env://VAR, file:///path#/ptr). Required for --source=snmp |
--snmp-port | 161 | UDP port for SNMP collection |
--username / --username-uri | — | SSH username (cleartext or URI). Required for --source=ssh |
--password / --password-uri | — | SSH password (cleartext or URI). One of --password* OR --ssh-key-file is required for --source=ssh |
--ssh-key-file | — | Absolute path to private key file (alternative to password). Typically a Docker secret mount like /run/secrets/ssh/sw-edge-7-key |
--ssh-passphrase / --ssh-passphrase-uri | — | Passphrase for an encrypted private key (cleartext or URI) |
--enable-secret / --enable-secret-uri | — | Optional privileged-mode password for IOS-XE |
--ssh-port | 22 | TCP port for SSH collection |
--poll-interval | 60.0 | Seconds between polls (FDB + LLDP + chassis-id) |
--vendor | — | Free-text vendor hint. Required for SSH — picks the napalm driver: cisco-ios-xe / cisco-ios / cisco-nxos |
URI-form credential flags are preferred for production. The
URI form stores a reference to the secret (env var name, JSON
pointer into a mounted file, etc.) instead of the value itself —
so pg_dump of the database doesn’t expose credentials. See
Manage collector credentials for the
full URI scheme catalog. Passing both forms for the same field (e.g.
--password and --password-uri) is a user error.
Idempotent. Re-running with the same --hostname updates fields in
place; the orchestrator picks up the change on its next reconfig pass.
You can register the same device twice with different --source
values to get redundant collection (e.g. gNMI primary + SNMP backup,
or SNMP primary + SSH fallback).
For SSH specifically — see How to add an SSH-only switch for the operator walkthrough including a working IOS-XE example.
device list
Section titled “device list”l2trace device listRenders a Rich table showing every registered device, its enabled sources, last successful poll, and the most recent collector error. Quick health check before opening a screenshot session.
device disable / device enable
Section titled “device disable / device enable”l2trace device disable --hostname HOST --source SRCl2trace device enable --hostname HOST --source SRCToggles the enabled flag on the (device, source) collector row.
Keeps history intact — re-enabling is one command. The orchestrator
gracefully stops the worker on the next reconfig pass.
device remove
Section titled “device remove”l2trace device remove --hostname HOST --yesDELETEs the device + cascade-deletes its entire observation history.
Destructive. The --yes flag is required to skip the confirmation
prompt. Most operators want device disable instead — that stops
collection but preserves the bitemporal log for forensic queries.
l2trace trace
Section titled “l2trace trace”Run a one-shot L2 traceroute from the CLI.
l2trace trace --src AA:BB:CC:11:22:33 --dst AA:BB:CC:44:55:66 --vlan 10 \ [--as-of TIMESTAMP|now] [--audit-at TIMESTAMP] [--max-hops 32]| Flag | Default | Description |
|---|---|---|
--src | (required) | Source MAC, any common format |
--dst | (required) | Destination MAC |
--vlan | (required) | VLAN ID, 1..4094 |
--as-of | now | Wire-time anchor: “what was the path at T?” |
--audit-at | current belief | Belief-time anchor: “what did we believe at T?” |
--max-hops | 32 | Loop guard — abort after N hops |
The Make wrapper is:
make trace SRC=... DST=... VLAN=... [AS_OF=...](AUDIT_AT is not in the Make wrapper — pass it via the docker compose
invocation directly.)
l2trace audit-adjacencies
Section titled “l2trace audit-adjacencies”Audit every open LLDP adjacency for one-way / asymmetric observations.
For each A → B row, checks whether B → A exists from the same
source and from any source.
l2trace audit-adjacencies [--source SRC] [--show-healthy]# ormake audit-adjacencies| Flag | Default | Effect |
|---|---|---|
--source | — | Limit audit to one telemetry source (gnmi, snmp, …) |
--show-healthy | off | Include bidirectionally-confirmed rows in output |
Output is a Rich table with one row per open adjacency:
| Symbol | Meaning |
|---|---|
| ✗ no reverse | No peer observation back from any source — likely one-way cable, LLDP disabled on peer, or port down |
| △ source asym | Link is bidirectional, but the same source only sees one direction (telemetry asymmetry, not a cable problem) |
| ✓ | Both directions confirmed by the same source |
A green “all open adjacencies are bidirectional” line means nothing to
investigate. Empty table with --show-healthy off (the default) is also
the all-clear signal. See Audit LLDP adjacencies
for the full operator playbook.
l2trace tui
Section titled “l2trace tui”Launch the Textual TUI operator console.
l2trace tui# ormake tuiThe TUI runs inside the tui compose service (which has TTY allocated
via --service-ports). It needs the postgres and nats services
running.
Four modes accessible from the HOME screen:
| Binding | Mode | What it does |
|---|---|---|
| Ctrl+T | TRACE | L2 traceroute form (src/dst MAC + VLAN) |
| Ctrl+H | HISTORY | MAC bitemporal timeline |
| Ctrl+O | OPS | Live FDB tree + disagreements + quarantine tail |
| Ctrl+U | AUDIT | Bidirectional LLDP audit (same query as audit-adjacencies) |
F1 opens a per-screen help overlay. The ambient as-of timestamp picker at the top of every screen re-fires whichever query is on display, so “what did the fabric look like an hour ago?” is one input change away. See the TUI tour.
l2trace oui-refresh
Section titled “l2trace oui-refresh”Fetch IEEE’s three OUI registries (MA-L / MA-M / MA-S) and UPSERT them
into oui_vendor. Safe to run on a live system — UPSERT, no TRUNCATE.
l2trace oui-refresh# ormake oui-refreshPulls ~5 MB across three HTTPS requests to standards-oui.ieee.org.
~52k rows land per refresh. Recommended cadence: weekly.
l2trace collect (one-shot)
Section titled “l2trace collect (one-shot)”Run a one-off poll cycle for a single (device, source) outside the
orchestrator — useful for debugging a new device config without
waiting for the next reconfig pass.
l2trace collect --device-id ID --source gnmiMost operators want l2trace reconcile (which is what make up
starts) — that runs the orchestrator that owns this lifecycle. Use
collect only when you need a single foreground cycle whose stderr you
can read directly.
Configuration via .env
Section titled “Configuration via .env”Most flags also have an env-var form. The reconciler reads them on start
via Pydantic settings. See .env.example for the full list.
Common ones:
| Variable | Default | Effect |
|---|---|---|
DATABASE_URL | (in compose) | asyncpg URL — set by compose to the in-network postgres |
NATS_URL | (in compose) | NATS URL — set by compose to the in-network nats |
GNMI_AGING_THRESHOLD_SECONDS | 300 | Compactor closes gNMI rows after this much silence |
SNMP_POLL_INTERVAL_SECONDS | 60 | Compactor uses 2 × this as the SNMP aging threshold |
COMPACTOR_INTERVAL_SECONDS | 30 | How often the compactor pass runs |
TUI_THEME | (empty / default) | One of hacker, amber, or empty for terminal default |
See also
Section titled “See also”- Data model reference — what the tables actually look like
- Event envelope reference — the canonical event schema collectors emit