- wgctl-monitor: update _hs_last_logged on ALL handshakes not just new sessions
- wgctl-monitor: fix endpoint_cache.json absolute path
- wgctl-monitor: move script to wgctl/daemon/ (correct location)
- watch: _poll_handshakes sorts by ts descending, endpoint cache fallback
- watch: empty endpoint uses - not em dash (alignment fix)
- logs: newline between fw and wg sections
- monitor::live extracted, cmd::logs::follow no longer calls cmd:⌚:run
- ui.sh: UTF-8 extra byte constants
174 lines
4.4 KiB
Bash
174 lines
4.4 KiB
Bash
#!/usr/bin/env bash
|
|
|
|
# ============================================
|
|
# Config
|
|
# ============================================
|
|
|
|
MONITOR_DIR="$(ctx::daemon)"
|
|
WATCHLIST_FILE="${MONITOR_DIR}/watchlist.json"
|
|
EVENTS_LOG="${MONITOR_DIR}/events.log"
|
|
ENDPOINT_CACHE="${MONITOR_DIR}/endpoint_cache.json"
|
|
FW_EVENTS_LOG="${MONITOR_DIR}/fw_events.log"
|
|
MONITOR_SERVICE="wgctl-monitor"
|
|
|
|
# ============================================
|
|
# Lifecycle
|
|
# ============================================
|
|
|
|
function monitor::on_load() {
|
|
if [[ ! -f "$WATCHLIST_FILE" ]]; then
|
|
echo '{}' > "$WATCHLIST_FILE"
|
|
fi
|
|
if [[ ! -f "$EVENTS_LOG" ]]; then
|
|
touch "$EVENTS_LOG"
|
|
fi
|
|
}
|
|
|
|
# ============================================
|
|
# Watchlist
|
|
# ============================================
|
|
|
|
function monitor::watch() {
|
|
local ip="$1" client="$2"
|
|
json::set "$WATCHLIST_FILE" "$ip" "$client"
|
|
log::debug "Watching: ${client} (${ip})"
|
|
}
|
|
|
|
function monitor::unwatch() {
|
|
local ip="$1"
|
|
json::delete "$WATCHLIST_FILE" "$ip"
|
|
}
|
|
|
|
function monitor::is_watched() {
|
|
local ip="$1"
|
|
json::has_key "$WATCHLIST_FILE" "$ip"
|
|
}
|
|
|
|
function monitor::unwatch_client() {
|
|
local name="$1"
|
|
json::filter_values "$WATCHLIST_FILE" "value" "$name"
|
|
log::debug "Unwatched client: ${name}"
|
|
}
|
|
|
|
# ============================================
|
|
# Events
|
|
# ============================================
|
|
|
|
function monitor::last_attempt() {
|
|
local client="$1"
|
|
json::last_event "$EVENTS_LOG" "client" "timestamp" "$client"
|
|
}
|
|
|
|
function monitor::last_endpoint() {
|
|
local client="$1"
|
|
json::last_event "$EVENTS_LOG" "client" "endpoint" "$client"
|
|
}
|
|
|
|
function monitor::events_for() {
|
|
local ip="$1"
|
|
local limit="${2:-50}"
|
|
json::events_for "$EVENTS_LOG" "$ip" "$limit"
|
|
}
|
|
|
|
function monitor::get_handshake_ts() {
|
|
local public_key="${1:-}"
|
|
[[ -z "$public_key" ]] && echo "0" && return
|
|
wg show "$(config::interface)" latest-handshakes 2>/dev/null \
|
|
| grep "^${public_key}" | awk '{print $2}' || echo "0"
|
|
}
|
|
|
|
# ============================================
|
|
# Endpoint Cache (for blocked clients)
|
|
# ============================================
|
|
|
|
ENDPOINT_CACHE="${WGCTL_DIR}/daemon/endpoint_cache.json"
|
|
|
|
function monitor::cache_endpoint() {
|
|
local client="$1" ip="$2"
|
|
json::set "$ENDPOINT_CACHE" "$client" "$ip"
|
|
}
|
|
|
|
function monitor::get_cached_endpoint() {
|
|
local client="${1:-}"
|
|
json::get "$ENDPOINT_CACHE" "$client"
|
|
}
|
|
|
|
function monitor::update_endpoint_cache() {
|
|
while IFS=$'\t' read -r key endpoint; do
|
|
[[ "$endpoint" == "(none)" ]] && continue
|
|
local ip
|
|
ip=$(echo "$endpoint" | cut -d':' -f1)
|
|
local client
|
|
client=$(keys::find_by_public "$key") || continue
|
|
monitor::cache_endpoint "$client" "$ip"
|
|
done < <(wg show "$(config::interface)" endpoints 2>/dev/null)
|
|
}
|
|
|
|
# ============================================
|
|
# Endpoint (from wg show, for active clients)
|
|
# ============================================
|
|
|
|
function monitor::endpoint_for_key() {
|
|
local public_key="$1"
|
|
wg show "$(config::interface)" endpoints 2>/dev/null \
|
|
| grep "^${public_key}" \
|
|
| awk '{print $2}' \
|
|
| cut -d':' -f1
|
|
}
|
|
|
|
# ============================================
|
|
# Service
|
|
# ============================================
|
|
|
|
function monitor::start() {
|
|
systemctl start "$MONITOR_SERVICE"
|
|
log::debug "Monitor daemon started"
|
|
}
|
|
|
|
function monitor::stop() {
|
|
systemctl stop "$MONITOR_SERVICE"
|
|
log::debug "Monitor daemon stopped"
|
|
}
|
|
|
|
function monitor::restart() {
|
|
systemctl restart "$MONITOR_SERVICE"
|
|
log::debug "Monitor daemon restarted"
|
|
}
|
|
|
|
function monitor::is_running() {
|
|
systemctl is-active --quiet "$MONITOR_SERVICE"
|
|
}
|
|
|
|
function monitor::live() {
|
|
local filter_name="${1:-}" filter_type="${2:-}" filter_peers="${3:-}"
|
|
local blocked_only="${4:-false}" restricted_only="${5:-false}" allowed_only="${6:-false}"
|
|
local raw="${7:-false}"
|
|
|
|
[[ "$raw" == "true" ]] && _WGCTL_RAW=true
|
|
|
|
rm -f /tmp/wgctl_hs_* /tmp/wgctl_attempt_* 2>/dev/null || true
|
|
|
|
local w_client=20 w_dest=18
|
|
|
|
if ! $blocked_only && ! $restricted_only; then
|
|
(
|
|
while true; do
|
|
cmd::watch::_poll_handshakes \
|
|
"$filter_name" "$filter_type" "$filter_peers" "$w_client" "$w_dest"
|
|
sleep 5
|
|
done
|
|
) &
|
|
local poller_pid=$!
|
|
fi
|
|
|
|
cmd::watch::_tail_events \
|
|
"$filter_name" "$filter_type" "$filter_peers" \
|
|
"$blocked_only" "$restricted_only" "$allowed_only" \
|
|
"$w_client" "$w_dest" &
|
|
local tailer_pid=$!
|
|
|
|
trap "kill $tailer_pid ${poller_pid:-} 2>/dev/null; \
|
|
rm -f /tmp/wgctl_hs_* /tmp/wgctl_attempt_*; printf '\n'; exit 0" INT TERM
|
|
|
|
wait
|
|
}
|