CLI Reference
Complete command reference for the ezyshield CLI.
Global conventions
Exit codes
Every command follows the same exit-code contract:
| Code | Meaning |
|---|---|
0 | Success |
1 | Runtime error — the command started but failed (invalid config, API error, write failure) |
2 | Usage error — unknown command/flag, bad argument, or an input file that does not exist / cannot be read |
3 | Daemon unreachable — the control socket refused the connection (is the daemon running?) |
Two deliberate exceptions: status exits 0 even when the daemon is stopped (it successfully reports the state), and doctor exits 0 even when individual checks fail (its output is the report).
JSON output (--json)
Every read command supports --json with stable field names, safe to script against:
| Command | Shape |
|---|---|
status | Object: daemon, enforcer, mode, uptime, version, active_bans, bans_by_strike, message |
list | Envelope: ok, error, data (rows under data) |
report <ip> | Object: versioned abuse report (schema_version, ip, country, asn, current_ban, strikes, actions, plus evidence with --evidence) |
report | Array of offender summaries (ip, first_seen, last_seen, total_strikes, banned, permanent, country, asn) |
watch | NDJSON: one event object per line |
scan | Object: listeners, new_listeners (each a socket, all fields of the scan.Listener struct: Addr, Protocol, UID, Inode, PID, ExePath, UserName, IsPublic, OwnerType, UnitName, ContainerID, ContainerName, ContainerImage, LogSource) |
doctor | Object: checks (name, status, hint) and summary (total, pass, fail, warn) |
config show | Object: config, policy (effective values, secrets redacted) |
version | Object: version, commit, build_date |
With --json, stdout carries only JSON; warnings and connection notices go to stderr, so piping into jq is always safe.
Color
Colored/styled output is enabled only when all of these hold: stdout is an interactive terminal, the NO_COLOR environment variable is unset, and --no-color was not passed. Piped or redirected output is always plain text, so ezyshield watch | grep ban never sees escape codes.
ezyshield init
Interactive setup wizard. Configures log sources, enforcement backends, AI providers, and notifications.
sudo ezyshield initCreates /etc/ezyshield/config.yaml and /etc/ezyshield/policy.yaml with secure permissions (0600).
The wizard walks through named sections — Environment (what was detected on the host), Collectors, Allowlist, Edge enforcers, AI analysis, Policy, Files, and System services — with ✓/✗/! status marks per line. Styling follows the global color conventions; piped output stays plain.
When Docker is detected, the Environment section enumerates the docker bridge network subnets that actually exist on the host and allowlists only those — never a blanket RFC1918 range. If enumeration fails, it falls back to Docker's default bridge subnet (172.17.0.0/16) alone and prints a ! warning. Hosts without Docker get no docker-related allowlist entry at all. See the allowlist section in Policy Reference for the trade-off if you want to broaden this deliberately, and re-run ezyshield doctor afterwards — it warns on any private allowlist entry /16 or broader.
At the end it prints a Summary section:
- what was configured (collectors, enforcers, AI) and what was skipped, with the reason;
- every file written (including the
.envthat holds secret tokens, mode 0600 — tokens never go intoconfig.yaml); - the current mode (
DRY-RUNby default — nothing is blocked until you setarmed: trueinpolicy.yaml); - numbered next steps (
doctor,status,watch).
The summary complements — never replaces — warnings printed during the run, such as the loud banner shown when Cloudflare enforcer setup aborts.
Flags:
--yes— non-interactive: accept every default, skip CDN detection.--config-dir <dir>— write files to a different directory; skips systemd unit installation and service start (next steps then use foregroundrun).--non-interactive,-n— scripted setup (no TTY), driven by--answersand/or the override flags below. Produces the same validated config the wizard produces (alwaysarmed: false). See the unattended install guide for the answers-file schema and Ansible/cloud-init examples.--answers <path>— YAML answers file (implies--non-interactive; flags override file values). Unknown keys are rejected.--force— overwrite an existingconfig.yaml/policy.yaml(non-interactive only; without it a re-run refuses, same as the wizard).--admin-ips,--monitor-ssh,--enable-ai,--ai-provider,--ai-model,--ai-key-env— per-answer overrides for the non-interactive path.--ai-key-envtakes an env var NAME, never the key itself; a literal secret in any flag or answers-file value is rejected.--json— with--non-interactive, emit the summary as JSON on stdout (progress goes to stderr).
ezyshield run
Start the daemon in the foreground. Reads logs, makes decisions, enforces bans.
sudo ezyshield run| Flag | Default | Description |
|---|---|---|
--config | /etc/ezyshield/config.yaml | path to config.yaml |
--policy | /etc/ezyshield/policy.yaml | path to policy.yaml |
--db | /var/lib/ezyshield/ezyshield.db | path to the SQLite database |
--socket | /run/ezyshield/ezyshield.sock | control socket path |
Runs in dry-run mode by default (armed: false in policy.yaml).
ezyshield watch
Stream live security events from the running daemon: detections, strike escalations, bans, dry-run bans, unbans, and allowlist changes. This is a live view — for a point-in-time snapshot of active bans, use list.
# Stream everything
ezyshield watch
# Only bans and dry-run bans
ezyshield watch --kind ban,dry_ban
# Only events for one address or CIDR block
ezyshield watch --ip 203.0.113.0/24
# NDJSON: one JSON object per line, for jq or a log shipper
ezyshield watch --json | jq .kindFlags:
--kind— filter by event kind:detection,record,notify_only,dry_ban,ban,already_banned,unban,allow(repeatable or comma-separated)--ip— filter by IP address or CIDR block--socket— daemon control socket path
Each event carries a timestamp, kind, IP, and context fields (score, category, rule, strike, TTL, enforcer, reason, source). Event text derived from log lines is sanitized before display — ANSI escape sequences and control characters are stripped so hostile log content cannot spoof your terminal.
If the daemon connection drops (e.g. a restart), watch reconnects automatically with backoff. Press Ctrl-C to exit. The daemon must be running (ezyshield run or sudo systemctl start ezyshield).
ezyshield arm
Arm enforcement after a mandatory pre-flight (issue #228). The daemon flips from dry-run to live blocking; the transition is persisted to policy.yaml and audited — no config editing, no restart.
sudo ezyshield arm [--for 1h] [--keep] [--force]The pre-flight reports pass/warn/fail for: enforcer configured, admin_cidrs and allowlist coverage, a self-ban simulation for your own SSH client IP, and recent dry-run activity. Failing checks refuse the transition.
| Flag | Meaning |
|---|---|
--for <dur> | Arm temporarily (1m–7d): unless confirmed with --keep, the daemon reverts to dry-run when the window expires and notifies. The revert is daemon-side — it survives losing your session. |
--keep | Confirm the active window; armed becomes unconditional |
--force | Override failing checks — except the self-ban check, which is never bypassable |
--socket | Daemon control socket path |
ezyshield status shows the auto-revert deadline while a window is active.
ezyshield disarm
Return to dry-run mode. No pre-flight — moving toward dry-run is always the safe direction. Persisted to policy.yaml and audited.
sudo ezyshield disarmezyshield status
Show daemon and enforcer status.
ezyshield status
# JSON output
ezyshield status --json| Flag | Description |
|---|---|
--socket | daemon control socket path override |
--enforcer-socket | enforcer socket path override |
Output:
- Daemon and enforcer reachability
- Mode (enforce / dry-run), uptime, version
- Active bans total and per-strike breakdown
ezyshield list
List active bans (default) or the allowlist.
# Active bans
ezyshield list
# Grouped by country / by ASN
ezyshield list --by-country
ezyshield list --by-asn
# Allowlist entries
ezyshield list --allow
# Historical action log (bans, expiries, unbans, allows) — newest first
ezyshield list --audit
ezyshield list --audit --ip 203.0.113.42
ezyshield list --audit --limit 50
# JSON output (works with --audit too)
ezyshield list --json| Flag | Description |
|---|---|
--allow | list allowlist entries instead of bans |
--by-country | aggregate bans by country (requires GeoIP enrichment) |
--by-asn | aggregate bans by ASN (requires GeoIP enrichment) |
--audit | show the historical action log instead of active bans |
--ip | with --audit: filter the history to one IP address |
--limit | with --audit: maximum rows to return (default 100) |
--socket | control socket path override |
Ban columns: IP / STRIKE / TTL / COUNTRY / ASN / REASON. Allowlist columns: IP/CIDR / EXPIRES / REASON. Audit columns: TIME / IP / ACTION / STRIKE / TTL / REASON (newest first; TTL is perm for a permanent ban, - for actions that carry none).
list --audit shows only the audit trail (timestamps, actions, strikes, reasons). For a single offender's full history with detection verdicts and evidence, use ezyshield report.
ezyshield report
Generate a complete abuse report for one offender IP from the daemon's records: identity and enrichment (country, ASN), the current ban, the full strike history with detection verdicts, and the action trail. Without an IP, list every offender on record.
# Full report for one IP (terminal text)
ezyshield report 203.0.113.7
# Markdown document, ready to attach to an abuse@ complaint
ezyshield report 203.0.113.7 -o md > abuse-203.0.113.7.md
# Same, including raw log excerpts mentioning the IP as evidence
ezyshield report 203.0.113.7 --evidence -o md > abuse-203.0.113.7.md
# Machine-readable (versioned schema, safe to script against)
ezyshield report 203.0.113.7 --json
# List all offenders on record / only permanently banned ones
ezyshield report
ezyshield report --permanentFlags:
-o, --output— output format:text(default) ormd(markdown abuse report; requires an IP)--evidence— include raw log excerpts mentioning the IP, extracted on demand from the daemon's configured log sources (requires an IP). File sources are scanned directly, journald sources throughjournalctl, and docker sources through the Docker Engine socket. Excerpts are bounded (most recent window, 50 lines per source) and never persisted; a source that cannot be read (log rotated away, journal empty, engine socket unreachable, container removed) degrades to an explanatory note instead of failing the report--permanent— listing mode: only offenders with a permanent active ban--limit— max strike/action rows (0 = server default of 100)--no-footer— omit the "Generated by EzyShield" footer from markdown output--socket— daemon control socket path
The report is read-only and works in both enforce and dry-run modes. Fields derived from log lines (reasons, categories) are sanitized before display — ANSI escapes and control characters are stripped, and markdown table cells are escaped — so hostile log content cannot spoof your terminal or break the document. Evidence excerpts are rendered as indented code blocks in markdown, so a log line cannot inject formatting into the report. Timestamps are UTC (RFC 3339).
ezyshield ban
Manually ban an IP or CIDR.
Manual bans pass the same safety guards as automatic decisions (issue #211): a target that overlaps the allowlist, admin_cidrs, or a runtime allow entry is refused; a target covering an active SSH session (yours included — the CLI forwards your client IP) is refused; and manual bans count against max_bans_per_minute. Refusals name the guard, exit non-zero, and are recorded in the audit log as ban_refused. There is no override — allowlist and anti-lockout are hard rules, and the rate-limit knob is the policy's max_bans_per_minute.
# Ban permanently
sudo ezyshield ban 203.0.113.42
# Explicit duration
sudo ezyshield ban --ttl 24h --reason "abuse report" 203.0.113.42
# Ban a subnet
sudo ezyshield ban 203.0.113.0/24| Flag | Description |
|---|---|
--ttl | ban duration (5m, 24h, 7d); empty = permanent |
--reason | free-text reason stored in the audit log |
--socket | control socket path override |
Manual bans bypass the rule engine, not the allowlist — an allowlisted IP can never be banned, manually or otherwise (safety invariant: allowlist always wins).
ezyshield unban
Remove an active ban.
sudo ezyshield unban 203.0.113.42
# Unban a subnet
sudo ezyshield unban 203.0.113.0/24Does not delete audit history. (--socket overrides the control socket path.)
ezyshield allow
Add an IP or CIDR to the runtime allowlist.
# Add IP (permanent)
sudo ezyshield allow 192.0.2.100
# Add CIDR
sudo ezyshield allow 192.0.2.0/24
# Temporary entries
sudo ezyshield allow --for 2h --reason "vendor maintenance" 198.51.100.7
sudo ezyshield allow --until 2026-08-01T00:00:00Z 198.51.100.8| Flag | Description |
|---|---|
--for | relative expiry (e.g. 2h, 7d); mutually exclusive with --until |
--until | absolute expiry (RFC 3339 timestamp) |
--reason | free-text reason stored with the entry |
--socket | control socket path override |
Allowlist is checked first. No rule can ban an allowlisted IP.
ezyshield unallow
Remove an IP or CIDR from the runtime allowlist.
sudo ezyshield unallow 192.0.2.100
sudo ezyshield unallow --reason "office moved" 192.0.2.0/24| Flag | Description |
|---|---|
--reason | free-text note recorded in the audit log |
--socket | control socket path override |
The target must match the stored entry exactly (the same IP or CIDR that was allowed). Only runtime entries (added with allow) can be removed; entries from the static config allowlist require a config edit and daemon restart. Removing an entry also drops it from the enforcer's @allowed set.
ezyshield scan
Discover every listening TCP socket on the host and detect drift against the previous baseline. This is a read-only reconnaissance command — it answers "what is exposed, which process owns it, and where are its logs?" and never bans.
# Scan and print the listener table
sudo ezyshield scan
# Machine-readable output
sudo ezyshield scan --jsonFor every socket in the LISTEN state (parsed from /proc/net/tcp and /proc/net/tcp6) it resolves the owning PID and binary, the user, the owner (systemd unit or Docker container), and a log source. Results are stored as a baseline in the SQLite database; on later runs, listeners that were not in the previous baseline are flagged [NEW]. A public listener with no resolvable log source is reported as ⚠ no logs, so an internet-facing service that EzyShield cannot watch stands out.
| Flag | Default | Description |
|---|---|---|
--db | /var/lib/ezyshield/ezyshield.db | path to the SQLite database that holds the baseline |
Table columns: PROTO / ADDR:PORT / PID / BINARY / USER / OWNER / UNIT/CONTAINER / LOG SOURCE.
Run as root. With the default --db path the command creates and reads the database under /var/lib/ezyshield, which needs privileges — so an unprivileged run hard-fails there unless you point --db at a writable location. Given a writable database, per-socket metadata you lack permission to resolve (the binary, owner, and log source of processes you do not own) then degrades gracefully — e.g. PID 0, empty binary — rather than failing the scan. The command follows the global exit-code contract; with --json it prints only JSON on stdout, with the newly detected sockets also collected under new_listeners.
ezyshield doctor
Validate config, permissions, and log sources.
sudo ezyshield doctor| Flag | Default | Description |
|---|---|---|
--config-dir | /etc/ezyshield | configuration directory to check |
--db | /var/lib/ezyshield/ezyshield.db | database for the read-only ban_ineffective check |
Checks:
config.yaml / policy.yaml exist, parse, and have safe permissions/ownership
nftbinary presentjournald readable
enforcer socket reachable
docker socket present (when Docker collectors are configured)
.envsecret file permissionsallowlist breadth: WARN (not FAIL) when
policy.yaml's allowlist contains a private (RFC1918/ULA) range at/16or broader — such a range can never be banned, so it silently exempts a large chunk of address space from enforcement forever. See the allowlist section in Policy Reference.ban_ineffective diagnostics: FAIL when an active ban is flagged ineffective (traffic flowing despite the ban) — names the IPs and points at the systemic remedy (edge enforcement / real-IP parsing / enforcer health); WARN when no ban is currently ineffective but some offender was flagged historically; PASS otherwise. Read-only query against the database at
--db.Enforcement state (issue #174) — the honest health of the enforcement path, derived from real enforcer outcomes, not config alone, and re-verified by a periodic reconcile probe (every 5 minutes) so it stays honest on quiet hosts. Shown loudly in text output and as the stable
enforcement_statefield in--json:ACTIVE— armed, enforcer healthy, bans are appliedDRY-RUN— detection running but nothing is enforcedDEGRADED— armed but the enforcer's recent Ban/Sync failed; bans may not be applied (with the failure detail)DISABLED— no enforcer configured; detection only
As a check it is FAIL when armed but the enforcer is DEGRADED (bans not being applied); WARN on DRY-RUN or DISABLED; N/A when the daemon isn't running.
To exercise enforcers and notification channels for real, use ezyshield test enforcer and ezyshield test notifier.
ezyshield config
Inspect and validate configuration.
ezyshield config show
Render the effective configuration — after parsing, strict validation, and defaults — as YAML, or JSON with --json. Secret values never appear in the output: credential fields hold env:VARNAME references by design, and webhook header values (which may carry raw tokens) are shown as <redacted>.
ezyshield config show
# JSON output
ezyshield config show --json
# Non-default file locations
ezyshield config show --config ./config.yaml --policy ./policy.yamlExit codes: 0 rendered, 1 configuration invalid, 2 file not found / unreadable.
ezyshield config validate
Validate config.yaml and policy.yaml without starting the daemon: strict YAML parsing, field constraints, strike-table monotonicity, allowlist CIDRs, and warnings for unreadable log paths or unset env vars.
ezyshield config validate
# Non-default file locations
ezyshield config validate --config ./config.yaml --policy ./policy.yamlThe top-level ezyshield validate is kept as an alias and behaves identically.
Exit codes: 0 valid (may have warnings), 1 errors found, 2 file not found / unreadable.
ezyshield config enforcer <name>
Interactive wizard to add or reconfigure one enforcer on an existing installation — the same prompts and dry token validation the init wizard runs, without regenerating anything else.
sudo ezyshield config enforcer cloudflare- The write is atomic (temp file + rename); the previous file is kept as
config.yaml.bakand the merged configuration is re-validated before anything touches disk. Comments are not carried over — recover them from the.bakif needed. - Secret tokens go to the
.envfile next toconfig.yaml(mode 0600), never intoconfig.yamlitself (api_token: env:CLOUDFLARE_API_TOKEN). - Multiple Cloudflare accounts are supported: with accounts already configured, the wizard asks whether to reconfigure an existing one or add another; each account keeps its own token env var (
CLOUDFLARE_API_TOKEN_<NAME>). See the Cloudflare guide's multi-account section. - On success the command prints the changed keys and next steps (
config validate, restart the daemon). If the wizard aborts, nothing is written.
Available names: cloudflare.
Exit codes: 0 saved, 1 wizard aborted or write failed, 2 config.yaml not found (run init first).
ezyshield config notifier <name>
Interactive wizard to add, reconfigure, or remove one notification channel on an existing installation.
sudo ezyshield config notifier telegram
sudo ezyshield config notifier email
sudo ezyshield config notifier slack
sudo ezyshield config notifier discord
sudo ezyshield config notifier webhook- Each channel asks for its own settings (telegram: chat IDs; email: from/to/SMTP host/port/TLS/username; slack: optional channel override; webhook: optional auth header) plus a severity filter (
info,warn,critical; empty = all). - Credential values — bot tokens, webhook URLs (capability URLs are secrets), SMTP passwords, auth header values — are read with input hidden and offered two ways: paste the value (stored in the
.envfile next toconfig.yaml, mode 0600, merged without touching other lines) or reference an env var you already manage (e.g. from sops/vault) — then the wizard writesenv:YOUR_VARand never touches.env. Secrets never land inconfig.yaml; it only carries references likebot_token: env:TELEGRAM_BOT_TOKEN. - Pressing ENTER at the paste prompt is fine: an existing value in
.envis kept as is; otherwise a placeholder is written for you to fill in later. - For the generic
webhookchannel the auth header value is a secret too:config.yamlgetsAuthorization: env:WEBHOOK_AUTH_HEADERand the daemon resolves the reference at startup. Plain (non-env:) header values in hand-written configs keep working unchanged. - Reconfiguring replaces that channel's entry; shared tuning (
rate_limit_per_minute,dedup_window_sec) and other channels are preserved. To disable a channel, answernat the configure prompt: the wizard then offers to remove the existing entry (default no). Declining leaves the file untouched. - Write semantics match the other wizards: atomic write,
config.yaml.bak, re-validation before saving, changed-keys summary on success. Verify delivery afterwards with the notification test command shown in the next steps.
Available names: telegram, email, slack, discord, webhook.
Exit codes: 0 saved, 1 wizard aborted or write failed, 2 config.yaml not found (run init first).
ezyshield config ai <provider>
Interactive wizard to configure (or switch) the AI provider on an existing installation — the same model and API-key prompts the init wizard runs, without regenerating anything else.
sudo ezyshield config ai anthropic
sudo ezyshield config ai openai
sudo ezyshield config ai ollama- The API key is read with input hidden and offered two ways: paste it (stored in the
.envfile next toconfig.yaml, mode 0600, merged without touching other lines) or reference an env var you already manage (e.g. from sops/vault) — in that case the wizard writesapi_key: env:YOUR_VARand never touches.env. Keys never land inconfig.yaml. - Pressing ENTER at the paste prompt is fine: an existing key in
.envis kept as is; otherwise a placeholder is written for you to fill in later.ollamaruns locally and has no key. - Reconfiguring replaces the provider fields (
provider,model,api_key) but preserves your tuning (ambiguous_band,token_budget_daily). Write semantics matchconfig enforcer: atomic write,config.yaml.bak, re-validation before saving.
Available providers: anthropic, openai, ollama.
Exit codes: 0 saved, 1 write failed, 2 config.yaml not found (run init first).
ezyshield config collector <name>
Interactive wizard to add, reconfigure, or remove one log collector on an existing installation — the same prompts the init wizard runs for that source, without regenerating anything else.
sudo ezyshield config collector sshd
sudo ezyshield config collector nginx
sudo ezyshield config collector apachesshdmanages the journald collector (confirm, then optionally override the systemd unit). Web server names (nginx,apache,traefik,caddy) first ask for the log source:file(host access-log path, default suggested per server) ordocker(container name, reading its stdout).- Reconfiguring replaces the existing entry for that source (matched by parser for web servers, by SSH unit for
sshd) — the wizard never appends duplicates. Setups with several sources for the same server (e.g. two nginx vhost logs) are edited inconfig.yamldirectly. - To disable a source, answer
nat the configure prompt: the wizard then offers to remove the existing entry (default no). Declining leaves the file untouched. - Collectors carry no secrets; everything stays in
config.yaml. Write semantics match the other wizards: atomic write,config.yaml.bak, re-validation before saving, changed-keys summary on success.
Available names: sshd, nginx, apache, traefik, caddy.
Exit codes: 0 saved, 1 wizard aborted or write failed, 2 config.yaml not found (run init first).
ezyshield config enrich maxmind
Interactive wizard to set up (or remove) GeoIP/ASN enrichment with the free MaxMind GeoLite2 databases — the workflow that enables block_countries / block_asns in policy.yaml and the country/ASN columns in list and report.
sudo ezyshield config enrich maxmind- Asks for the two database paths (defaults under
/var/lib/ezyshield/) and whether the daemon should keep them updated (auto_update, default yes). - With
auto_updateon, the wizard asks for your MaxMind license key (free GeoLite2 signup) via the standard secret prompt: paste it (stored in.envnext toconfig.yaml, mode 0600) or reference an env var you already manage —config.yamlonly ever carrieslicense_key: env:MAXMIND_LICENSE_KEY. On the next daemon start the databases are downloaded automatically if missing, then refreshed weekly. - With
auto_updateoff no key is needed: downloadGeoLite2-Country.mmdbandGeoLite2-ASN.mmdbfrom your MaxMind account yourself and place them at the configured paths. Missing files are not an error — the daemon runs with empty enrichment until they appear. - To disable enrichment, answer
nat the configure prompt: the wizard then offers to remove the existingenrich:section (default no). - Write semantics match the other wizards: atomic write,
config.yaml.bak, re-validation before saving, changed-keys summary on success.
Available names: maxmind.
Exit codes: 0 saved, 1 wizard aborted or write failed, 2 config.yaml not found (run init first).
ezyshield update
Self-update the binaries from GitHub Releases. checksums.txt is signature-verified with cosign against the pinned release-workflow identity (see Verifying releases), then each binary is checksum-verified. Signature verification is fail-closed: a missing checksums.txt.sig/.pem, a missing cosign binary, or a failed verification aborts the update and nothing is installed. Pass --allow-unsigned to proceed without a signature (missing signature assets — e.g. a pre-signing release — or no cosign on the host); a failed verification of a present signature always aborts, with or without the flag.
# Check whether a newer release exists
sudo ezyshield update --check
# Update to the latest stable
sudo ezyshield update
# Update/downgrade to a specific version
sudo ezyshield update --version v0.1.0--version is also the official rollback path: when the tag is older than the running version, the command warns you — the database schema is never reverted, so keep a backup — and asks for confirmation ([y/N], default no). Pass --yes (-y) for unattended rollbacks; without it, a non-interactive run refuses the downgrade.
If you installed via apt/dnf, prefer the package manager instead (see the install guide).
ezyshield dashboard
Serve the localhost-only web dashboard. Full reference (auth, pages, remote access): dashboard.md.
| Flag | Description |
|---|---|
--config | path to config.yaml |
--addr | bind address override (loopback only — non-loopback is refused) |
--auth-db | auth database path override |
--socket | daemon control socket path override |
ezyshield completion
Generate shell completion scripts (bash, zsh, fish, powershell):
ezyshield completion zsh > "${fpath[1]}/_ezyshield"ezyshield version
Show version info.
ezyshield version
# JSON output
ezyshield version --jsonezyshield test
Run connectivity tests against configured components. Like config, the group follows the <kind> <name> pattern, so future component kinds plug into the same verbs.
ezyshield test enforcer <name>
Test the configuration and permissions of an enforcer backend: token validity, account/zone access, and the exact API permissions the enforcer needs — with a fix suggestion for every failing check.
sudo ezyshield test enforcer cloudflare
# Test all configured enforcer backends
sudo ezyshield test enforcer allAvailable names: all, cloudflare, nftables.
Exit code is 0 if all checks pass, non-zero if any check fails.
ezyshield test notifier <name>
Send a synthetic alert to verify a notification channel end to end (secrets resolved from the environment, message actually delivered).
sudo ezyshield test notifier telegram
# Test all configured channels
sudo ezyshield test notifier allAvailable names: all, email, telegram.
Exit code is non-zero on failure.
Deprecated aliases
The pre-1.0 verbs test-enforce <name> and test-notify <name> keep working as hidden aliases of test enforcer / test notifier — same flags, same behavior — and print a one-line migration notice on stderr. They will be removed in 1.0.
Global flags
| Flag | Description |
|---|---|
--json | Output as JSON (see Global conventions for shapes) |
--no-color | Disable colored output (the NO_COLOR env var is also honored) |
-h, --help | Show help text |
--config / --policy are not global — they exist on the commands that read those files (run, config show, validate, dashboard), with defaults under /etc/ezyshield.
Root-command-only flags
| Flag | Description |
|---|---|
-v, --version | Print version and exit |
Unlike --json/--no-color, -v/--version is wired only on the root ezyshield command and is not inherited by subcommands — ezyshield ban --version fails with unknown flag: --version. Use ezyshield version (or ezyshield --version) to check the version.
Examples
Monitor daemon activity live:
ezyshield watch --kind ban,dry_banExport per-IP history with evidence to JSON:
ezyshield report --json > report.jsonCheck if an IP is currently banned:
ezyshield list --json | jq '.[] | select(.ip == "203.0.113.42")'Permanently ban a botnet subnet:
sudo ezyshield ban --ttl 0 203.0.113.0/24Add your office to allowlist:
sudo ezyshield allow 192.0.2.0/24