add README
This commit is contained in:
parent
f32ca5c0a1
commit
87f6c770ef
3 changed files with 471 additions and 2 deletions
465
README.md
Normal file
465
README.md
Normal file
|
|
@ -0,0 +1,465 @@
|
||||||
|
# wgctl
|
||||||
|
|
||||||
|
> WireGuard management CLI for Linux — peer lifecycle, RBAC-style access control, live monitoring, and firewall enforcement.
|
||||||
|
|
||||||
|
wgctl started as a simple peer manager and evolved into a complete WireGuard operations tool. It handles everything from adding a guest device to enforcing fine-grained network access policies across groups of peers, with full persistence across restarts.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Peer lifecycle** — add, remove, rename, list, inspect
|
||||||
|
- **Rule system** — iptables-backed access control with inheritance
|
||||||
|
- **Network services registry** — name your LAN services, use them everywhere
|
||||||
|
- **Per-peer and group blocks** — restrict access to specific IPs, ports, subnets, or named services
|
||||||
|
- **Group management** — organize peers, bulk block/unblock with M:N tracking
|
||||||
|
- **Live monitoring** — unified watch showing WireGuard handshakes and firewall drops
|
||||||
|
- **Activity metrics** — per-peer transfer totals and current rates
|
||||||
|
- **Log management** — structured event logs with dedup, filtering, rotation
|
||||||
|
- **Audit** — verify iptables state matches expected configuration
|
||||||
|
- **Interactive shell** — REPL with tab completion and history
|
||||||
|
- **Full persistence** — all state restored on WireGuard restart
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://git.lan.krilio.net/nuno/wgctl
|
||||||
|
cd wgctl/install
|
||||||
|
bash install.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Requires: `bash`, `wireguard-tools`, `python3`, `iptables`, `ulogd2`, `qrencode`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Add a peer
|
||||||
|
wgctl add --name nuno --type phone
|
||||||
|
|
||||||
|
# Add a guest with a rule and group
|
||||||
|
wgctl add --name visitor --type guest --subtype phone --rule guest --group family
|
||||||
|
|
||||||
|
# List all peers
|
||||||
|
wgctl list
|
||||||
|
|
||||||
|
# Show detailed info
|
||||||
|
wgctl inspect --name phone-nuno
|
||||||
|
|
||||||
|
# Show QR code for mobile setup
|
||||||
|
wgctl qr --name phone-nuno
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Peer Management
|
||||||
|
|
||||||
|
```bash
|
||||||
|
wgctl add --name <name> --type <type> [--subtype <subtype>] [--rule <rule>] [--group <group>]
|
||||||
|
wgctl remove --name <name> [--force]
|
||||||
|
wgctl rename --name <name> --new-name <new>
|
||||||
|
wgctl list [--type phone] [--rule user] [--group family] [--online] [--blocked] [--restricted]
|
||||||
|
wgctl inspect --name <name> [--config] [--qr]
|
||||||
|
wgctl config --name <name>
|
||||||
|
wgctl qr --name <name>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Device types:** `desktop`, `laptop`, `phone`, `tablet`, `guest`, `guest-desktop`, `guest-laptop`, `guest-phone`, `guest-tablet`
|
||||||
|
|
||||||
|
Each type maps to a dedicated subnet (`phone` → `10.1.3.0/24`, `guest` → `10.1.100.0/24`, etc.), keeping device families organized.
|
||||||
|
|
||||||
|
**List status values:**
|
||||||
|
|
||||||
|
| Status | Meaning |
|
||||||
|
|--------|---------|
|
||||||
|
| `online` | Connected (recent handshake) |
|
||||||
|
| `offline` | Not connected but in WireGuard server |
|
||||||
|
| `blocked` | Removed from WireGuard — no tunnel possible |
|
||||||
|
| `restricted` | In WireGuard but with specific access blocks applied |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Rule System
|
||||||
|
|
||||||
|
Rules define what a peer can and cannot access. They are enforced via iptables and restored automatically on restart.
|
||||||
|
|
||||||
|
### Rule inheritance
|
||||||
|
|
||||||
|
Rules can extend base rules, composing access policies from primitives:
|
||||||
|
|
||||||
|
```
|
||||||
|
Base rules (building blocks):
|
||||||
|
no-nginx blocks 10.0.0.101
|
||||||
|
no-proxmox extends no-nginx, blocks 10.0.0.100
|
||||||
|
no-truenas extends no-nginx, blocks 10.0.0.200
|
||||||
|
no-admin extends no-proxmox, no-truenas
|
||||||
|
restricted-dns allows DNS:53 to Pi-hole, blocks Pi-hole otherwise
|
||||||
|
no-lan blocks 10.0.0.0/24
|
||||||
|
|
||||||
|
Assignable rules:
|
||||||
|
guest extends no-lan, restricted-dns
|
||||||
|
user extends no-admin, restricted-dns
|
||||||
|
admin no restrictions
|
||||||
|
moonlight-02 extends no-lan, allows 10.0.0.244/32
|
||||||
|
```
|
||||||
|
|
||||||
|
Adding a new admin service only requires updating one base rule — all inheriting rules get the restriction automatically.
|
||||||
|
|
||||||
|
### Rule commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
wgctl rule list # list all rules
|
||||||
|
wgctl rule list --tree # show inheritance tree inline
|
||||||
|
wgctl rule list --group "VM Rules" # filter by group
|
||||||
|
wgctl rule list --base # show only base rules
|
||||||
|
wgctl rule show --name user # show rule with inheritance
|
||||||
|
wgctl rule show --name moonlight-02 --resolved # show merged/resolved entries
|
||||||
|
|
||||||
|
# Create rules
|
||||||
|
wgctl rule add --name no-npm --base --block-ip 10.0.0.101/32
|
||||||
|
wgctl rule add --name restricted-dns \
|
||||||
|
--allow-service pihole:dns \
|
||||||
|
--block-service pihole
|
||||||
|
wgctl rule add --name dev-01 \
|
||||||
|
--desc "Dev VM access" \
|
||||||
|
--group "VM Rules" \
|
||||||
|
--extends no-lan \
|
||||||
|
--allow-ip 10.0.0.50/32
|
||||||
|
|
||||||
|
# Update rules
|
||||||
|
wgctl rule update --name user --add-extends no-nginx
|
||||||
|
wgctl rule update --name dev-01 --allow-ip 10.0.0.51/32
|
||||||
|
|
||||||
|
# Assign to peers
|
||||||
|
wgctl rule assign --name user --peer phone-nuno
|
||||||
|
wgctl rule unassign --peer phone-nuno
|
||||||
|
wgctl rule reapply --name user # re-apply to all assigned peers
|
||||||
|
wgctl rule reapply --all # re-apply all rules
|
||||||
|
```
|
||||||
|
|
||||||
|
### Rule display (wgctl rule show)
|
||||||
|
|
||||||
|
```
|
||||||
|
Rule: user
|
||||||
|
────────────────────────────────────────────────
|
||||||
|
|
||||||
|
Description: Standard user
|
||||||
|
Group: Users
|
||||||
|
DNS: true (inherited)
|
||||||
|
|
||||||
|
── Extends ──────────────────────────────────
|
||||||
|
↳ no-proxmox
|
||||||
|
- 10.0.0.101/32 → npm
|
||||||
|
- 10.0.0.100/32 → proxmox
|
||||||
|
|
||||||
|
↳ dns-restricted
|
||||||
|
+ 10.0.0.103:53:udp → pihole:dns-udp
|
||||||
|
+ 10.0.0.103:53:tcp → pihole:dns-tcp
|
||||||
|
- 10.0.0.103/32 → pihole
|
||||||
|
↺ DNS → 10.0.0.103 → pihole
|
||||||
|
|
||||||
|
── Peers ──────────────────────────────────
|
||||||
|
Assigned: 11
|
||||||
|
phone-nuno 10.1.3.1
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Network Services Registry
|
||||||
|
|
||||||
|
Map your LAN services to names for use in rules and blocks. IPs/ports are resolved at creation time — rules remain independent of the registry.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Add services
|
||||||
|
wgctl net add --name proxmox --ip 10.0.0.100 --desc "Proxmox VE" --tag admin
|
||||||
|
wgctl net add --name proxmox:web-ui --port 8006:tcp
|
||||||
|
wgctl net add --name proxmox:ssh --port 22:tcp
|
||||||
|
|
||||||
|
wgctl net add --name pihole --ip 10.0.0.103 --desc "Pi-hole + Unbound" --tag dns
|
||||||
|
wgctl net add --name pihole:dns-udp --port 53:udp
|
||||||
|
wgctl net add --name pihole:dns-tcp --port 53:tcp
|
||||||
|
|
||||||
|
# List
|
||||||
|
wgctl net list
|
||||||
|
wgctl net list --detailed
|
||||||
|
wgctl net list --tag admin
|
||||||
|
wgctl net show --name proxmox
|
||||||
|
|
||||||
|
# Remove
|
||||||
|
wgctl net rm --name proxmox:web-ui # remove specific port
|
||||||
|
wgctl net rm --name proxmox:ports # remove all ports
|
||||||
|
wgctl net rm --name proxmox # remove service entirely
|
||||||
|
```
|
||||||
|
|
||||||
|
Services are used with `--service` in `block`/`unblock` and `--block-service`/`--allow-service` in `rule add`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
wgctl block --name phone-nuno --service proxmox
|
||||||
|
wgctl block --name phone-nuno --service proxmox:web-ui
|
||||||
|
wgctl rule add --name no-admin --block-service proxmox --block-service truenas
|
||||||
|
```
|
||||||
|
|
||||||
|
Service names appear as annotations in `inspect` and `rule show`:
|
||||||
|
|
||||||
|
```
|
||||||
|
- 10.0.0.100:8006:tcp → proxmox:web-ui
|
||||||
|
- 10.0.0.200 → truenas
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Blocking and Restrictions
|
||||||
|
|
||||||
|
### Full block (removes peer from WireGuard)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
wgctl block --name phone-nuno # full block
|
||||||
|
wgctl unblock --name phone-nuno # restore (overrides group blocks)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Specific restrictions (peer stays in WireGuard)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Block by IP, subnet, port, or named service
|
||||||
|
wgctl block --name phone-nuno --ip 10.0.0.210
|
||||||
|
wgctl block --name phone-nuno --subnet 10.0.0.0/24
|
||||||
|
wgctl block --name phone-nuno --port 10.0.0.100:8006:tcp
|
||||||
|
wgctl block --name phone-nuno --service proxmox
|
||||||
|
wgctl block --name phone-nuno --service truenas:web-ui --block-name "no truenas ui"
|
||||||
|
|
||||||
|
# Unblock specific rules
|
||||||
|
wgctl unblock --name phone-nuno --ip 10.0.0.210
|
||||||
|
wgctl unblock --name phone-nuno --service proxmox
|
||||||
|
wgctl unblock --name phone-nuno --service truenas:web-ui
|
||||||
|
```
|
||||||
|
|
||||||
|
All block rules are persisted in JSON and restored on restart.
|
||||||
|
|
||||||
|
### Block state in inspect
|
||||||
|
|
||||||
|
```
|
||||||
|
── Peer Blocks ──────────────────────────────────
|
||||||
|
🚫 blocked by groups: family
|
||||||
|
- 10.0.0.210 → docker
|
||||||
|
- 10.0.0.100:8006:tcp → proxmox:web-ui no truenas ui
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Groups
|
||||||
|
|
||||||
|
Peers can belong to multiple groups (M:N). Group block/unblock tracks which groups have blocked a peer — unblocking one group won't restore a peer still blocked by another.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
wgctl group list
|
||||||
|
wgctl group add --name family --desc "Family devices"
|
||||||
|
wgctl group show --name family
|
||||||
|
|
||||||
|
# Peer membership
|
||||||
|
wgctl group peer add --name family --peer phone-nuno
|
||||||
|
wgctl group peer remove --name family --peer phone-nuno
|
||||||
|
|
||||||
|
# Bulk operations
|
||||||
|
wgctl group block --name family # block all peers
|
||||||
|
wgctl group unblock --name family # unblock (respects M:N)
|
||||||
|
wgctl group rule assign --name family --rule user
|
||||||
|
|
||||||
|
# Monitoring per group
|
||||||
|
wgctl group watch --name family
|
||||||
|
wgctl group logs --name family --limit 20
|
||||||
|
wgctl group audit --name family
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Monitoring
|
||||||
|
|
||||||
|
### Live monitor
|
||||||
|
|
||||||
|
```bash
|
||||||
|
wgctl watch # all peers — handshakes + fw drops
|
||||||
|
wgctl watch --name phone-nuno # single peer
|
||||||
|
wgctl watch --type phone # by device type
|
||||||
|
wgctl watch --blocked # only blocked peer attempts
|
||||||
|
wgctl watch --allowed # only handshakes
|
||||||
|
```
|
||||||
|
|
||||||
|
Output shows source (`wg` = WireGuard, `fw` = firewall drop), client, destination/endpoint, event, and status.
|
||||||
|
|
||||||
|
### Logs
|
||||||
|
|
||||||
|
```bash
|
||||||
|
wgctl logs # WireGuard events + firewall drops
|
||||||
|
wgctl logs --name phone-nuno # filter by peer
|
||||||
|
wgctl logs --fw # firewall drops only
|
||||||
|
wgctl logs --follow # live tail
|
||||||
|
wgctl logs remove --name phone-nuno # remove entries for peer
|
||||||
|
wgctl logs remove --before 7 # remove entries older than 7 days
|
||||||
|
wgctl logs rotate # rotate logs (default: keep 7 days)
|
||||||
|
wgctl logs rotate --days 30
|
||||||
|
```
|
||||||
|
|
||||||
|
Duplicate events are collapsed with counts: `attempt (x88)`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Firewall Inspection
|
||||||
|
|
||||||
|
```bash
|
||||||
|
wgctl fw # show FORWARD chain rules
|
||||||
|
wgctl fw list --peer phone-nuno # filter by peer
|
||||||
|
wgctl fw list --rule user # all peers with this rule
|
||||||
|
wgctl fw list --no-nflog # hide logging rules
|
||||||
|
wgctl fw nat # show NAT/DNS redirect rules
|
||||||
|
wgctl fw count # rule counts by type
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Audit
|
||||||
|
|
||||||
|
Verify that iptables state matches expected configuration. Includes inherited rules and peer-specific blocks in expected counts.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
wgctl audit # audit all peers
|
||||||
|
wgctl audit --peer phone-nuno # single peer
|
||||||
|
wgctl audit --type guest # by device type
|
||||||
|
wgctl audit --fix # auto-repair missing rules
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
```
|
||||||
|
✅ phone-nuno rule=admin fw: 0/0
|
||||||
|
✅ phone-helena rule=user fw: 14/14
|
||||||
|
✅ guest-zephyr rule=moonlight fw: 5/5
|
||||||
|
✅ phone-test rule=user fw: 16/16 (includes 2 peer-specific blocks)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Service Management
|
||||||
|
|
||||||
|
```bash
|
||||||
|
wgctl service start
|
||||||
|
wgctl service stop
|
||||||
|
wgctl service restart # flushes and restores all fw rules
|
||||||
|
wgctl service status
|
||||||
|
wgctl service enable
|
||||||
|
wgctl service disable
|
||||||
|
```
|
||||||
|
|
||||||
|
On restart, wgctl restores:
|
||||||
|
- All rule iptables rules for all peers
|
||||||
|
- All block state (full blocks, group blocks, peer-specific restrictions)
|
||||||
|
- DNS redirect NAT rules
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Interactive Shell
|
||||||
|
|
||||||
|
```bash
|
||||||
|
wgctl shell
|
||||||
|
```
|
||||||
|
|
||||||
|
Full REPL with tab completion, command history (`~/.wgctl_history`), and all wgctl commands available without the `wgctl` prefix. Bash commands also work.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Override defaults in `/etc/wireguard/.wgctl/wgctl.conf`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
WG_INTERFACE=wg0
|
||||||
|
WG_ENDPOINT=wg.example.com:51820
|
||||||
|
WG_DNS=10.0.0.103
|
||||||
|
WG_LISTEN_PORT=51820
|
||||||
|
WG_SUBNET=10.1.0.0/16
|
||||||
|
WG_LAN=10.0.0.0/24
|
||||||
|
DATE_FORMAT=eu # iso | eu | eu-dash
|
||||||
|
HANDSHAKE_CHECK_TIME_SEC=300
|
||||||
|
ACTIVITY_TOTAL_LOW=1000000
|
||||||
|
ACTIVITY_TOTAL_MED=10000000
|
||||||
|
ACTIVITY_TOTAL_HIGH=100000000
|
||||||
|
ACTIVITY_CURRENT_LOW=10000
|
||||||
|
ACTIVITY_CURRENT_MED=100000
|
||||||
|
ACTIVITY_CURRENT_HIGH=1000000
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Data Layout
|
||||||
|
|
||||||
|
```
|
||||||
|
/etc/wireguard/
|
||||||
|
├── wg0.conf # WireGuard server config
|
||||||
|
├── clients/ # per-peer client configs + keys
|
||||||
|
│ ├── phone-nuno.conf
|
||||||
|
│ ├── phone-nuno_public.key
|
||||||
|
│ └── phone-nuno_private.key
|
||||||
|
└── .wgctl/
|
||||||
|
├── wgctl.conf # config overrides
|
||||||
|
├── rules/ # assignable rule files
|
||||||
|
│ ├── user.rule
|
||||||
|
│ ├── guest.rule
|
||||||
|
│ └── base/ # base rules (not directly assignable)
|
||||||
|
│ ├── no-nginx.rule
|
||||||
|
│ └── no-proxmox.rule
|
||||||
|
├── groups/ # group definitions
|
||||||
|
│ └── family.group
|
||||||
|
├── blocks/ # per-peer block state (JSON)
|
||||||
|
│ └── phone-test.block
|
||||||
|
├── meta/ # per-peer metadata (rule, subtype)
|
||||||
|
│ └── phone-nuno.meta
|
||||||
|
├── services.json # network services registry
|
||||||
|
└── daemon/
|
||||||
|
├── events.log # WireGuard connection events (JSONL)
|
||||||
|
├── fw_events.log # firewall drop events (JSONL, via ulogd2)
|
||||||
|
├── watchlist.json # blocked peer IPs for scapy monitor
|
||||||
|
├── endpoint_cache.json # cached real endpoints
|
||||||
|
├── transfer_cache.json # activity delta cache
|
||||||
|
└── wgctl-monitor.py # scapy packet capture daemon
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
wgctl follows a layered architecture:
|
||||||
|
|
||||||
|
```
|
||||||
|
commands/ — user-facing commands (add, block, rule, group...)
|
||||||
|
modules/ — shared business logic (peers, rules, blocks, fw, net...)
|
||||||
|
core/ — bootstrap, utilities, JSON helper, formatting
|
||||||
|
install/ — systemd units, logrotate, example config
|
||||||
|
```
|
||||||
|
|
||||||
|
Commands orchestrate. Modules enforce. Python handles all JSON state. Bash handles orchestration and display.
|
||||||
|
|
||||||
|
Key modules:
|
||||||
|
- `rule.module.sh` — rule resolution with inheritance, apply/unapply
|
||||||
|
- `block.module.sh` — block state management, apply/restore
|
||||||
|
- `fw.module.sh` — iptables wrappers (idempotent, mode-aware)
|
||||||
|
- `net.module.sh` — service registry lookups and annotations
|
||||||
|
- `peers.module.sh` — peer queries, status, formatting
|
||||||
|
- `monitor.module.sh` — endpoint cache, watchlist
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Test Suite
|
||||||
|
|
||||||
|
```bash
|
||||||
|
wgctl test # non-destructive tests (64 tests)
|
||||||
|
wgctl test --destructive # includes add/remove/block operations
|
||||||
|
wgctl test --section rules # run specific section
|
||||||
|
wgctl test --section net
|
||||||
|
wgctl test --section destructive
|
||||||
|
```
|
||||||
|
|
||||||
|
Sections: `list`, `inspect`, `config`, `rules`, `groups`, `audit`, `logs`, `fw`, `net`, `destructive`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
@ -178,6 +178,7 @@ function cmd::add::run() {
|
||||||
log::wg_add "Type: ${type}"
|
log::wg_add "Type: ${type}"
|
||||||
log::wg_add "IP: ${ip}"
|
log::wg_add "IP: ${ip}"
|
||||||
log::wg_add "Tunnel: ${tunnel} (${allowed_ips})"
|
log::wg_add "Tunnel: ${tunnel} (${allowed_ips})"
|
||||||
|
log::wg_add "Endpoint: $(config::endpoint)"
|
||||||
log::wg_add "Rule: ${rule:-none}"
|
log::wg_add "Rule: ${rule:-none}"
|
||||||
|
|
||||||
keys::generate_pair "$full_name" || return 1
|
keys::generate_pair "$full_name" || return 1
|
||||||
|
|
|
||||||
|
|
@ -160,10 +160,11 @@ function cmd::inspect::_blocks_info() {
|
||||||
function cmd::inspect::_group_info() {
|
function cmd::inspect::_group_info() {
|
||||||
local name="$1"
|
local name="$1"
|
||||||
|
|
||||||
ui::section "Groups"
|
|
||||||
|
|
||||||
local groups=()
|
local groups=()
|
||||||
mapfile -t groups < <(json::peer_groups "$(ctx::groups)" "$name")
|
mapfile -t groups < <(json::peer_groups "$(ctx::groups)" "$name")
|
||||||
|
[[ ${#groups[@]} -eq 0 || -z "${groups[0]:-}" ]] && return 0
|
||||||
|
|
||||||
|
ui::section "Groups"
|
||||||
|
|
||||||
if [[ ${#groups[@]} -eq 0 ]] || [[ -z "${groups[0]:-}" ]]; then
|
if [[ ${#groups[@]} -eq 0 ]] || [[ -z "${groups[0]:-}" ]]; then
|
||||||
printf " —\n"
|
printf " —\n"
|
||||||
|
|
@ -195,6 +196,8 @@ function cmd::inspect::_firewall_info() {
|
||||||
rules_output+=("$line")
|
rules_output+=("$line")
|
||||||
done < <(fw::forward_rules_for_ip "$ip" | grep -v NFLOG)
|
done < <(fw::forward_rules_for_ip "$ip" | grep -v NFLOG)
|
||||||
|
|
||||||
|
[[ ${#rules_output[@]} -eq 0 || -z "${rules_output[0]:-}" ]] && return 0
|
||||||
|
|
||||||
# printf "\n \033[0;37m── Firewall (\033[0;32m+%d\033[0m \033[0;31m-%d\033[0m) \033[0m%s\n" \
|
# printf "\n \033[0;37m── Firewall (\033[0;32m+%d\033[0m \033[0;31m-%d\033[0m) \033[0m%s\n" \
|
||||||
# "$accepts" "$drops" "$(printf '─%.0s' {1..28})"
|
# "$accepts" "$drops" "$(printf '─%.0s' {1..28})"
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue