最後活躍 1 month ago

nuno 已修改 1 month ago. 還原成這個修訂版本

1 file changed, 313 insertions

add.command.sh(檔案已創建)

@@ -0,0 +1,313 @@
1 + #!/usr/bin/env bash
2 +
3 + # ============================================
4 + # Lifecycle
5 + # ============================================
6 +
7 + function cmd::add::on_load() {
8 + load_module subnet
9 + load_module identity
10 + load_module policy
11 +
12 + flag::register --name
13 + flag::register --identity
14 + flag::register --type
15 + flag::register --subnet
16 + flag::register --rule
17 + flag::register --group
18 + flag::register --ip
19 + flag::register --tunnel
20 + flag::register --show-qr
21 +
22 + # Dynamically register --<subnet_name> as shorthand flags
23 + local subnet_name
24 + while IFS= read -r subnet_name; do
25 + [[ -n "$subnet_name" ]] && flag::register "--${subnet_name}"
26 + done < <(subnet::list_names)
27 + }
28 +
29 + # ============================================
30 + # Help
31 + # ============================================
32 +
33 + function cmd::add::help() {
34 + cat <<EOF
35 + Usage: wgctl add --name <name> --type <type> [options]
36 + or: wgctl add --identity <identity> --type <type> [options]
37 +
38 + Add a new WireGuard client.
39 +
40 + Options:
41 + --name <name> Client name (e.g. nuno) — combined with type: phone-nuno
42 + --identity <name> Identity name — auto-names peer with next available index
43 + --type <type> Device type: desktop, laptop, phone, tablet, server, iot
44 + --subnet <subnet> Subnet to allocate from (default: type-native)
45 + --ip <ip> Override auto-assigned IP (optional)
46 + --tunnel <mode> Tunnel mode: split|full (overrides policy)
47 + --rule <rule> Peer rule (default: from policy default_rule or none)
48 + --group <group> Add to group on creation (group must exist)
49 + --show-qr Show the WireGuard config as a QR code after creation
50 +
51 + Subnet shorthands (equivalent to --subnet <name>):
52 + --guests, --servers, --iot, ... (see: wgctl subnet list)
53 +
54 + Examples:
55 + wgctl add --name nuno --type phone
56 + wgctl add --identity nuno --type phone
57 + wgctl add --name zephyr --type desktop --guests
58 + wgctl add --identity zephyr --type desktop --guests
59 + wgctl add --name visitor --type phone --guests --show-qr
60 + wgctl add --name dev --type laptop --rule dev-01
61 + EOF
62 + }
63 +
64 + # ============================================
65 + # Validation
66 + # ============================================
67 +
68 + function cmd::add::_validate() {
69 + local name="$1" identity="$2" type="$3" ip="$4" tunnel="$5"
70 +
71 + if [[ -z "$name" && -z "$identity" ]]; then
72 + log::error "Missing required flag: --name or --identity"
73 + return 1
74 + fi
75 +
76 + if [[ -z "$type" ]]; then
77 + log::error "Missing required flag: --type"
78 + return 1
79 + fi
80 +
81 + if ! json::subnet_exists "$(ctx::subnets)" "$type" 2>/dev/null; then
82 + log::error "Unknown device type: '${type}'"
83 + log::info "Use 'wgctl subnet list' to see valid types and subnets"
84 + return 1
85 + fi
86 +
87 + if [[ -n "$tunnel" && "$tunnel" != "split" && "$tunnel" != "full" ]]; then
88 + log::error "Invalid tunnel mode: '${tunnel}' (use 'split' or 'full')"
89 + return 1
90 + fi
91 +
92 + if [[ -n "$ip" ]]; then
93 + ip::require_valid "$ip"
94 + fi
95 + }
96 +
97 + function cmd::add::_validate_not_exists() {
98 + local full_name="$1"
99 + if [[ -f "$(ctx::clients)/${full_name}.conf" ]]; then
100 + log::error "Client already exists: ${full_name}"
101 + return 1
102 + fi
103 + }
104 +
105 + # ============================================
106 + # Display helpers
107 + # ============================================
108 +
109 + function cmd::add::is_mobile() {
110 + local type="$1"
111 + [[ "$type" == "phone" || "$type" == "tablet" ]]
112 + }
113 +
114 + # ============================================
115 + # Run
116 + # ============================================
117 +
118 + function cmd::add::run() {
119 + local name="" identity="" type="" subnet_name="" rule="" \
120 + group="" ip="" tunnel="" show_qr=false
121 +
122 + while [[ $# -gt 0 ]]; do
123 + case "$1" in
124 + --name) name="$2"; shift 2 ;;
125 + --identity) identity="$2"; shift 2 ;;
126 + --type) type="$2"; shift 2 ;;
127 + --subnet) subnet_name="$2"; shift 2 ;;
128 + --rule) rule="$2"; shift 2 ;;
129 + --group) group="$2"; shift 2 ;;
130 + --ip) ip="$2"; shift 2 ;;
131 + --tunnel) tunnel="$2"; shift 2 ;;
132 + --show-qr) show_qr=true; shift ;;
133 + --help) cmd::add::help; return ;;
134 + --*)
135 + local flag_name="${1#--}"
136 + if subnet::exists "$flag_name" 2>/dev/null; then
137 + subnet_name="$flag_name"
138 + shift
139 + else
140 + log::error "Unknown flag: $1"
141 + cmd::add::help
142 + return 1
143 + fi
144 + ;;
145 + *)
146 + log::error "Unknown flag: $1"
147 + cmd::add::help
148 + return 1
149 + ;;
150 + esac
151 + done
152 +
153 + cmd::add::_validate "$name" "$identity" "$type" "$ip" "$tunnel" || return 1
154 +
155 + # Resolve full peer name
156 + local full_name
157 + if [[ -n "$identity" ]]; then
158 + full_name=$(identity::next_peer_name "$identity" "$type") || return 1
159 + log::info "Auto-named: ${full_name}"
160 + else
161 + full_name="${type}-${name}"
162 + fi
163 +
164 + cmd::add::_validate_not_exists "$full_name" || return 1
165 +
166 + # Resolve subnet CIDR and canonical type
167 + local resolved_cidr resolved_type
168 + resolved_cidr=$(subnet::resolve_for_add "$type" "$subnet_name") || return 1
169 + resolved_type=$(subnet::type_for_add "$type" "$subnet_name") || return 1
170 +
171 + # Resolve effective policy
172 + local identity_name="${identity:-$(identity::get_name "$full_name")}"
173 + local effective_policy
174 + effective_policy=$(policy::effective "$subnet_name" "$resolved_type" "$identity_name")
175 +
176 + # Resolve tunnel mode — flag overrides policy
177 + if [[ -z "$tunnel" ]]; then
178 + tunnel=$(policy::tunnel_mode "$effective_policy")
179 + fi
180 +
181 + # Resolve peer rule — explicit flag overrides policy default_rule
182 + if [[ -z "$rule" ]]; then
183 + rule=$(policy::default_rule "$effective_policy")
184 + fi
185 +
186 + # Validate rule if set
187 + if [[ -n "$rule" ]]; then
188 + rule::exists "$rule" || { log::error "Rule not found: ${rule}"; return 1; }
189 + fi
190 +
191 + local allowed_ips
192 + allowed_ips=$(config::allowed_ips_for "$tunnel") || return 1
193 +
194 + log::section "Adding client: ${full_name}"
195 +
196 + # Allocate IP
197 + if [[ -n "$ip" ]]; then
198 + subnet::require_ip_valid_for "$resolved_cidr" "$ip" || return 1
199 + else
200 + ip=$(ip::next_for_subnet "$resolved_cidr") || return 1
201 + fi
202 +
203 + cmd::add::_log_plan "$full_name" "$type" "$resolved_type" \
204 + "$subnet_name" "$resolved_cidr" "$ip" "$tunnel" \
205 + "$allowed_ips" "${rule:---}" "$effective_policy"
206 +
207 + keys::generate_pair "$full_name" || return 1
208 + peers::create_client_config "$full_name" "$resolved_type" "$ip" "$allowed_ips" || return 1
209 +
210 + # Write meta — type, subnet, rule (if set)
211 + peers::set_meta "$full_name" "type" "$resolved_type"
212 + if [[ -n "$subnet_name" ]]; then
213 + peers::set_meta "$full_name" "subnet" "$subnet_name"
214 + fi
215 + if [[ -n "$rule" ]]; then
216 + peers::set_meta "$full_name" "rule" "$rule"
217 + fi
218 +
219 + cmd::add::_assign_group "$full_name" "$group"
220 +
221 + local public_key
222 + public_key=$(keys::public "$full_name") || return 1
223 + peers::add_to_server "$full_name" "$public_key" "$ip" || return 1
224 +
225 + # Apply peer rule if set
226 + if [[ -n "$rule" ]]; then
227 + rule::apply "$rule" "$ip" "$full_name" || return 1
228 + fi
229 +
230 + # Auto-attach to identity and apply identity rule if set
231 + identity::auto_attach "$full_name" "$resolved_type"
232 + cmd::add::_apply_identity_rule "$full_name" "$ip" "$identity_name" "$effective_policy" "$rule"
233 +
234 + peers::reload || return 1
235 +
236 + log::wg_success "Client added: ${full_name} (${ip}) [${tunnel} tunnel]"
237 + cmd::add::_show_result "$full_name" "$resolved_type" "$show_qr"
238 + }
239 +
240 + # ============================================
241 + # Internal helpers
242 + # ============================================
243 +
244 + function cmd::add::_log_plan() {
245 + local full_name="${1:-}" type="${2:-}" resolved_type="${3:-}" \
246 + subnet_name="${4:-}" resolved_cidr="${5:-}" ip="${6:-}" \
247 + tunnel="${7:-}" allowed_ips="${8:-}" rule="${9:-}" policy="${10:-}"
248 +
249 + log::wg_add "Name: ${full_name}"
250 + log::wg_add "Type: ${resolved_type}"
251 + [[ -n "$subnet_name" ]] && log::wg_add "Subnet: ${subnet_name} (${resolved_cidr})"
252 + log::wg_add "IP: ${ip}"
253 + log::wg_add "Tunnel: ${tunnel} (${allowed_ips})"
254 + log::wg_add "Endpoint: $(config::endpoint)"
255 + log::wg_add "Rule: ${rule}"
256 + log::wg_add "Policy: ${policy}"
257 + }
258 +
259 + function cmd::add::_assign_group() {
260 + local full_name="${1:-}" group="${2:-}"
261 + [[ -z "$group" ]] && return 0
262 + if ! group::exists "$group"; then
263 + log::wg_warning "Group '${group}' not found — skipping group assignment"
264 + return 0
265 + fi
266 + group::add_peer "$group" "$full_name"
267 + log::wg "Added to group: ${group}"
268 + }
269 +
270 + function cmd::add::_apply_identity_rule() {
271 + local full_name="${1:-}" ip="${2:-}" identity_name="${3:-}" \
272 + effective_policy="${4:-}" peer_rule="${5:-}"
273 +
274 + [[ -z "$identity_name" ]] && return 0
275 +
276 + local rules
277 + rules=$(identity::rules "$identity_name")
278 +
279 + if [[ -z "$rules" ]]; then
280 + # No identity rules — warn if no peer rule either
281 + if [[ -z "$peer_rule" ]]; then
282 + policy::warn_no_rule "$full_name"
283 + fi
284 + return 0
285 + fi
286 +
287 + # Apply all identity rules
288 + rule::_apply_identity_rule "$full_name" "$ip"
289 +
290 + # Warn based on strict_rule
291 + local strict
292 + strict=$(identity::rule_flags "$identity_name" "strict_rule")
293 + if [[ "$strict" == "true" ]]; then
294 + local rule_list
295 + rule_list=$(echo "$rules" | tr '\n' ',' | sed 's/,$//')
296 + policy::warn_strict_rule "$identity_name" "$effective_policy" "$rule_list"
297 + elif [[ -n "$peer_rule" ]]; then
298 + local rule_list
299 + rule_list=$(echo "$rules" | tr '\n' ',' | sed 's/,$//')
300 + policy::warn_additive_rule "$identity_name" "$rule_list" "$peer_rule"
301 + fi
302 + }
303 +
304 + function cmd::add::_show_result() {
305 + local full_name="${1:-}" type="${2:-}" show_qr="${3:-false}"
306 + if $show_qr || cmd::add::is_mobile "$type"; then
307 + log::section "Client QR"
308 + keys::qr "$full_name"
309 + else
310 + log::section "Client Config"
311 + cat "$(ctx::clients)/${full_name}.conf"
312 + fi
313 + }
上一頁 下一頁