最后活跃于 2 months ago

修订 9bcec292104477d3bd557cbdfd366dacd6afbdc1

gistfile1.txt 原始文件
1#!/usr/bin/env bash
2
3# ============================================
4# Lifecycle
5# ============================================
6
7function cmd::add::on_load() {
8 flag::register --name
9 flag::register --type
10 flag::register --subtype
11 flag::register --rule
12 flag::register --ip
13 flag::register --preset
14 flag::register --guest
15 flag::register --tunnel
16 flag::register --show-config
17 flag::register --show-qr
18}
19
20# ============================================
21# Help
22# ============================================
23
24function cmd::add::help() {
25 cat <<EOF
26Usage: wgctl add --name <name> --type <type> [options]
27
28Add a new WireGuard client.
29
30Options:
31 --name <name> Client name (e.g. nuno)
32 --type <type> Device type: desktop, laptop, phone, tablet, guest
33 --ip <ip> Override auto-assigned IP (optional)
34 --preset <name> Apply a firewall preset (repeatable)
35 --guest Shorthand for --type guest
36 --tunnel <mode> Tunnel mode: split (default) or full
37 --show-config Shows the WireGuard peer config
38 --show-qr Shows the WireGuard config in a QR Code
39
40Device Types and Subnets:
41 desktop 10.1.1.x
42 laptop 10.1.2.x
43 phone 10.1.3.x
44 tablet 10.1.4.x
45 guest 10.1.100.x
46
47Tunnel Modes:
48 split Route only VPN subnet + LAN through WireGuard (default)
49 full Route all traffic through WireGuard
50
51Examples:
52 wgctl add --name nuno --type phone
53 wgctl add --name nuno --type laptop --ip 10.1.2.5
54 wgctl add --name nuno --type phone --tunnel full
55 wgctl add --name guest1 --type phone --guest
56 wgctl add --name restricted --type desktop --preset no-docker --preset no-proxmox
57EOF
58}
59
60# ============================================
61# Validation
62# ============================================
63
64function cmd::add::validate() {
65 local name="$1"
66 local type="$2"
67 local ip="$3"
68 local tunnel="$4"
69
70 if [[ -z "$name" ]]; then
71 log::error "Missing required flag: --name"
72 return 1
73 fi
74
75 if [[ -z "$type" ]]; then
76 log::error "Missing required flag: --type"
77 return 1
78 fi
79
80 if ! config::is_valid_type "$type"; then
81 log::error "Invalid device type: ${type}"
82 log::info "Valid types: $(config::device_types | tr ' ' ', ')"
83 return 1
84 fi
85
86 if [[ -n "$tunnel" && "$tunnel" != "split" && "$tunnel" != "full" ]]; then
87 log::error "Invalid tunnel mode: ${tunnel} (use 'split' or 'full')"
88 return 1
89 fi
90
91 if [[ -n "$ip" ]]; then
92 ip::require_valid "$ip"
93 fi
94
95 local full_name="${type}-${name}"
96
97 if [[ -f "$(ctx::clients)/${full_name}.conf" ]]; then
98 log::error "Client already exists: ${full_name}"
99 return 1
100 fi
101}
102
103# ============================================
104# Display helpers
105# ============================================
106
107function cmd::add::is_mobile() {
108 local type="$1"
109 [[ "$type" == "phone" || "$type" == "tablet" ]]
110}
111
112# ============================================
113# Run
114# ============================================
115
116function cmd::add::run() {
117 local name=""
118 local type=""
119 local subtype=""
120 local rule=""
121 local ip=""
122 local tunnel=""
123 local guest=false
124 local presets=()
125 local show_config=false
126 local show_qr=false
127
128 # Parse flags
129 while [[ $# -gt 0 ]]; do
130 case "$1" in
131 --name) name="$2"; shift 2 ;;
132 --type) type="$2"; shift 2 ;;
133 --subtype) subtype="$2"; shift 2 ;;
134 --rule) rule="$2"; shift 2 ;;
135 --ip) ip="$2"; shift 2 ;;
136 --preset) presets+=("$2"); shift 2 ;;
137 --guest) guest=true; shift ;;
138 --tunnel) tunnel="$2"; shift 2 ;;
139 --show-config) show_config=true; shift ;;
140 --show-qr) show_qr=true; shift ;;
141 --help) cmd::add::help; return ;;
142 *)
143 log::error "Unknown flag: $1"
144 cmd::add::help
145 return 1
146 ;;
147 esac
148 done
149
150 # --guest shorthand
151 if $guest; then
152 type="guest"
153 fi
154
155 # Resolve guest subtype
156 local effective_type="$type"
157 if [[ "$type" == "guest" && -n "$subtype" ]]; then
158 # Validate subtype
159 local valid_subtypes="desktop laptop phone tablet"
160 if ! echo "$valid_subtypes" | grep -qw "$subtype"; then
161 log::error "Invalid subtype: ${subtype} (valid: desktop, laptop, phone, tablet)"
162 return 1
163 fi
164 effective_type="guest-${subtype}"
165 fi
166
167 # Build full client name
168 local full_name="${type}-${name}"
169
170 # Validate
171 cmd::add::validate "$name" "$type" "$ip" "$tunnel" || return 1
172
173 # Resolve tunnel mode — flag > device default
174 if [[ -z "$tunnel" ]]; then
175 tunnel=$(config::default_tunnel_for "$type")
176 fi
177
178 # Determine rule — explicit flag > type default
179 if [[ -z "$rule" ]]; then
180 if config::is_guest_type "$type"; then
181 rule="guest"
182 else
183 rule="user"
184 fi
185 fi
186
187 # Validate rule if specified
188 if ! rule::exists "$rule"; then
189 log::error "Rule not found: ${rule}"
190 return 1
191 fi
192
193 local allowed_ips
194 allowed_ips=$(config::allowed_ips_for "$effective_type" "$tunnel") || return 1
195
196 log::section "Adding client: ${full_name}"
197
198 # Auto-assign IP if not provided
199 if [[ -z "$ip" ]]; then
200 ip=$(ip::next_for_type "$effective_type") || return 1
201 fi
202
203 log::wg_add "Name: ${full_name}"
204 log::wg_add "Type: ${type}"
205 log::wg_add "IP: ${ip}"
206 log::wg_add "Tunnel: ${tunnel} (${allowed_ips})"
207
208 # Generate keys
209 keys::generate_pair "$full_name" || return 1
210
211 # Create client config
212 peers::create_client_config "$full_name" "$effective_type" "$ip" "$allowed_ips" || return 1
213
214 # Store subtype in meta
215 if [[ -n "$subtype" ]]; then
216 peers::set_meta "$full_name" "subtype" "$subtype"
217 fi
218
219 # Add peer to server config
220 local public_key
221 public_key=$(keys::public "$full_name") || return 1
222 peers::add_to_server "$full_name" "$public_key" "$ip" || return 1
223
224 log::wg_add "Rule: ${rule:-none}"
225
226 # Apply presets
227 for preset in "${presets[@]}"; do
228 firewall::apply_preset "$preset" "${ip}" || return 1
229 done
230
231 # Apply rule
232 if [[ -n "$rule" ]]; then
233 rule::apply "$rule" "$ip" || return 1
234 fi
235
236 # Reload WireGuard
237 peers::reload || return 1
238
239 log::wg_success "Client added successfully: ${full_name} (${ip}) [${tunnel} tunnel]"
240
241 # Show QR for mobile by default, config for desktop/laptop
242 local display_type="${subtype:-$type}"
243
244 if cmd::add::is_mobile "$display_type"; then
245 keys::qr "$full_name"
246 else
247 log::section "Client Config"
248 cat "$(ctx::clients)/${full_name}.conf"
249 fi
250}