Ostatnio aktywny 2 months ago

add.command.sh Surowy
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 ip=""
121 local tunnel=""
122 local guest=false
123 local presets=()
124 local show_config=false
125 local show_qr=false
126
127 # Parse flags
128 while [[ $# -gt 0 ]]; do
129 case "$1" in
130 --name) name="$2"; shift 2 ;;
131 --type) type="$2"; shift 2 ;;
132 --subtype) subtype="$2"; shift 2 ;;
133 --ip) ip="$2"; shift 2 ;;
134 --preset) presets+=("$2"); shift 2 ;;
135 --guest) guest=true; shift ;;
136 --tunnel) tunnel="$2"; shift 2 ;;
137 --show-config) show_config=true; shift ;;
138 --show-qr) show_qr=true; shift ;;
139 --help) cmd::add::help; return ;;
140 *)
141 log::error "Unknown flag: $1"
142 cmd::add::help
143 return 1
144 ;;
145 esac
146 done
147
148 # --guest shorthand
149 if $guest; then
150 type="guest"
151 fi
152
153 # Resolve guest subtype
154 local effective_type="$type"
155 if [[ "$type" == "guest" && -n "$subtype" ]]; then
156 # Validate subtype
157 local valid_subtypes="desktop laptop phone tablet"
158 if ! echo "$valid_subtypes" | grep -qw "$subtype"; then
159 log::error "Invalid subtype: ${subtype} (valid: desktop, laptop, phone, tablet)"
160 return 1
161 fi
162 effective_type="guest-${subtype}"
163 fi
164
165 # Build full client name
166 local full_name="${type}-${name}"
167
168 # Validate
169 cmd::add::validate "$name" "$type" "$ip" "$tunnel" || return 1
170
171 # Resolve tunnel mode — flag > device default
172 if [[ -z "$tunnel" ]]; then
173 tunnel=$(config::default_tunnel_for "$type")
174 fi
175
176 local allowed_ips
177 allowed_ips=$(config::allowed_ips_for "$effective_type" "$tunnel") || return 1
178
179 log::section "Adding client: ${full_name}"
180
181 # Auto-assign IP if not provided
182 if [[ -z "$ip" ]]; then
183 ip=$(ip::next_for_type "$effective_type") || return 1
184 fi
185
186 log::wg_add "Name: ${full_name}"
187 log::wg_add "Type: ${type}"
188 log::wg_add "IP: ${ip}"
189 log::wg_add "Tunnel: ${tunnel} (${allowed_ips})"
190
191 # Generate keys
192 keys::generate_pair "$full_name" || return 1
193
194 # Create client config
195 peers::create_client_config "$full_name" "$effective_type" "$ip" "$allowed_ips" || return 1
196
197 # Store subtype in meta
198 if [[ -n "$subtype" ]]; then
199 peers::set_meta "$full_name" "subtype" "$subtype"
200 fi
201
202 # Add peer to server config
203 local public_key
204 public_key=$(keys::public "$full_name") || return 1
205 peers::add_to_server "$full_name" "$public_key" "$ip" || return 1
206
207 # Apply presets
208 for preset in "${presets[@]}"; do
209 firewall::apply_preset "$preset" "${ip}" || return 1
210 done
211
212 # Apply guest rules if guest type
213 if config::is_guest_type "$type"; then
214 firewall::apply_guest_rules
215 fi
216
217 # Reload WireGuard
218 peers::reload || return 1
219
220 log::wg_success "Client added successfully: ${full_name} (${ip}) [${tunnel} tunnel]"
221
222 # Show QR for mobile by default, config for desktop/laptop
223 local display_type="${subtype:-$type}"
224
225 if cmd::add::is_mobile "$display_type"; then
226 keys::qr "$full_name"
227 else
228 log::section "Client Config"
229 cat "$(ctx::clients)/${full_name}.conf"
230 fi
231}