Última atividade 1 month ago

nuno revisou este gist 1 month ago. Ir para a revisão

1 file changed, 340 insertions

subnet.command.sh(arquivo criado)

@@ -0,0 +1,340 @@
1 + #!/usr/bin/env bash
2 + # subnet.command.sh — manage the subnet map (subnets.json)
3 + #
4 + # Subcommands:
5 + # wgctl subnet list
6 + # wgctl subnet show --name <name>
7 + # wgctl subnet add --name <name> --subnet <cidr> [--type <type>]
8 + # [--tunnel-mode split|full] [--desc <desc>]
9 + # [--group <parent>]
10 + # wgctl subnet rm --name <name>
11 + # wgctl subnet rename --name <old> --new-name <new>
12 +
13 + # ============================================
14 + # Lifecycle
15 + # ============================================
16 +
17 + function cmd::subnet::on_load() {
18 + flag::register --name
19 + flag::register --subnet
20 + flag::register --type
21 + flag::register --tunnel-mode
22 + flag::register --desc
23 + flag::register --group
24 + flag::register --new-name
25 +
26 + command::mixin json_output
27 + }
28 +
29 + # ============================================
30 + # Help
31 + # ============================================
32 +
33 + function cmd::subnet::help() {
34 + cat <<EOF
35 + Usage: wgctl subnet <subcommand> [options]
36 +
37 + Manage the subnet map.
38 +
39 + Subcommands:
40 + list List all configured subnets
41 + show --name <name> Show details for a subnet
42 + add --name <name> Add a new subnet entry
43 + --subnet <cidr>
44 + [--type <type>]
45 + [--tunnel-mode split|full]
46 + [--desc <desc>]
47 + [--group <parent>]
48 + rm --name <name> Remove a subnet (refused if in use)
49 + rename --name <old> Rename a subnet (refused if in use)
50 + --new-name <new>
51 +
52 + Examples:
53 + wgctl subnet list
54 + wgctl subnet show --name guests
55 + wgctl subnet add --name iot-cctv --subnet 10.1.211.0/24 --type iot
56 + wgctl subnet add --name desktop --subnet 10.1.101.0/24 --group guests
57 + wgctl subnet rm --name iot-cctv
58 + wgctl subnet rename --name iot-cctv --new-name cctv
59 + EOF
60 + }
61 +
62 + # ============================================
63 + # Run
64 + # ============================================
65 +
66 + function cmd::subnet::run() {
67 + local subcmd="${1:-list}"
68 + shift || true
69 +
70 + if command::json; then
71 + cmd::subnet::_output_json
72 + return 0
73 + fi
74 +
75 + case "$subcmd" in
76 + list) cmd::subnet::_list "$@" ;;
77 + show) cmd::subnet::_show "$@" ;;
78 + add) cmd::subnet::_add "$@" ;;
79 + rm) cmd::subnet::_rm "$@" ;;
80 + rename) cmd::subnet::_rename "$@" ;;
81 + --help) cmd::subnet::help ;;
82 + *)
83 + log::error "Unknown subcommand '${subcmd}'. Available: list, show, add, rm, rename"
84 + return 1
85 + ;;
86 + esac
87 + }
88 +
89 + # ============================================
90 + # Subcommands
91 + # ============================================
92 +
93 + function cmd::subnet::_list() {
94 + local data
95 + data=$(subnet::list_data | ui::sort_rows 1)
96 +
97 + if [[ -z "$data" ]]; then
98 + log::info "No subnets defined."
99 + return 0
100 + fi
101 +
102 + if display::is_table "subnet_list"; then
103 + cmd::subnet::_render_table "$data"
104 + return 0
105 + fi
106 +
107 + echo ""
108 + local prev_group=""
109 + while IFS='|' read -r display_name subnet type_key tunnel_mode desc is_group group_parent; do
110 + if [[ "$is_group" == "true" ]]; then
111 + # Print group parent header when we encounter first child
112 + if [[ "$group_parent" != "$prev_group" ]]; then
113 + [[ -n "$prev_group" ]] && ui::subnet::group_separator
114 + ui::subnet::row_group_parent "$group_parent"
115 + prev_group="$group_parent"
116 + fi
117 + ui::subnet::row_group_child "$type_key" "$subnet" "$tunnel_mode"
118 + else
119 + # Scalar entry
120 + [[ -n "$prev_group" ]] && ui::subnet::group_separator
121 + prev_group=""
122 + ui::subnet::row_scalar "$display_name" "$subnet" "$tunnel_mode"
123 + fi
124 + done <<< "$data"
125 + echo ""
126 + }
127 +
128 + function cmd::subnet::_render_table() {
129 + local data="${1:-}"
130 + [[ -z "$data" ]] && return 0
131 +
132 + ui::subnet::list_header_table
133 + while IFS='|' read -r type cidr display_name tunnel desc is_group group_parent; do
134 + [[ -z "$type" ]] && continue
135 + ui::subnet::list_row_table "$type" "$cidr" "$tunnel" "$desc"
136 + done <<< "$data"
137 + printf "\n"
138 + }
139 +
140 + function cmd::subnet::_maybe_group_separator() {
141 + local is_group="${1:-}" group_parent="${2:-}" prev_group="${3:-}"
142 + if [[ "$is_group" == "true" && "$group_parent" != "$prev_group" && -n "$prev_group" ]]; then
143 + ui::subnet::group_separator
144 + elif [[ "$is_group" == "false" && -n "$prev_group" ]]; then
145 + ui::subnet::group_separator
146 + fi
147 + }
148 +
149 + function cmd::subnet::_update_prev_group() {
150 + local is_group="${1:-}" group_parent="${2:-}" prev_group="${3:-}"
151 + if [[ "$is_group" == "true" ]]; then
152 + echo "$group_parent"
153 + else
154 + echo ""
155 + fi
156 + }
157 +
158 + function cmd::subnet::_show() {
159 + local name=""
160 + while [[ $# -gt 0 ]]; do
161 + case "$1" in
162 + --name) name="$2"; shift 2 ;;
163 + --help) cmd::subnet::help; return ;;
164 + *) log::error "Unknown flag: $1"; return 1 ;;
165 + esac
166 + done
167 +
168 + [[ -z "$name" ]] && { log::error "Missing required flag: --name"; return 1; }
169 + subnet::require_exists "$name" || return 1
170 +
171 + local data
172 + data=$(subnet::show_data "$name")
173 +
174 + local is_group="false"
175 + local show_name="" show_subnet="" show_tunnel="" show_desc=""
176 +
177 + while IFS='|' read -r key val rest; do
178 + case "$key" in
179 + name) show_name="$val" ;;
180 + is_group) is_group="$val" ;;
181 + subnet) show_subnet="$val" ;;
182 + tunnel_mode) show_tunnel="$val" ;;
183 + desc) show_desc="$val" ;;
184 + esac
185 + done <<< "$data"
186 +
187 + if [[ "$is_group" == "true" ]]; then
188 + # Group display
189 + ui::subnet::show_group "$show_name"
190 +
191 + while IFS='|' read -r key val rest; do
192 + [[ "$key" != "child" ]] && continue
193 + local c_type="$val"
194 + local c_subnet c_tunnel c_desc
195 + c_subnet=$(echo "$rest" | cut -d'|' -f1)
196 + c_tunnel=$(echo "$rest" | cut -d'|' -f2)
197 + c_desc=$(echo "$rest" | cut -d'|' -f3)
198 + ui::subnet::show_child_row "$c_type" "$c_subnet" "$c_tunnel" "$c_desc"
199 + done <<< "$data"
200 +
201 + local peers_using
202 + peers_using=$(subnet::peers_using "$name")
203 + ui::subnet::show_peers_annotated "$peers_using" "$(ctx::subnets)"
204 + else
205 + # Scalar display
206 + ui::subnet::show_scalar "$show_name" "$show_subnet" "$show_tunnel" "$show_desc"
207 +
208 + local peers_using
209 + peers_using=$(subnet::peers_using "$name")
210 + ui::subnet::show_peers "$peers_using"
211 + fi
212 +
213 + echo ""
214 + }
215 +
216 + function cmd::subnet::_add() {
217 + local name="" cidr="" type_key="" tunnel_mode="split" desc="" group_parent=""
218 +
219 + while [[ $# -gt 0 ]]; do
220 + case "$1" in
221 + --name) name="$2"; shift 2 ;;
222 + --subnet) cidr="$2"; shift 2 ;;
223 + --type) type_key="$2"; shift 2 ;;
224 + --tunnel-mode) tunnel_mode="$2"; shift 2 ;;
225 + --desc) desc="$2"; shift 2 ;;
226 + --group) group_parent="$2"; shift 2 ;;
227 + --help) cmd::subnet::help; return ;;
228 + *) log::error "Unknown flag: $1"; return 1 ;;
229 + esac
230 + done
231 +
232 + [[ -z "$name" ]] && { log::error "Missing required flag: --name"; return 1; }
233 + [[ -z "$cidr" ]] && { log::error "Missing required flag: --subnet"; return 1; }
234 +
235 + cmd::subnet::_validate_tunnel_mode "$tunnel_mode" || return 1
236 + cmd::subnet::_validate_cidr "$cidr" || return 1
237 +
238 + json::subnet_add "$(ctx::subnets)" "$name" "$cidr" \
239 + "${type_key:-$name}" "$tunnel_mode" "$desc" "$group_parent"
240 +
241 + log::ok "Subnet '${name}' added (${cidr})"
242 + }
243 +
244 + function cmd::subnet::_rm() {
245 + local name=""
246 + while [[ $# -gt 0 ]]; do
247 + case "$1" in
248 + --name) name="$2"; shift 2 ;;
249 + --help) cmd::subnet::help; return ;;
250 + *) log::error "Unknown flag: $1"; return 1 ;;
251 + esac
252 + done
253 +
254 + [[ -z "$name" ]] && { log::error "Missing required flag: --name"; return 1; }
255 + subnet::require_exists "$name" || return 1
256 +
257 + local peers_using
258 + peers_using=$(subnet::peers_using "$name")
259 +
260 + if [[ -n "$peers_using" ]]; then
261 + log::error "Cannot remove subnet '${name}' — in use by: ${peers_using//,/, }"
262 + log::error "Migrate or remove those peers first."
263 + return 1
264 + fi
265 +
266 + json::subnet_remove "$(ctx::subnets)" "$name" ""
267 + log::ok "Subnet '${name}' removed"
268 + }
269 +
270 + function cmd::subnet::_rename() {
271 + local name="" new_name=""
272 + while [[ $# -gt 0 ]]; do
273 + case "$1" in
274 + --name) name="$2"; shift 2 ;;
275 + --new-name) new_name="$2"; shift 2 ;;
276 + --help) cmd::subnet::help; return ;;
277 + *) log::error "Unknown flag: $1"; return 1 ;;
278 + esac
279 + done
280 +
281 + [[ -z "$name" ]] && { log::error "Missing required flag: --name"; return 1; }
282 + [[ -z "$new_name" ]] && { log::error "Missing required flag: --new-name"; return 1; }
283 + subnet::require_exists "$name" || return 1
284 +
285 + local peers_using
286 + peers_using=$(subnet::peers_using "$name")
287 + if [[ -n "$peers_using" ]]; then
288 + log::error "Cannot rename subnet '${name}' — configs already distributed to: ${peers_using//,/, }"
289 + log::error "Client configs reference the subnet CIDR which cannot change after distribution."
290 + return 1
291 + fi
292 +
293 + json::subnet_rename "$(ctx::subnets)" "$name" "$new_name" ""
294 + log::ok "Subnet '${name}' renamed to '${new_name}'"
295 + }
296 +
297 + # ============================================
298 + # Validation Helpers
299 + # ============================================
300 +
301 + function cmd::subnet::_validate_tunnel_mode() {
302 + local mode="${1:-}"
303 + case "$mode" in
304 + split|full) return 0 ;;
305 + *)
306 + log::error "Invalid --tunnel-mode '${mode}'. Use: split, full"
307 + return 1
308 + ;;
309 + esac
310 + }
311 +
312 + function cmd::subnet::_validate_cidr() {
313 + local cidr="${1:-}"
314 + if ! echo "$cidr" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/[0-9]+$'; then
315 + log::error "Invalid CIDR format: '${cidr}'"
316 + return 1
317 + fi
318 + }
319 +
320 + function cmd::subnet::_output_json() {
321 + local data
322 + data=$(subnet::list_data 2>/dev/null)
323 +
324 + local -a subnets=()
325 + while IFS='|' read -r type cidr display_name tunnel_mode desc is_group group_parent; do
326 + [[ -z "$type" ]] && continue
327 +
328 + local is_group_json="false"
329 + [[ "$is_group" == "true" ]] && is_group_json="true"
330 +
331 + subnets+=("$(printf '{"type":"%s","cidr":"%s","display_name":"%s","tunnel_mode":"%s","desc":"%s","is_group":%s,"group_parent":"%s"}' \
332 + "$type" "$cidr" "$display_name" "$tunnel_mode" \
333 + "$desc" "$is_group_json" "${group_parent:-}")")
334 + done <<< "$data"
335 +
336 + local count=${#subnets[@]}
337 + local array
338 + array=$(printf '%s\n' "${subnets[@]:-}" | paste -sd ',' -)
339 + printf '{"subnets":[%s]}' "${array:-}" | json::envelope "subnet list" "$count"
340 + }
Próximo Anterior