Son aktivite 1 month ago

Revizyon 43ef9d54ba56fdb0c5cbb42eed2bd7d71d9c5969

gistfile1.txt Ham
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
17function 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
27# ============================================
28# Help
29# ============================================
30
31function cmd::subnet::help() {
32 cat <<EOF
33Usage: wgctl subnet <subcommand> [options]
34
35Manage the subnet map.
36
37Subcommands:
38 list List all configured subnets
39 show --name <name> Show details for a subnet
40 add --name <name> Add a new subnet entry
41 --subnet <cidr>
42 [--type <type>]
43 [--tunnel-mode split|full]
44 [--desc <desc>]
45 [--group <parent>]
46 rm --name <name> Remove a subnet (refused if in use)
47 rename --name <old> Rename a subnet (refused if in use)
48 --new-name <new>
49
50Examples:
51 wgctl subnet list
52 wgctl subnet show --name guests
53 wgctl subnet add --name iot-cctv --subnet 10.1.211.0/24 --type iot
54 wgctl subnet add --name desktop --subnet 10.1.101.0/24 --group guests
55 wgctl subnet rm --name iot-cctv
56 wgctl subnet rename --name iot-cctv --new-name cctv
57EOF
58}
59
60# ============================================
61# Run
62# ============================================
63
64function cmd::subnet::run() {
65 local subcmd="${1:-list}"
66 shift || true
67
68 case "$subcmd" in
69 list) cmd::subnet::_list "$@" ;;
70 show) cmd::subnet::_show "$@" ;;
71 add) cmd::subnet::_add "$@" ;;
72 rm) cmd::subnet::_rm "$@" ;;
73 rename) cmd::subnet::_rename "$@" ;;
74 --help) cmd::subnet::help ;;
75 *)
76 log::error "Unknown subcommand '${subcmd}'. Available: list, show, add, rm, rename"
77 return 1
78 ;;
79 esac
80}
81
82# ============================================
83# Subcommands
84# ============================================
85
86function cmd::subnet::_list() {
87 local data
88 data=$(subnet::list_data)
89
90 if [[ -z "$data" ]]; then
91 log::info "No subnets defined."
92 return 0
93 fi
94
95 echo ""
96 local prev_group=""
97 while IFS='|' read -r display_name subnet type_key tunnel_mode desc is_group group_parent; do
98 if [[ "$is_group" == "true" ]]; then
99 # Print group parent header when we encounter first child
100 if [[ "$group_parent" != "$prev_group" ]]; then
101 [[ -n "$prev_group" ]] && ui::subnet::group_separator
102 ui::subnet::row_group_parent "$group_parent"
103 prev_group="$group_parent"
104 fi
105 ui::subnet::row_group_child "$type_key" "$subnet" "$tunnel_mode"
106 else
107 # Scalar entry
108 [[ -n "$prev_group" ]] && ui::subnet::group_separator
109 prev_group=""
110 ui::subnet::row_scalar "$display_name" "$subnet" "$tunnel_mode"
111 fi
112 done <<< "$data"
113 echo ""
114}
115
116function cmd::subnet::_maybe_group_separator() {
117 local is_group="${1:-}" group_parent="${2:-}" prev_group="${3:-}"
118 if [[ "$is_group" == "true" && "$group_parent" != "$prev_group" && -n "$prev_group" ]]; then
119 ui::subnet::group_separator
120 elif [[ "$is_group" == "false" && -n "$prev_group" ]]; then
121 ui::subnet::group_separator
122 fi
123}
124
125function cmd::subnet::_update_prev_group() {
126 local is_group="${1:-}" group_parent="${2:-}" prev_group="${3:-}"
127 if [[ "$is_group" == "true" ]]; then
128 echo "$group_parent"
129 else
130 echo ""
131 fi
132}
133
134function cmd::subnet::_show() {
135 local name=""
136 while [[ $# -gt 0 ]]; do
137 case "$1" in
138 --name) name="$2"; shift 2 ;;
139 --help) cmd::subnet::help; return ;;
140 *) log::error "Unknown flag: $1"; return 1 ;;
141 esac
142 done
143
144 [[ -z "$name" ]] && { log::error "Missing required flag: --name"; return 1; }
145 subnet::require_exists "$name" || return 1
146
147 local data
148 data=$(subnet::show_data "$name")
149
150 local is_group="false"
151 while IFS='|' read -r key val rest; do
152 case "$key" in
153 name)
154 ui::subnet::detail "$val" "$is_group"
155 ;;
156 is_group)
157 is_group="$val"
158 # Only print type once
159 if [[ "$val" == "true" ]]; then
160 ui::subnet::detail_field "Type" "group"
161 ui::subnet::child_header
162 else
163 ui::subnet::detail_field "Type" "scalar"
164 fi
165 ;;
166 subnet) ui::subnet::detail_field "Subnet" "$val" ;;
167 type) ui::subnet::detail_field "Device type" "$val" ;;
168 tunnel_mode) ui::subnet::detail_field "Tunnel" "$val" ;;
169 desc) ui::subnet::detail_field "Description" "$val" ;;
170 child)
171 local c_type="$val"
172 local c_subnet c_tunnel c_desc
173 c_subnet=$(echo "$rest" | cut -d'|' -f1)
174 c_tunnel=$(echo "$rest" | cut -d'|' -f2)
175 c_desc=$(echo "$rest" | cut -d'|' -f3)
176 ui::subnet::child_row "$c_type" "$c_subnet" "$c_tunnel" "$c_desc"
177 ;;
178 esac
179 done <<< "$data"
180
181 local peers_using
182 peers_using=$(subnet::peers_using "$name")
183 ui::subnet::peers_in_use "$peers_using"
184}
185
186function cmd::subnet::_add() {
187 local name="" cidr="" type_key="" tunnel_mode="split" desc="" group_parent=""
188
189 while [[ $# -gt 0 ]]; do
190 case "$1" in
191 --name) name="$2"; shift 2 ;;
192 --subnet) cidr="$2"; shift 2 ;;
193 --type) type_key="$2"; shift 2 ;;
194 --tunnel-mode) tunnel_mode="$2"; shift 2 ;;
195 --desc) desc="$2"; shift 2 ;;
196 --group) group_parent="$2"; shift 2 ;;
197 --help) cmd::subnet::help; return ;;
198 *) log::error "Unknown flag: $1"; return 1 ;;
199 esac
200 done
201
202 [[ -z "$name" ]] && { log::error "Missing required flag: --name"; return 1; }
203 [[ -z "$cidr" ]] && { log::error "Missing required flag: --subnet"; return 1; }
204
205 cmd::subnet::_validate_tunnel_mode "$tunnel_mode" || return 1
206 cmd::subnet::_validate_cidr "$cidr" || return 1
207
208 json::subnet_add "$(ctx::subnets)" "$name" "$cidr" \
209 "${type_key:-$name}" "$tunnel_mode" "$desc" "$group_parent"
210
211 log::ok "Subnet '${name}' added (${cidr})"
212}
213
214function cmd::subnet::_rm() {
215 local name=""
216 while [[ $# -gt 0 ]]; do
217 case "$1" in
218 --name) name="$2"; shift 2 ;;
219 --help) cmd::subnet::help; return ;;
220 *) log::error "Unknown flag: $1"; return 1 ;;
221 esac
222 done
223
224 [[ -z "$name" ]] && { log::error "Missing required flag: --name"; return 1; }
225 subnet::require_exists "$name" || return 1
226
227 local peers_using
228 peers_using=$(subnet::peers_using "$name")
229
230 if [[ -n "$peers_using" ]]; then
231 log::error "Cannot remove subnet '${name}' — in use by: ${peers_using//,/, }"
232 log::error "Migrate or remove those peers first."
233 return 1
234 fi
235
236 json::subnet_remove "$(ctx::subnets)" "$name" ""
237 log::ok "Subnet '${name}' removed"
238}
239
240function cmd::subnet::_rename() {
241 local name="" new_name=""
242 while [[ $# -gt 0 ]]; do
243 case "$1" in
244 --name) name="$2"; shift 2 ;;
245 --new-name) new_name="$2"; shift 2 ;;
246 --help) cmd::subnet::help; return ;;
247 *) log::error "Unknown flag: $1"; return 1 ;;
248 esac
249 done
250
251 [[ -z "$name" ]] && { log::error "Missing required flag: --name"; return 1; }
252 [[ -z "$new_name" ]] && { log::error "Missing required flag: --new-name"; return 1; }
253 subnet::require_exists "$name" || return 1
254
255 local peers_using
256 peers_using=$(subnet::peers_using "$name")
257 if [[ -n "$peers_using" ]]; then
258 log::error "Cannot rename subnet '${name}' — configs already distributed to: ${peers_using//,/, }"
259 log::error "Client configs reference the subnet CIDR which cannot change after distribution."
260 return 1
261 fi
262
263 json::subnet_rename "$(ctx::subnets)" "$name" "$new_name" ""
264 log::ok "Subnet '${name}' renamed to '${new_name}'"
265}
266
267# ============================================
268# Validation Helpers
269# ============================================
270
271function cmd::subnet::_validate_tunnel_mode() {
272 local mode="${1:-}"
273 case "$mode" in
274 split|full) return 0 ;;
275 *)
276 log::error "Invalid --tunnel-mode '${mode}'. Use: split, full"
277 return 1
278 ;;
279 esac
280}
281
282function cmd::subnet::_validate_cidr() {
283 local cidr="${1:-}"
284 if ! echo "$cidr" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/[0-9]+$'; then
285 log::error "Invalid CIDR format: '${cidr}'"
286 return 1
287 fi
288}