gistfile1.txt
· 12 KiB · Text
Eredeti
#!/usr/bin/env bash
FW_EVENTS_LOG="$(ctx::fw_events_log)"
WG_EVENTS_LOG="$(ctx::events_log)"
function cmd::logs::on_load() {
flag::register --name
flag::register --type
flag::register --since
flag::register --limit
flag::register --fw
flag::register --wg
flag::register --follow
flag::register --merged
flag::register --all
flag::register --before
flag::register --force
flag::register --days
flag::register --raw
flag::register --detailed
}
function cmd::logs::help() {
cat <<EOF
Usage: wgctl logs [subcommand] [options]
Show or manage WireGuard and firewall activity logs.
Subcommands:
show (default) Show activity logs
remove, rm Remove log entries
rotate Remove entries older than N days
Options for show:
--name <name> Filter by client name
--type <type> Filter by device type
--limit <n> Max results per source (default: 50)
--fw Show only firewall drops
--wg Show only WireGuard events
--merged Show all events chronologically interleaved
--follow, -f Follow logs in real time (alias: wgctl watch)
--raw Show raw IPs without service annotation
Options for remove:
--name <name> Remove entries for specific peer
--all Remove all log entries
--fw Remove only firewall events
--wg Remove only WireGuard events
--before <days> Remove entries older than N days
--force Skip confirmation
Options for rotate:
--days <n> Days to keep (default: 7)
--force Skip confirmation
Examples:
wgctl logs
wgctl logs --name phone-nuno
wgctl logs --fw --limit 100
wgctl logs --merged
wgctl logs --follow
wgctl logs remove --name phone-nuno
wgctl logs rotate --days 30
EOF
}
function cmd::logs::run() {
local subcmd="${1:-show}"
if [[ "$subcmd" == --* ]]; then
subcmd="show"
else
shift || true
fi
case "$subcmd" in
show) cmd::logs::show "$@" ;;
remove|rm|del) cmd::logs::remove "$@" ;;
rotate) cmd::logs::rotate "$@" ;;
help) cmd::logs::help ;;
*)
log::error "Unknown subcommand: '${subcmd}'"
cmd::logs::help
return 1
;;
esac
}
function cmd::logs::show() {
local name="" type="" limit=50
local fw_only=false wg_only=false follow=false merged=false raw=false detailed=false
while [[ $# -gt 0 ]]; do
case "$1" in
--name) name="$2"; shift 2 ;;
--type) type="$2"; shift 2 ;;
--limit) limit="$2"; shift 2 ;;
--fw) fw_only=true; shift ;;
--wg) wg_only=true; shift ;;
--merged) merged=true; shift ;;
--follow|-f) follow=true; shift ;;
--raw) raw=true; shift ;;
--detailed) detailed=true shift ;;
--help) cmd::logs::help; return ;;
*)
log::error "Unknown flag: $1"
return 1
;;
esac
done
local collapse=1
$detailed && collapse=0
if [[ -n "$name" && -n "$type" ]]; then
name=$(peers::resolve_and_require "$name" "$type") || return 1
fi
local filter_ip=""
if [[ -n "$name" ]]; then
filter_ip=$(peers::get_ip "$name")
[[ -z "$filter_ip" ]] && log::error "Could not find IP for: $name" && return 1
fi
if $follow; then
cmd::logs::follow "$filter_ip" "$name" "$type" "$fw_only" "$wg_only"
return
fi
local net_file=""
$raw || net_file="$(ctx::net)"
log::section "WireGuard Activity Log"
printf "\n"
if $merged; then
cmd::logs::show_merged "$filter_ip" "$name" "$type" "$limit" "$net_file"
return
fi
$wg_only || cmd::logs::show_fw_events "$filter_ip" "$name" "$type" "$limit" "$net_file" "$collapse"
$fw_only || cmd::logs::show_wg_events "$filter_ip" "$name" "$type" "$limit" "$collapse"
}
function cmd::logs::show_fw_events() {
local filter_ip="${1:-}" filter_name="${2:-}" filter_type="${3:-}" \
limit="${4:-50}" net_file="${5:-}" collapse="${6:-1}"
[[ ! -f "$FW_EVENTS_LOG" ]] && return 0
local data
data=$(json::fw_events "$FW_EVENTS_LOG" "$filter_ip" "$filter_type" \
"$(ctx::clients)" "${net_file:-}" "$limit" "$collapse" 2>/dev/null)
[[ -z "$data" ]] && return 0
# Measure column widths
local w_client=16 w_dest=20
while IFS='|' read -r ts client dest_ip dest_port proto svc count; do
[[ -z "$ts" ]] && continue
(( ${#client} > w_client )) && w_client=${#client}
local dest_display
local host_name
host_name=$(hosts::resolve_ip "$dest_ip")
if [[ -n "$host_name" ]]; then
dest_display="$host_name"
elif [[ -n "$svc" ]]; then
[[ -n "$dest_port" ]] && dest_display="${svc}/${proto}" || dest_display="${svc} (${proto})"
else
[[ -n "$dest_port" ]] && dest_display="${dest_ip}:${dest_port}/${proto}" || dest_display="${dest_ip} (${proto})"
fi
(( ${#dest_display} > w_dest )) && w_dest=${#dest_display}
done <<< "$data"
(( w_client += 2 ))
(( w_dest += 2 ))
ui::logs::fw_section_header
while IFS='|' read -r ts client dest_ip dest_port proto svc count; do
[[ -z "$ts" ]] && continue
ui::logs::fw_row "$ts" "$client" "$dest_ip" "$dest_port" \
"$proto" "$svc" "$count" "$w_client" "$w_dest"
done <<< "$data"
printf "\n"
}
function cmd::logs::show_wg_events() {
local filter_ip="${1:-}" filter_name="${2:-}" filter_type="${3:-}" \
limit="${4:-50}" collapse="${5:-1}"
[[ ! -f "$WG_EVENTS_LOG" ]] && return 0
local data
data=$(json::wg_events "$WG_EVENTS_LOG" "$filter_name" "$filter_type" "$limit" "$collapse" 2>/dev/null)
[[ -z "$data" ]] && return 0
# Resolve endpoints and measure column widths
local w_client=16 w_endpoint=16
local resolved_data=""
while IFS='|' read -r ts client endpoint event count; do
[[ -z "$ts" ]] && continue
local endpoint_display
endpoint_display=$(resolve::ip "$endpoint")
[[ -z "$endpoint_display" ]] && endpoint_display="$endpoint"
resolved_data+="${ts}|${client}|${endpoint_display}|${event}|${count}"$'\n'
(( ${#client} > w_client )) && w_client=${#client}
(( ${#endpoint_display} > w_endpoint )) && w_endpoint=${#endpoint_display}
done <<< "$data"
(( w_client += 2 ))
(( w_endpoint += 2 ))
ui::logs::wg_section_header
while IFS='|' read -r ts client endpoint event count; do
[[ -z "$ts" ]] && continue
ui::logs::wg_row "$ts" "$client" "$endpoint" "$event" \
"$count" "$w_client" "$w_endpoint"
done <<< "$resolved_data"
printf "\n"
}
function cmd::logs::show_merged() {
local filter_ip="${1:-}" filter_name="${2:-}" filter_type="${3:-}" \
limit="${4:-50}" net_file="${5:-}"
local fw_data wg_data
fw_data=$(json::fw_events "$FW_EVENTS_LOG" "$filter_ip" "$filter_type" \
"$(ctx::clients)" "${net_file:-}" "$limit" 2>/dev/null)
wg_data=$(json::wg_events "$WG_EVENTS_LOG" "$filter_name" "$filter_type" \
"$limit" 2>/dev/null)
# Measure widths across both sources
local w_client=16 w_dest=20
while IFS='|' read -r ts client rest; do
[[ -z "$ts" ]] && continue
(( ${#client} > w_client )) && w_client=${#client}
done < <(echo "$fw_data"; echo "$wg_data")
(( w_client += 2 ))
# Tag and merge: prefix fw lines with "fw|", wg lines with "wg|"
local merged_data
merged_data=$(
while IFS='|' read -r ts client dest_ip dest_port proto svc count; do
[[ -z "$ts" ]] && continue
echo "fw|${ts}|${client}|${dest_ip}|${dest_port}|${proto}|${svc}|${count}"
done <<< "$fw_data"
while IFS='|' read -r ts client endpoint event count; do
[[ -z "$ts" ]] && continue
echo "wg|${ts}|${client}|${endpoint}|${event}|${count}"
done <<< "$wg_data"
)
# Sort by timestamp field 2
while IFS='|' read -r source ts rest; do
[[ -z "$source" ]] && continue
case "$source" in
fw)
IFS='|' read -r client dest_ip dest_port proto svc count <<< "$rest"
local dest_display
if [[ -n "$svc" ]]; then
[[ -n "$dest_port" ]] && dest_display="${svc}/${proto}" || dest_display="${svc} (${proto})"
else
[[ -n "$dest_port" ]] && dest_display="${dest_ip}:${dest_port}/${proto}" || dest_display="${dest_ip} (${proto})"
fi
(( ${#dest_display} > w_dest )) && w_dest=${#dest_display}
;;
esac
done <<< "$merged_data"
(( w_dest += 2 ))
while IFS='|' read -r source ts rest; do
[[ -z "$source" ]] && continue
case "$source" in
fw)
IFS='|' read -r client dest_ip dest_port proto svc count <<< "$rest"
ui::watch::fw_row "$ts" "$client" \
"$(ui::logs::build_dest "$dest_ip" "$dest_port" "$proto" "$svc")" \
"$w_client" "$w_dest"
;;
wg)
IFS='|' read -r client endpoint event count <<< "$rest"
ui::watch::wg_row "$ts" "$client" "$endpoint" "$event" \
"$w_client" "$w_dest"
;;
esac
done < <(echo "$merged_data" | sort -t'|' -k2,2)
printf "\n"
}
function cmd::logs::follow() {
local filter_ip="${1:-}" filter_name="${2:-}" filter_type="${3:-}"
local fw_only="${4:-false}" wg_only="${5:-false}"
log::section "WireGuard Live Log (Ctrl+C to stop)"
printf "\n"
# Delegate to watch command
local watch_args=()
[[ -n "$filter_name" ]] && watch_args+=(--name "$filter_name")
[[ -n "$filter_type" ]] && watch_args+=(--type "$filter_type")
$fw_only && watch_args+=(--restricted)
$wg_only && watch_args+=(--blocked)
cmd::watch::run "${watch_args[@]}"
}
function cmd::logs::remove() {
local name="" type="" before="" force=false
local fw_only=false wg_only=false all=false
while [[ $# -gt 0 ]]; do
case "$1" in
--name) name="$2"; shift 2 ;;
--type) type="$2"; shift 2 ;;
--before) before="$2"; shift 2 ;;
--fw) fw_only=true; shift ;;
--wg) wg_only=true; shift ;;
--all) all=true; shift ;;
--force) force=true; shift ;;
--help) cmd::logs::help; return ;;
*)
log::error "Unknown flag: $1"
return 1
;;
esac
done
if ! $all && [[ -z "$name" && -z "$before" ]]; then
log::error "Specify --name, --before, or --all"
cmd::logs::help
return 1
fi
local filter_ip=""
if [[ -n "$name" ]]; then
name=$(peers::resolve_and_require "$name" "$type") || return 1
filter_ip=$(peers::get_ip "$name")
fi
local desc=""
$all && desc="all entries"
[[ -n "$name" ]] && desc="entries for '${name}'"
[[ -n "$before" ]] && desc="${desc:+$desc, }entries older than ${before} days"
$fw_only && desc="${desc} (fw only)"
$wg_only && desc="${desc} (wg only)"
if ! $force; then
read -r -p "Remove ${desc}? [y/N] " confirm
case "$confirm" in
[yY]*) ;;
*) log::info "Aborted"; return 0 ;;
esac
fi
local result
result=$(json::remove_events_filtered \
"$WG_EVENTS_LOG" "$FW_EVENTS_LOG" \
"${name:-}" "${filter_ip:-}" \
"$fw_only" "$wg_only" \
"${before:-}")
local removed_wg removed_fw
IFS="|" read -r removed_wg removed_fw <<< "$result"
local total=$(( removed_wg + removed_fw ))
if [[ "$total" -eq 0 ]]; then
log::wg_warning "No log entries found matching the criteria"
return 0
fi
log::wg_success "Removed ${total} log entries (wg: ${removed_wg}, fw: ${removed_fw})"
}
function cmd::logs::rotate() {
local days=7 force=false
while [[ $# -gt 0 ]]; do
case "$1" in
--days) days="$2"; shift 2 ;;
--force) force=true; shift ;;
--help) cmd::logs::help; return ;;
*) log::error "Unknown flag: $1"; return 1 ;;
esac
done
$force || {
read -r -p "Remove log entries older than ${days} days? [y/N] " confirm
case "$confirm" in
[yY]*) ;;
*) log::info "Aborted"; return 0 ;;
esac
}
local result
result=$(json::remove_events_filtered \
"$WG_EVENTS_LOG" "$FW_EVENTS_LOG" \
"" "" "false" "false" "$days")
local removed_wg removed_fw
IFS="|" read -r removed_wg removed_fw <<< "$result"
local total=$(( removed_wg + removed_fw ))
if [[ "$total" -eq 0 ]]; then
log::wg_warning "No log entries older than ${days} days"
return 0
fi
log::wg_success "Rotated ${total} entries older than ${days} days (wg: ${removed_wg}, fw: ${removed_fw})"
}
| 1 | #!/usr/bin/env bash |
| 2 | |
| 3 | FW_EVENTS_LOG="$(ctx::fw_events_log)" |
| 4 | WG_EVENTS_LOG="$(ctx::events_log)" |
| 5 | |
| 6 | function cmd::logs::on_load() { |
| 7 | flag::register --name |
| 8 | flag::register --type |
| 9 | flag::register --since |
| 10 | flag::register --limit |
| 11 | flag::register --fw |
| 12 | flag::register --wg |
| 13 | flag::register --follow |
| 14 | flag::register --merged |
| 15 | flag::register --all |
| 16 | flag::register --before |
| 17 | flag::register --force |
| 18 | flag::register --days |
| 19 | flag::register --raw |
| 20 | flag::register --detailed |
| 21 | } |
| 22 | |
| 23 | function cmd::logs::help() { |
| 24 | cat <<EOF |
| 25 | Usage: wgctl logs [subcommand] [options] |
| 26 | |
| 27 | Show or manage WireGuard and firewall activity logs. |
| 28 | |
| 29 | Subcommands: |
| 30 | show (default) Show activity logs |
| 31 | remove, rm Remove log entries |
| 32 | rotate Remove entries older than N days |
| 33 | |
| 34 | Options for show: |
| 35 | --name <name> Filter by client name |
| 36 | --type <type> Filter by device type |
| 37 | --limit <n> Max results per source (default: 50) |
| 38 | --fw Show only firewall drops |
| 39 | --wg Show only WireGuard events |
| 40 | --merged Show all events chronologically interleaved |
| 41 | --follow, -f Follow logs in real time (alias: wgctl watch) |
| 42 | --raw Show raw IPs without service annotation |
| 43 | |
| 44 | Options for remove: |
| 45 | --name <name> Remove entries for specific peer |
| 46 | --all Remove all log entries |
| 47 | --fw Remove only firewall events |
| 48 | --wg Remove only WireGuard events |
| 49 | --before <days> Remove entries older than N days |
| 50 | --force Skip confirmation |
| 51 | |
| 52 | Options for rotate: |
| 53 | --days <n> Days to keep (default: 7) |
| 54 | --force Skip confirmation |
| 55 | |
| 56 | Examples: |
| 57 | wgctl logs |
| 58 | wgctl logs --name phone-nuno |
| 59 | wgctl logs --fw --limit 100 |
| 60 | wgctl logs --merged |
| 61 | wgctl logs --follow |
| 62 | wgctl logs remove --name phone-nuno |
| 63 | wgctl logs rotate --days 30 |
| 64 | EOF |
| 65 | } |
| 66 | |
| 67 | function cmd::logs::run() { |
| 68 | local subcmd="${1:-show}" |
| 69 | if [[ "$subcmd" == --* ]]; then |
| 70 | subcmd="show" |
| 71 | else |
| 72 | shift || true |
| 73 | fi |
| 74 | |
| 75 | case "$subcmd" in |
| 76 | show) cmd::logs::show "$@" ;; |
| 77 | remove|rm|del) cmd::logs::remove "$@" ;; |
| 78 | rotate) cmd::logs::rotate "$@" ;; |
| 79 | help) cmd::logs::help ;; |
| 80 | *) |
| 81 | log::error "Unknown subcommand: '${subcmd}'" |
| 82 | cmd::logs::help |
| 83 | return 1 |
| 84 | ;; |
| 85 | esac |
| 86 | } |
| 87 | |
| 88 | function cmd::logs::show() { |
| 89 | local name="" type="" limit=50 |
| 90 | local fw_only=false wg_only=false follow=false merged=false raw=false detailed=false |
| 91 | |
| 92 | |
| 93 | while [[ $# -gt 0 ]]; do |
| 94 | case "$1" in |
| 95 | --name) name="$2"; shift 2 ;; |
| 96 | --type) type="$2"; shift 2 ;; |
| 97 | --limit) limit="$2"; shift 2 ;; |
| 98 | --fw) fw_only=true; shift ;; |
| 99 | --wg) wg_only=true; shift ;; |
| 100 | --merged) merged=true; shift ;; |
| 101 | --follow|-f) follow=true; shift ;; |
| 102 | --raw) raw=true; shift ;; |
| 103 | --detailed) detailed=true shift ;; |
| 104 | --help) cmd::logs::help; return ;; |
| 105 | *) |
| 106 | log::error "Unknown flag: $1" |
| 107 | return 1 |
| 108 | ;; |
| 109 | esac |
| 110 | done |
| 111 | |
| 112 | local collapse=1 |
| 113 | $detailed && collapse=0 |
| 114 | |
| 115 | |
| 116 | if [[ -n "$name" && -n "$type" ]]; then |
| 117 | name=$(peers::resolve_and_require "$name" "$type") || return 1 |
| 118 | fi |
| 119 | |
| 120 | local filter_ip="" |
| 121 | if [[ -n "$name" ]]; then |
| 122 | filter_ip=$(peers::get_ip "$name") |
| 123 | [[ -z "$filter_ip" ]] && log::error "Could not find IP for: $name" && return 1 |
| 124 | fi |
| 125 | |
| 126 | if $follow; then |
| 127 | cmd::logs::follow "$filter_ip" "$name" "$type" "$fw_only" "$wg_only" |
| 128 | return |
| 129 | fi |
| 130 | |
| 131 | local net_file="" |
| 132 | $raw || net_file="$(ctx::net)" |
| 133 | |
| 134 | log::section "WireGuard Activity Log" |
| 135 | printf "\n" |
| 136 | |
| 137 | if $merged; then |
| 138 | cmd::logs::show_merged "$filter_ip" "$name" "$type" "$limit" "$net_file" |
| 139 | return |
| 140 | fi |
| 141 | |
| 142 | $wg_only || cmd::logs::show_fw_events "$filter_ip" "$name" "$type" "$limit" "$net_file" "$collapse" |
| 143 | $fw_only || cmd::logs::show_wg_events "$filter_ip" "$name" "$type" "$limit" "$collapse" |
| 144 | } |
| 145 | |
| 146 | function cmd::logs::show_fw_events() { |
| 147 | local filter_ip="${1:-}" filter_name="${2:-}" filter_type="${3:-}" \ |
| 148 | limit="${4:-50}" net_file="${5:-}" collapse="${6:-1}" |
| 149 | |
| 150 | [[ ! -f "$FW_EVENTS_LOG" ]] && return 0 |
| 151 | |
| 152 | local data |
| 153 | data=$(json::fw_events "$FW_EVENTS_LOG" "$filter_ip" "$filter_type" \ |
| 154 | "$(ctx::clients)" "${net_file:-}" "$limit" "$collapse" 2>/dev/null) |
| 155 | |
| 156 | [[ -z "$data" ]] && return 0 |
| 157 | |
| 158 | # Measure column widths |
| 159 | local w_client=16 w_dest=20 |
| 160 | while IFS='|' read -r ts client dest_ip dest_port proto svc count; do |
| 161 | [[ -z "$ts" ]] && continue |
| 162 | (( ${#client} > w_client )) && w_client=${#client} |
| 163 | local dest_display |
| 164 | local host_name |
| 165 | host_name=$(hosts::resolve_ip "$dest_ip") |
| 166 | if [[ -n "$host_name" ]]; then |
| 167 | dest_display="$host_name" |
| 168 | elif [[ -n "$svc" ]]; then |
| 169 | [[ -n "$dest_port" ]] && dest_display="${svc}/${proto}" || dest_display="${svc} (${proto})" |
| 170 | else |
| 171 | [[ -n "$dest_port" ]] && dest_display="${dest_ip}:${dest_port}/${proto}" || dest_display="${dest_ip} (${proto})" |
| 172 | fi |
| 173 | (( ${#dest_display} > w_dest )) && w_dest=${#dest_display} |
| 174 | done <<< "$data" |
| 175 | (( w_client += 2 )) |
| 176 | (( w_dest += 2 )) |
| 177 | |
| 178 | ui::logs::fw_section_header |
| 179 | while IFS='|' read -r ts client dest_ip dest_port proto svc count; do |
| 180 | [[ -z "$ts" ]] && continue |
| 181 | ui::logs::fw_row "$ts" "$client" "$dest_ip" "$dest_port" \ |
| 182 | "$proto" "$svc" "$count" "$w_client" "$w_dest" |
| 183 | done <<< "$data" |
| 184 | printf "\n" |
| 185 | } |
| 186 | |
| 187 | function cmd::logs::show_wg_events() { |
| 188 | local filter_ip="${1:-}" filter_name="${2:-}" filter_type="${3:-}" \ |
| 189 | limit="${4:-50}" collapse="${5:-1}" |
| 190 | |
| 191 | [[ ! -f "$WG_EVENTS_LOG" ]] && return 0 |
| 192 | |
| 193 | local data |
| 194 | data=$(json::wg_events "$WG_EVENTS_LOG" "$filter_name" "$filter_type" "$limit" "$collapse" 2>/dev/null) |
| 195 | |
| 196 | [[ -z "$data" ]] && return 0 |
| 197 | |
| 198 | # Resolve endpoints and measure column widths |
| 199 | local w_client=16 w_endpoint=16 |
| 200 | local resolved_data="" |
| 201 | while IFS='|' read -r ts client endpoint event count; do |
| 202 | [[ -z "$ts" ]] && continue |
| 203 | local endpoint_display |
| 204 | endpoint_display=$(resolve::ip "$endpoint") |
| 205 | [[ -z "$endpoint_display" ]] && endpoint_display="$endpoint" |
| 206 | resolved_data+="${ts}|${client}|${endpoint_display}|${event}|${count}"$'\n' |
| 207 | (( ${#client} > w_client )) && w_client=${#client} |
| 208 | (( ${#endpoint_display} > w_endpoint )) && w_endpoint=${#endpoint_display} |
| 209 | done <<< "$data" |
| 210 | (( w_client += 2 )) |
| 211 | (( w_endpoint += 2 )) |
| 212 | |
| 213 | ui::logs::wg_section_header |
| 214 | while IFS='|' read -r ts client endpoint event count; do |
| 215 | [[ -z "$ts" ]] && continue |
| 216 | ui::logs::wg_row "$ts" "$client" "$endpoint" "$event" \ |
| 217 | "$count" "$w_client" "$w_endpoint" |
| 218 | done <<< "$resolved_data" |
| 219 | printf "\n" |
| 220 | } |
| 221 | |
| 222 | |
| 223 | function cmd::logs::show_merged() { |
| 224 | local filter_ip="${1:-}" filter_name="${2:-}" filter_type="${3:-}" \ |
| 225 | limit="${4:-50}" net_file="${5:-}" |
| 226 | |
| 227 | local fw_data wg_data |
| 228 | fw_data=$(json::fw_events "$FW_EVENTS_LOG" "$filter_ip" "$filter_type" \ |
| 229 | "$(ctx::clients)" "${net_file:-}" "$limit" 2>/dev/null) |
| 230 | wg_data=$(json::wg_events "$WG_EVENTS_LOG" "$filter_name" "$filter_type" \ |
| 231 | "$limit" 2>/dev/null) |
| 232 | |
| 233 | # Measure widths across both sources |
| 234 | local w_client=16 w_dest=20 |
| 235 | while IFS='|' read -r ts client rest; do |
| 236 | [[ -z "$ts" ]] && continue |
| 237 | (( ${#client} > w_client )) && w_client=${#client} |
| 238 | done < <(echo "$fw_data"; echo "$wg_data") |
| 239 | (( w_client += 2 )) |
| 240 | |
| 241 | # Tag and merge: prefix fw lines with "fw|", wg lines with "wg|" |
| 242 | local merged_data |
| 243 | merged_data=$( |
| 244 | while IFS='|' read -r ts client dest_ip dest_port proto svc count; do |
| 245 | [[ -z "$ts" ]] && continue |
| 246 | echo "fw|${ts}|${client}|${dest_ip}|${dest_port}|${proto}|${svc}|${count}" |
| 247 | done <<< "$fw_data" |
| 248 | while IFS='|' read -r ts client endpoint event count; do |
| 249 | [[ -z "$ts" ]] && continue |
| 250 | echo "wg|${ts}|${client}|${endpoint}|${event}|${count}" |
| 251 | done <<< "$wg_data" |
| 252 | ) |
| 253 | |
| 254 | # Sort by timestamp field 2 |
| 255 | while IFS='|' read -r source ts rest; do |
| 256 | [[ -z "$source" ]] && continue |
| 257 | case "$source" in |
| 258 | fw) |
| 259 | IFS='|' read -r client dest_ip dest_port proto svc count <<< "$rest" |
| 260 | local dest_display |
| 261 | if [[ -n "$svc" ]]; then |
| 262 | [[ -n "$dest_port" ]] && dest_display="${svc}/${proto}" || dest_display="${svc} (${proto})" |
| 263 | else |
| 264 | [[ -n "$dest_port" ]] && dest_display="${dest_ip}:${dest_port}/${proto}" || dest_display="${dest_ip} (${proto})" |
| 265 | fi |
| 266 | (( ${#dest_display} > w_dest )) && w_dest=${#dest_display} |
| 267 | ;; |
| 268 | esac |
| 269 | done <<< "$merged_data" |
| 270 | (( w_dest += 2 )) |
| 271 | |
| 272 | while IFS='|' read -r source ts rest; do |
| 273 | [[ -z "$source" ]] && continue |
| 274 | case "$source" in |
| 275 | fw) |
| 276 | IFS='|' read -r client dest_ip dest_port proto svc count <<< "$rest" |
| 277 | ui::watch::fw_row "$ts" "$client" \ |
| 278 | "$(ui::logs::build_dest "$dest_ip" "$dest_port" "$proto" "$svc")" \ |
| 279 | "$w_client" "$w_dest" |
| 280 | ;; |
| 281 | wg) |
| 282 | IFS='|' read -r client endpoint event count <<< "$rest" |
| 283 | ui::watch::wg_row "$ts" "$client" "$endpoint" "$event" \ |
| 284 | "$w_client" "$w_dest" |
| 285 | ;; |
| 286 | esac |
| 287 | done < <(echo "$merged_data" | sort -t'|' -k2,2) |
| 288 | |
| 289 | printf "\n" |
| 290 | } |
| 291 | |
| 292 | function cmd::logs::follow() { |
| 293 | local filter_ip="${1:-}" filter_name="${2:-}" filter_type="${3:-}" |
| 294 | local fw_only="${4:-false}" wg_only="${5:-false}" |
| 295 | |
| 296 | log::section "WireGuard Live Log (Ctrl+C to stop)" |
| 297 | printf "\n" |
| 298 | |
| 299 | # Delegate to watch command |
| 300 | local watch_args=() |
| 301 | [[ -n "$filter_name" ]] && watch_args+=(--name "$filter_name") |
| 302 | [[ -n "$filter_type" ]] && watch_args+=(--type "$filter_type") |
| 303 | $fw_only && watch_args+=(--restricted) |
| 304 | $wg_only && watch_args+=(--blocked) |
| 305 | |
| 306 | cmd::watch::run "${watch_args[@]}" |
| 307 | } |
| 308 | |
| 309 | function cmd::logs::remove() { |
| 310 | local name="" type="" before="" force=false |
| 311 | local fw_only=false wg_only=false all=false |
| 312 | |
| 313 | while [[ $# -gt 0 ]]; do |
| 314 | case "$1" in |
| 315 | --name) name="$2"; shift 2 ;; |
| 316 | --type) type="$2"; shift 2 ;; |
| 317 | --before) before="$2"; shift 2 ;; |
| 318 | --fw) fw_only=true; shift ;; |
| 319 | --wg) wg_only=true; shift ;; |
| 320 | --all) all=true; shift ;; |
| 321 | --force) force=true; shift ;; |
| 322 | --help) cmd::logs::help; return ;; |
| 323 | *) |
| 324 | log::error "Unknown flag: $1" |
| 325 | return 1 |
| 326 | ;; |
| 327 | esac |
| 328 | done |
| 329 | |
| 330 | if ! $all && [[ -z "$name" && -z "$before" ]]; then |
| 331 | log::error "Specify --name, --before, or --all" |
| 332 | cmd::logs::help |
| 333 | return 1 |
| 334 | fi |
| 335 | |
| 336 | local filter_ip="" |
| 337 | if [[ -n "$name" ]]; then |
| 338 | name=$(peers::resolve_and_require "$name" "$type") || return 1 |
| 339 | filter_ip=$(peers::get_ip "$name") |
| 340 | fi |
| 341 | |
| 342 | local desc="" |
| 343 | $all && desc="all entries" |
| 344 | [[ -n "$name" ]] && desc="entries for '${name}'" |
| 345 | [[ -n "$before" ]] && desc="${desc:+$desc, }entries older than ${before} days" |
| 346 | $fw_only && desc="${desc} (fw only)" |
| 347 | $wg_only && desc="${desc} (wg only)" |
| 348 | |
| 349 | if ! $force; then |
| 350 | read -r -p "Remove ${desc}? [y/N] " confirm |
| 351 | case "$confirm" in |
| 352 | [yY]*) ;; |
| 353 | *) log::info "Aborted"; return 0 ;; |
| 354 | esac |
| 355 | fi |
| 356 | |
| 357 | local result |
| 358 | result=$(json::remove_events_filtered \ |
| 359 | "$WG_EVENTS_LOG" "$FW_EVENTS_LOG" \ |
| 360 | "${name:-}" "${filter_ip:-}" \ |
| 361 | "$fw_only" "$wg_only" \ |
| 362 | "${before:-}") |
| 363 | |
| 364 | local removed_wg removed_fw |
| 365 | IFS="|" read -r removed_wg removed_fw <<< "$result" |
| 366 | local total=$(( removed_wg + removed_fw )) |
| 367 | |
| 368 | if [[ "$total" -eq 0 ]]; then |
| 369 | log::wg_warning "No log entries found matching the criteria" |
| 370 | return 0 |
| 371 | fi |
| 372 | |
| 373 | log::wg_success "Removed ${total} log entries (wg: ${removed_wg}, fw: ${removed_fw})" |
| 374 | } |
| 375 | |
| 376 | function cmd::logs::rotate() { |
| 377 | local days=7 force=false |
| 378 | |
| 379 | while [[ $# -gt 0 ]]; do |
| 380 | case "$1" in |
| 381 | --days) days="$2"; shift 2 ;; |
| 382 | --force) force=true; shift ;; |
| 383 | --help) cmd::logs::help; return ;; |
| 384 | *) log::error "Unknown flag: $1"; return 1 ;; |
| 385 | esac |
| 386 | done |
| 387 | |
| 388 | $force || { |
| 389 | read -r -p "Remove log entries older than ${days} days? [y/N] " confirm |
| 390 | case "$confirm" in |
| 391 | [yY]*) ;; |
| 392 | *) log::info "Aborted"; return 0 ;; |
| 393 | esac |
| 394 | } |
| 395 | |
| 396 | local result |
| 397 | result=$(json::remove_events_filtered \ |
| 398 | "$WG_EVENTS_LOG" "$FW_EVENTS_LOG" \ |
| 399 | "" "" "false" "false" "$days") |
| 400 | |
| 401 | local removed_wg removed_fw |
| 402 | IFS="|" read -r removed_wg removed_fw <<< "$result" |
| 403 | local total=$(( removed_wg + removed_fw )) |
| 404 | |
| 405 | if [[ "$total" -eq 0 ]]; then |
| 406 | log::wg_warning "No log entries older than ${days} days" |
| 407 | return 0 |
| 408 | fi |
| 409 | |
| 410 | log::wg_success "Rotated ${total} entries older than ${days} days (wg: ${removed_wg}, fw: ${removed_fw})" |
| 411 | } |