nuno revised this gist 1 month ago. Go to revision
1 file changed, 326 insertions
hosts.command.sh(file created)
| @@ -0,0 +1,326 @@ | |||
| 1 | + | #!/usr/bin/env bash | |
| 2 | + | # hosts.command.sh — manage host/IP display name mappings | |
| 3 | + | ||
| 4 | + | # ============================================ | |
| 5 | + | # Lifecycle | |
| 6 | + | # ============================================ | |
| 7 | + | ||
| 8 | + | function cmd::hosts::on_load() { | |
| 9 | + | flag::register --ip | |
| 10 | + | flag::register --subnet | |
| 11 | + | flag::register --port | |
| 12 | + | flag::register --name | |
| 13 | + | flag::register --desc | |
| 14 | + | flag::register --tag | |
| 15 | + | flag::register --tags | |
| 16 | + | flag::register --force | |
| 17 | + | ||
| 18 | + | command::mixin json_output | |
| 19 | + | } | |
| 20 | + | ||
| 21 | + | # ============================================ | |
| 22 | + | # Help | |
| 23 | + | # ============================================ | |
| 24 | + | ||
| 25 | + | function cmd::hosts::help() { | |
| 26 | + | cat <<EOF | |
| 27 | + | Usage: wgctl hosts <subcommand> [options] | |
| 28 | + | ||
| 29 | + | Manage host display names for IP resolution in logs, watch, and activity. | |
| 30 | + | Maps IPs, subnets, and ports to human-readable names. | |
| 31 | + | ||
| 32 | + | Subcommands: | |
| 33 | + | list List all host entries | |
| 34 | + | show --ip <ip> Show host entry details | |
| 35 | + | show --subnet <cidr> Show subnet entry details | |
| 36 | + | show --port <port> Show port entry details | |
| 37 | + | add --ip <ip> --name <name> Add a host entry | |
| 38 | + | add --subnet <cidr> --name <name> | |
| 39 | + | Add a subnet entry | |
| 40 | + | add --port <port> --name <name> | |
| 41 | + | Add a port entry | |
| 42 | + | rm --ip <ip> Remove a host entry | |
| 43 | + | rm --subnet <cidr> Remove a subnet entry | |
| 44 | + | rm --port <port> Remove a port entry | |
| 45 | + | ||
| 46 | + | Options for add: | |
| 47 | + | --ip <ip> IP address to map | |
| 48 | + | --subnet <cidr> Subnet CIDR to map (e.g. 10.0.0.0/24) | |
| 49 | + | --port <port> Port number to map (e.g. 443) | |
| 50 | + | --name <name> Display name (e.g. vodafone-wan) | |
| 51 | + | --desc <description> Optional description | |
| 52 | + | --tag <tag> Tag (repeatable) | |
| 53 | + | --tags <tag1,tag2> Tags (comma-separated) | |
| 54 | + | ||
| 55 | + | Options for rm: | |
| 56 | + | --force Skip confirmation | |
| 57 | + | ||
| 58 | + | Examples: | |
| 59 | + | wgctl hosts list | |
| 60 | + | wgctl hosts add --ip 148.69.46.73 --name vodafone-wan --desc "Vodafone WAN" | |
| 61 | + | wgctl hosts add --ip 94.63.0.129 --name nuno-home --tags home,isp | |
| 62 | + | wgctl hosts add --subnet 10.0.0.0/24 --name lan --desc "Local LAN" | |
| 63 | + | wgctl hosts add --port 443 --name https | |
| 64 | + | wgctl hosts show --ip 148.69.46.73 | |
| 65 | + | wgctl hosts rm --ip 148.69.46.73 | |
| 66 | + | EOF | |
| 67 | + | } | |
| 68 | + | ||
| 69 | + | # ============================================ | |
| 70 | + | # Run | |
| 71 | + | # ============================================ | |
| 72 | + | ||
| 73 | + | function cmd::hosts::run() { | |
| 74 | + | local subcmd="${1:-list}" | |
| 75 | + | shift || true | |
| 76 | + | ||
| 77 | + | if command::json; then | |
| 78 | + | cmd::hosts::_output_json | |
| 79 | + | return 0 | |
| 80 | + | fi | |
| 81 | + | ||
| 82 | + | case "$subcmd" in | |
| 83 | + | list) cmd::hosts::list "$@" ;; | |
| 84 | + | show) cmd::hosts::show "$@" ;; | |
| 85 | + | add) cmd::hosts::add "$@" ;; | |
| 86 | + | rm|remove|del) cmd::hosts::rm "$@" ;; | |
| 87 | + | help) cmd::hosts::help ;; | |
| 88 | + | *) | |
| 89 | + | log::error "Unknown subcommand: '${subcmd}'" | |
| 90 | + | cmd::hosts::help | |
| 91 | + | return 1 ;; | |
| 92 | + | esac | |
| 93 | + | } | |
| 94 | + | ||
| 95 | + | # ============================================ | |
| 96 | + | # List | |
| 97 | + | # ============================================ | |
| 98 | + | ||
| 99 | + | function cmd::hosts::list() { | |
| 100 | + | local filter_tag="" | |
| 101 | + | ||
| 102 | + | while [[ $# -gt 0 ]]; do | |
| 103 | + | case "$1" in | |
| 104 | + | --tag) filter_tag="$2"; shift 2 ;; | |
| 105 | + | --help) cmd::hosts::help; return ;; | |
| 106 | + | *) log::error "Unknown flag: $1"; return 1 ;; | |
| 107 | + | esac | |
| 108 | + | done | |
| 109 | + | ||
| 110 | + | local hosts_file | |
| 111 | + | hosts_file="$(ctx::hosts)" | |
| 112 | + | ||
| 113 | + | if [[ ! -f "$hosts_file" ]]; then | |
| 114 | + | log::wg_warning "No hosts configured. Use 'wgctl hosts add' to add one." | |
| 115 | + | return 0 | |
| 116 | + | fi | |
| 117 | + | ||
| 118 | + | local data | |
| 119 | + | data=$(json::hosts_list "$hosts_file" 2>/dev/null) | |
| 120 | + | [[ -z "$data" ]] && log::wg_warning "No hosts configured." && return 0 | |
| 121 | + | ||
| 122 | + | # Apply tag filter to data first | |
| 123 | + | local filtered_data="" | |
| 124 | + | while IFS='|' read -r type key name desc tags; do | |
| 125 | + | [[ -z "$type" ]] && continue | |
| 126 | + | [[ -n "$filter_tag" && "$tags" != *"$filter_tag"* ]] && continue | |
| 127 | + | filtered_data+="${type}|${key}|${name}|${desc}|${tags}"$'\n' | |
| 128 | + | done <<< "$data" | |
| 129 | + | ||
| 130 | + | [[ -z "$filtered_data" ]] && log::wg_warning "No hosts found." && return 0 | |
| 131 | + | ||
| 132 | + | # Measure column widths from filtered data | |
| 133 | + | local w_key=15 w_name=16 w_desc=10 | |
| 134 | + | while IFS='|' read -r type key name desc tags; do | |
| 135 | + | [[ -z "$type" ]] && continue | |
| 136 | + | (( ${#key} > w_key )) && w_key=${#key} | |
| 137 | + | (( ${#name} > w_name )) && w_name=${#name} | |
| 138 | + | local desc_len=${#desc} | |
| 139 | + | [[ -z "$desc" ]] && desc_len=1 # "—" = 1 visible char | |
| 140 | + | (( desc_len > w_desc )) && w_desc=$desc_len | |
| 141 | + | done <<< "$filtered_data" | |
| 142 | + | (( w_key += 2 )) | |
| 143 | + | (( w_name += 2 )) | |
| 144 | + | (( w_desc += 2 )) | |
| 145 | + | ||
| 146 | + | log::section "Host Mappings" | |
| 147 | + | echo "" | |
| 148 | + | ||
| 149 | + | if display::is_table "hosts_list"; then | |
| 150 | + | cmd::hosts::_render_table "$data" | |
| 151 | + | return 0 | |
| 152 | + | fi | |
| 153 | + | ||
| 154 | + | local last_type="" found=false | |
| 155 | + | while IFS='|' read -r type key name desc tags; do | |
| 156 | + | [[ -z "$type" ]] && continue | |
| 157 | + | found=true | |
| 158 | + | ||
| 159 | + | # Section header when type changes | |
| 160 | + | if [[ "$type" != "$last_type" ]]; then | |
| 161 | + | [[ -n "$last_type" ]] && echo "" | |
| 162 | + | ui::hosts::section_header "$type" | |
| 163 | + | last_type="$type" | |
| 164 | + | fi | |
| 165 | + | ||
| 166 | + | ui::hosts::list_row "$type" "$key" "$name" "$desc" "$tags" \ | |
| 167 | + | "$w_key" "$w_name" "$w_desc" | |
| 168 | + | ||
| 169 | + | done <<< "$filtered_data" | |
| 170 | + | ||
| 171 | + | $found || log::wg_warning "No hosts configured." | |
| 172 | + | echo "" | |
| 173 | + | } | |
| 174 | + | ||
| 175 | + | function cmd::hosts::_render_table() { | |
| 176 | + | local data="${1:-}" | |
| 177 | + | [[ -z "$data" ]] && return 0 | |
| 178 | + | ||
| 179 | + | ui::hosts::list_header_table | |
| 180 | + | while IFS='|' read -r type key name desc tags; do | |
| 181 | + | [[ -z "$type" ]] && continue | |
| 182 | + | ui::hosts::list_row_table "$type" "$key" "$name" "$desc" "$tags" | |
| 183 | + | done <<< "$data" | |
| 184 | + | } | |
| 185 | + | ||
| 186 | + | # ============================================ | |
| 187 | + | # Show | |
| 188 | + | # ============================================ | |
| 189 | + | ||
| 190 | + | function cmd::hosts::show() { | |
| 191 | + | local ip="" subnet="" port="" | |
| 192 | + | ||
| 193 | + | while [[ $# -gt 0 ]]; do | |
| 194 | + | case "$1" in | |
| 195 | + | --ip) ip="$2"; shift 2 ;; | |
| 196 | + | --subnet) subnet="$2"; shift 2 ;; | |
| 197 | + | --port) port="$2"; shift 2 ;; | |
| 198 | + | --help) cmd::hosts::help; return ;; | |
| 199 | + | *) log::error "Unknown flag: $1"; return 1 ;; | |
| 200 | + | esac | |
| 201 | + | done | |
| 202 | + | ||
| 203 | + | local key entry_type | |
| 204 | + | if [[ -n "$ip" ]]; then key="$ip"; entry_type="host"; fi | |
| 205 | + | if [[ -n "$subnet" ]]; then key="$subnet"; entry_type="subnet"; fi | |
| 206 | + | if [[ -n "$port" ]]; then key="$port"; entry_type="port"; fi | |
| 207 | + | ||
| 208 | + | [[ -z "$key" ]] && log::error "Specify --ip, --subnet, or --port" && return 1 | |
| 209 | + | ||
| 210 | + | hosts::require_exists "$entry_type" "$key" || return 1 | |
| 211 | + | ||
| 212 | + | log::section "${entry_type^}: ${key}" | |
| 213 | + | printf "\n" | |
| 214 | + | ||
| 215 | + | while IFS='|' read -r field val; do | |
| 216 | + | case "$field" in | |
| 217 | + | name) ui::row "Name" "${val:-—}" ;; | |
| 218 | + | desc) ui::row "Description" "${val:-—}" ;; | |
| 219 | + | tags) ui::row "Tags" "${val:-—}" ;; | |
| 220 | + | esac | |
| 221 | + | done < <(json::hosts_show "$(ctx::hosts)" "$key" "$entry_type") | |
| 222 | + | ||
| 223 | + | printf "\n" | |
| 224 | + | } | |
| 225 | + | ||
| 226 | + | # ============================================ | |
| 227 | + | # Add | |
| 228 | + | # ============================================ | |
| 229 | + | ||
| 230 | + | function cmd::hosts::add() { | |
| 231 | + | local ip="" subnet="" port="" name="" desc="" tags=() | |
| 232 | + | ||
| 233 | + | while [[ $# -gt 0 ]]; do | |
| 234 | + | case "$1" in | |
| 235 | + | --ip) ip="$2"; shift 2 ;; | |
| 236 | + | --subnet) subnet="$2"; shift 2 ;; | |
| 237 | + | --port) port="$2"; shift 2 ;; | |
| 238 | + | --name) name="$2"; shift 2 ;; | |
| 239 | + | --desc) desc="$2"; shift 2 ;; | |
| 240 | + | --tag) tags+=("$2"); shift 2 ;; | |
| 241 | + | --tags) IFS=',' read -ra t <<< "$2"; tags+=("${t[@]}"); shift 2 ;; | |
| 242 | + | --help) cmd::hosts::help; return ;; | |
| 243 | + | *) log::error "Unknown flag: $1"; return 1 ;; | |
| 244 | + | esac | |
| 245 | + | done | |
| 246 | + | ||
| 247 | + | [[ -z "$name" ]] && log::error "Missing required flag: --name" && return 1 | |
| 248 | + | ||
| 249 | + | local key entry_type | |
| 250 | + | if [[ -n "$ip" ]]; then key="$ip"; entry_type="host"; fi | |
| 251 | + | if [[ -n "$subnet" ]]; then key="$subnet"; entry_type="subnet"; fi | |
| 252 | + | if [[ -n "$port" ]]; then key="$port"; entry_type="port"; fi | |
| 253 | + | ||
| 254 | + | [[ -z "$key" ]] && log::error "Specify --ip, --subnet, or --port" && return 1 | |
| 255 | + | ||
| 256 | + | local tags_str | |
| 257 | + | tags_str=$(IFS=','; echo "${tags[*]}") | |
| 258 | + | ||
| 259 | + | json::hosts_add "$(ctx::hosts)" "$entry_type" "$key" "$name" "$desc" "$tags_str" | |
| 260 | + | log::wg_success "Added ${entry_type}: ${key} → ${name}" | |
| 261 | + | } | |
| 262 | + | ||
| 263 | + | # ============================================ | |
| 264 | + | # Remove | |
| 265 | + | # ============================================ | |
| 266 | + | ||
| 267 | + | function cmd::hosts::rm() { | |
| 268 | + | local ip="" subnet="" port="" force=false | |
| 269 | + | ||
| 270 | + | while [[ $# -gt 0 ]]; do | |
| 271 | + | case "$1" in | |
| 272 | + | --ip) ip="$2"; shift 2 ;; | |
| 273 | + | --subnet) subnet="$2"; shift 2 ;; | |
| 274 | + | --port) port="$2"; shift 2 ;; | |
| 275 | + | --force) force=true; shift ;; | |
| 276 | + | --help) cmd::hosts::help; return ;; | |
| 277 | + | *) log::error "Unknown flag: $1"; return 1 ;; | |
| 278 | + | esac | |
| 279 | + | done | |
| 280 | + | ||
| 281 | + | local key entry_type | |
| 282 | + | if [[ -n "$ip" ]]; then key="$ip"; entry_type="host"; fi | |
| 283 | + | if [[ -n "$subnet" ]]; then key="$subnet"; entry_type="subnet"; fi | |
| 284 | + | if [[ -n "$port" ]]; then key="$port"; entry_type="port"; fi | |
| 285 | + | ||
| 286 | + | [[ -z "$key" ]] && log::error "Specify --ip, --subnet, or --port" && return 1 | |
| 287 | + | ||
| 288 | + | hosts::require_exists "$entry_type" "$key" || return 1 | |
| 289 | + | ||
| 290 | + | if ! $force; then | |
| 291 | + | read -r -p "Remove ${entry_type} '${key}'? [y/N] " confirm | |
| 292 | + | case "$confirm" in | |
| 293 | + | [yY]*) ;; | |
| 294 | + | *) log::info "Aborted"; return 0 ;; | |
| 295 | + | esac | |
| 296 | + | fi | |
| 297 | + | ||
| 298 | + | json::hosts_remove "$(ctx::hosts)" "$entry_type" "$key" | |
| 299 | + | log::wg_success "Removed ${entry_type}: ${key}" | |
| 300 | + | } | |
| 301 | + | ||
| 302 | + | function cmd::hosts::_output_json() { | |
| 303 | + | local data | |
| 304 | + | data=$(json::hosts_list "$(ctx::hosts)" 2>/dev/null) | |
| 305 | + | ||
| 306 | + | local -a hosts=() | |
| 307 | + | while IFS='|' read -r type ip name desc tags; do | |
| 308 | + | [[ -z "$type" ]] && continue | |
| 309 | + | ||
| 310 | + | local tags_json="[]" | |
| 311 | + | if [[ -n "$tags" ]]; then | |
| 312 | + | local tags_array | |
| 313 | + | tags_array=$(echo "$tags" | tr ',' '\n' | \ | |
| 314 | + | while IFS= read -r t; do [[ -n "$t" ]] && printf '"%s",' "$t"; done | sed 's/,$//') | |
| 315 | + | tags_json="[${tags_array}]" | |
| 316 | + | fi | |
| 317 | + | ||
| 318 | + | hosts+=("$(printf '{"type":"%s","ip":"%s","name":"%s","desc":"%s","tags":%s}' \ | |
| 319 | + | "$type" "$ip" "$name" "$desc" "$tags_json")") | |
| 320 | + | done <<< "$data" | |
| 321 | + | ||
| 322 | + | local count=${#hosts[@]} | |
| 323 | + | local array | |
| 324 | + | array=$(printf '%s\n' "${hosts[@]:-}" | paste -sd ',' -) | |
| 325 | + | printf '{"hosts":[%s]}' "${array:-}" | json::envelope "hosts list" "$count" | |
| 326 | + | } | |
Newer
Older