Naposledy aktivní 2 months ago

Revize 665e874e2514ac236f83bbf1b12451b4ae0e0428

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