Последняя активность 1 month ago

Версия cf0d14433f9f1a22d1df7b43baa63b85d594bc97

hosts.command.sh Исходник
1#!/usr/bin/env bash
2# hosts.command.sh — manage host/IP display name mappings
3
4# ============================================
5# Lifecycle
6# ============================================
7
8function 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
25function cmd::hosts::help() {
26 cat <<EOF
27Usage: wgctl hosts <subcommand> [options]
28
29Manage host display names for IP resolution in logs, watch, and activity.
30Maps IPs, subnets, and ports to human-readable names.
31
32Subcommands:
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
46Options 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
55Options for rm:
56 --force Skip confirmation
57
58Examples:
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
66EOF
67}
68
69# ============================================
70# Run
71# ============================================
72
73function 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
99function 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
175function 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
190function 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
230function 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
267function 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
302function 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}