- ui::_render_endpoint_col: shared endpoint padding primitive
- ui::_build_dest: shared destination display primitive
- ui:⌚:wg_row/fw_row: endpoint annotation (raw_ip → resolved)
- resolve::endpoint_parts: fresh resolution, no stale cache
- resolve::service_name: returns service name or empty (no raw fallback)
- monitor::live: pre-measure w_client from peer names
- watch: fixed w_endpoint=30 for consistent live alignment
- shell: add peer/hosts/identity/subnet/policy/activity to known commands
- shell: updated banner with new commands
- identity/rule help: updated with new features
202 lines
No EOL
6.3 KiB
Bash
202 lines
No EOL
6.3 KiB
Bash
#!/usr/bin/env bash
|
|
|
|
# ============================================
|
|
# Private helpers
|
|
# ============================================
|
|
|
|
function cmd::shell::_prompt() {
|
|
local user host dir
|
|
user=$(whoami)
|
|
host=$(hostname -s)
|
|
dir=$(basename "$PWD")
|
|
printf "\033[1;32m%s@%s\033[0m:\033[0;36m%s\033[0m \033[1;34mwgctl\033[0m> " \
|
|
"$user" "$host" "$dir"
|
|
}
|
|
|
|
function cmd::shell::_is_wgctl_command() {
|
|
local cmd="${1:-}"
|
|
local known=(
|
|
list add remove rm inspect block unblock
|
|
rule group audit logs watch fw config qr
|
|
rename keys ip net service shell help test
|
|
peer hosts identity subnet policy activity
|
|
)
|
|
local c
|
|
for c in "${known[@]}"; do
|
|
[[ "$c" == "$cmd" ]] && return 0
|
|
done
|
|
return 1
|
|
}
|
|
|
|
function cmd::shell::_handle_builtin() {
|
|
local input="${1:-}"
|
|
local first="${input%% *}"
|
|
|
|
case "$first" in
|
|
cd)
|
|
local dir="${input#cd }"
|
|
[[ "$dir" == "$input" ]] && dir="$HOME"
|
|
cd "$dir" 2>/dev/null || log::error "cd: $dir: No such file or directory"
|
|
return 0
|
|
;;
|
|
export|unset|source|.)
|
|
eval "$input"
|
|
return 0
|
|
;;
|
|
esac
|
|
return 1
|
|
}
|
|
|
|
function cmd::shell::_execute() {
|
|
local input="${1:-}"
|
|
local first="${input%% *}"
|
|
local rest="${input#"$first"}"
|
|
rest="${rest# }"
|
|
|
|
cmd::shell::_handle_builtin "$input" && return 0
|
|
|
|
if cmd::shell::_is_wgctl_command "$first"; then
|
|
if [[ -n "$rest" ]]; then
|
|
wgctl::dispatch "$first" $rest || true
|
|
else
|
|
wgctl::dispatch "$first" || true
|
|
fi
|
|
return 0
|
|
fi
|
|
|
|
bash -c "$input" || true
|
|
}
|
|
|
|
function cmd::shell::_setup_history() {
|
|
HISTFILE="${HOME}/.wgctl_history"
|
|
HISTSIZE=1000
|
|
HISTFILESIZE=2000
|
|
history -r 2>/dev/null || true
|
|
}
|
|
|
|
function cmd::shell::_save_history() {
|
|
history -w 2>/dev/null || true
|
|
}
|
|
|
|
function cmd::shell::_banner() {
|
|
ui::section "wgctl shell"
|
|
printf "\n"
|
|
printf " Type wgctl commands directly (no 'wgctl' prefix).\n"
|
|
printf " Bash commands work too: ls, cat, systemctl, vim...\n\n"
|
|
printf " \033[1;37mPeer management:\033[0m\n"
|
|
printf " list List all peers\n"
|
|
printf " list --blocked Show blocked peers\n"
|
|
printf " list --rule user Filter by rule\n"
|
|
printf " inspect --name <peer> Full peer details\n"
|
|
printf " add --identity <id> --type phone Add a peer\n"
|
|
printf " block --name <peer> Block a peer\n"
|
|
printf " unblock --name <peer> Restore access\n"
|
|
printf " peer update-dns --all Update DNS on all peers\n"
|
|
printf " peer update-tunnel --name <p> --mode split\n\n"
|
|
printf " \033[1;37mRules & access:\033[0m\n"
|
|
printf " rule list Show firewall rules\n"
|
|
printf " rule list --tree Show with inheritance\n"
|
|
printf " rule assign --name <r> --peer <p> Assign rule to peer\n"
|
|
printf " identity list Show identities\n"
|
|
printf " identity show --name <id> Identity details + rule tree\n"
|
|
printf " identity rule assign --name <id> --rule <r>\n\n"
|
|
printf " \033[1;37mNetwork & services:\033[0m\n"
|
|
printf " net list Show network services\n"
|
|
printf " net list --detailed Show with ports\n"
|
|
printf " hosts list Show host annotations\n"
|
|
printf " subnet list Show subnets\n"
|
|
printf " group list Show groups\n"
|
|
printf " group block --name <group> Block all in group\n\n"
|
|
printf " \033[1;37mMonitoring:\033[0m\n"
|
|
printf " logs Activity logs\n"
|
|
printf " logs --since 2h Logs from last 2h\n"
|
|
printf " logs --fw --service pihole FW drops to service\n"
|
|
printf " logs --wg --event handshake WG handshake events\n"
|
|
printf " logs --resolved Show resolved names only\n"
|
|
printf " logs --follow Live activity log\n"
|
|
printf " logs clean Remove keepalive entries\n"
|
|
printf " logs rotate Clean old log entries\n"
|
|
printf " watch Live WG + firewall monitor\n"
|
|
printf " activity Transfer + drop summary\n"
|
|
printf " fw list Show iptables rules\n"
|
|
printf " audit Verify firewall state\n"
|
|
printf " audit --fix Auto-repair firewall\n\n"
|
|
printf " \033[1mexit\033[0m or \033[1mquit\033[0m to leave · \033[1mhelp\033[0m for full command list\n\n"
|
|
}
|
|
|
|
# ============================================
|
|
# Lifecycle
|
|
# ============================================
|
|
|
|
function cmd::shell::on_load() {
|
|
: # no flags needed
|
|
}
|
|
|
|
function cmd::shell::help() {
|
|
cat <<EOF
|
|
Usage: wgctl shell
|
|
|
|
Start an interactive wgctl shell.
|
|
All wgctl commands work directly (no 'wgctl' prefix needed).
|
|
Bash commands (ls, cat, systemctl, vim, etc.) also work.
|
|
|
|
Shell builtins handled natively: cd, export, unset, source
|
|
History saved to: ~/.wgctl_history
|
|
|
|
Examples:
|
|
wgctl shell
|
|
wgctl> list --blocked
|
|
wgctl> inspect --name phone-nuno
|
|
wgctl> rule list --tree
|
|
wgctl> group block --name family
|
|
wgctl> logs --follow
|
|
wgctl> ls /etc/wireguard/.wgctl/rules/
|
|
wgctl> exit
|
|
EOF
|
|
}
|
|
|
|
# ============================================
|
|
# Tab completion
|
|
# ============================================
|
|
|
|
function cmd::shell::_setup_completion() {
|
|
local commands="list add remove rm inspect block unblock rule group audit \
|
|
logs watch fw config qr rename service shell help test \
|
|
peer hosts identity subnet policy activity"
|
|
|
|
function _wgctl_shell_complete() {
|
|
local cur="${COMP_WORDS[COMP_CWORD]}"
|
|
COMPREPLY=( $(compgen -W "$commands" -- "$cur") )
|
|
}
|
|
|
|
bind 'set show-all-if-ambiguous on' 2>/dev/null || true
|
|
bind 'set completion-ignore-case on' 2>/dev/null || true
|
|
}
|
|
|
|
# ============================================
|
|
# Run
|
|
# ============================================
|
|
|
|
function cmd::shell::run() {
|
|
cmd::shell::_banner
|
|
cmd::shell::_setup_history
|
|
cmd::shell::_setup_completion
|
|
|
|
while true; do
|
|
local input
|
|
IFS= read -r -e -p "$(cmd::shell::_prompt)" input || break
|
|
|
|
[[ -z "${input// }" ]] && continue
|
|
|
|
history -s "$input"
|
|
|
|
case "${input%% *}" in
|
|
exit|quit) break ;;
|
|
esac
|
|
|
|
cmd::shell::_execute "$input"
|
|
done
|
|
|
|
cmd::shell::_save_history
|
|
printf "\n Goodbye!\n\n"
|
|
} |