Naposledy aktivní 1 month ago

gistfile1.txt 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 --rule
12 flag::register --group
13 flag::register --ip
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 --subtype <subtype> Guest subtype: desktop, laptop, phone, tablet (mostly used for guest)
34 --ip <ip> Override auto-assigned IP (optional)
35 --guest Shorthand for --type guest
36 --tunnel <mode> Tunnel mode: split|full (default: split)
37 --rule <rule> Assign rule on creation (default: user, guest types: guest)
38 --group <group> Add to group on creation (group must exist)
39 --show-config Shows the WireGuard peer config
40 --show-qr Shows the WireGuard config in a QR Code
41
42Device Types and Subnets:
43 desktop 10.1.1.x
44 laptop 10.1.2.x
45 phone 10.1.3.x
46 tablet 10.1.4.x
47 guest 10.1.100.x
48
49Rules:
50 Automatically assigned based on type (guest → guest rule, others → user rule).
51 Override with --rule. Manage rules with: wgctl rule help
52
53Tunnel Modes:
54 split Route only VPN subnet + LAN through WireGuard (default)
55 full Route all traffic through WireGuard
56
57Examples:
58 wgctl add --name nuno --type phone
59 wgctl add --name nuno --type laptop --ip 10.1.2.5
60 wgctl add --name nuno --type phone --tunnel full
61 wgctl add --name guest1 --type phone --guest
62 wgctl add --name restricted --type desktop
63 wgctl add --name dev --type laptop --rule dev-01
64 wgctl add --name visitor --type guest --show-qr
65EOF
66}
67
68# ============================================
69# Validation
70# ============================================
71
72function cmd::add::validate() {
73 local name="$1"
74 local type="$2"
75 local ip="$3"
76 local tunnel="$4"
77
78 if [[ -z "$name" ]]; then
79 log::error "Missing required flag: --name"
80 return 1
81 fi
82
83 if [[ -z "$type" ]]; then
84 log::error "Missing required flag: --type"
85 return 1
86 fi
87
88 if ! config::is_valid_type "$type"; then
89 log::error "Invalid device type: ${type}"
90 log::info "Valid types: $(config::device_types | tr ' ' ', ')"
91 return 1
92 fi
93
94 if [[ -n "$tunnel" && "$tunnel" != "split" && "$tunnel" != "full" ]]; then
95 log::error "Invalid tunnel mode: ${tunnel} (use 'split' or 'full')"
96 return 1
97 fi
98
99 if [[ -n "$ip" ]]; then
100 ip::require_valid "$ip"
101 fi
102
103 local full_name="${type}-${name}"
104
105 if [[ -f "$(ctx::clients)/${full_name}.conf" ]]; then
106 log::error "Client already exists: ${full_name}"
107 return 1
108 fi
109}
110
111# ============================================
112# Display helpers
113# ============================================
114
115function cmd::add::is_mobile() {
116 local type="$1"
117 [[ "$type" == "phone" || "$type" == "tablet" ]]
118}
119
120# ============================================
121# Run
122# ============================================
123
124function cmd::add::run() {
125 local name=""
126 local type=""
127 local subtype=""
128 local rule=""
129 local group=""
130 local ip=""
131 local tunnel=""
132 local guest=false
133 local show_config=false
134 local show_qr=false
135
136 # Parse flags
137 while [[ $# -gt 0 ]]; do
138 case "$1" in
139 --name) name="$2"; shift 2 ;;
140 --type) type="$2"; shift 2 ;;
141 --subtype) subtype="$2"; shift 2 ;;
142 --rule) rule="$2"; shift 2 ;;
143 --group) group="$2"; shift 2 ;;
144 --ip) ip="$2"; shift 2 ;;
145 --guest) guest=true; shift ;;
146 --tunnel) tunnel="$2"; shift 2 ;;
147 --show-config) show_config=true; shift ;;
148 --show-qr) show_qr=true; shift ;;
149 --help) cmd::add::help; return ;;
150 *)
151 log::error "Unknown flag: $1"
152 cmd::add::help
153 return 1
154 ;;
155 esac
156 done
157
158 $guest && type="guest"
159
160 local effective_type
161 effective_type=$(cmd::add::_resolve_type "$type" "$subtype") || return 1
162
163 local full_name="${type}-${name}"
164 cmd::add::validate "$name" "$type" "$ip" "$tunnel" || return 1
165
166 [[ -z "$tunnel" ]] && tunnel=$(config::default_tunnel_for "$type")
167 [[ -z "$rule" ]] && rule=$(cmd::add::_default_rule "$type")
168
169 rule::exists "$rule" || { log::error "Rule not found: ${rule}"; return 1; }
170
171 local allowed_ips
172 allowed_ips=$(config::allowed_ips_for "$effective_type" "$tunnel") || return 1
173
174 log::section "Adding client: ${full_name}"
175 [[ -z "$ip" ]] && ip=$(ip::next_for_type "$effective_type") || return 1
176
177 log::wg_add "Name: ${full_name}"
178 log::wg_add "Type: ${type}"
179 log::wg_add "IP: ${ip}"
180 log::wg_add "Tunnel: ${tunnel} (${allowed_ips})"
181 log::wg_add "Endpoint: $(config::endpoint)"
182 log::wg_add "Rule: ${rule:-none}"
183
184 keys::generate_pair "$full_name" || return 1
185 peers::create_client_config "$full_name" "$effective_type" "$ip" "$allowed_ips" || return 1
186 [[ -n "$subtype" ]] && peers::set_meta "$full_name" "subtype" "$subtype"
187
188 if [[ -n "$group" ]]; then
189 if ! group::exists "$group"; then
190 log::wg_warning "Group '${group}' not found — skipping group assignment"
191 else
192 group::add_peer "$group" "$full_name"
193 log::wg "Added to group: ${group}"
194 fi
195 fi
196
197 local public_key
198 public_key=$(keys::public "$full_name") || return 1
199 peers::add_to_server "$full_name" "$public_key" "$ip" || return 1
200
201 [[ -n "$rule" ]] && rule::apply "$rule" "$ip" || return 1
202 peers::reload || return 1
203
204 log::wg_success "Client added successfully: ${full_name} (${ip}) [${tunnel} tunnel]"
205 cmd::add::_show_result "$full_name" "${subtype:-$type}"
206}
207
208function cmd::add::_resolve_type() {
209 local type="$1" subtype="$2"
210 if [[ "$type" == "guest" && -n "$subtype" ]]; then
211 local valid_subtypes="desktop laptop phone tablet"
212 if ! echo "$valid_subtypes" | grep -qw "$subtype"; then
213 log::error "Invalid subtype: ${subtype} (valid: desktop, laptop, phone, tablet)"
214 return 1
215 fi
216 echo "guest-${subtype}"
217 else
218 echo "$type"
219 fi
220}
221
222function cmd::add::_default_rule() {
223 local type="$1"
224 config::is_guest_type "$type" && echo "guest" || echo "user"
225}
226
227function cmd::add::_show_result() {
228 local full_name="$1" display_type="$2"
229 if cmd::add::is_mobile "$display_type"; then
230 keys::qr "$full_name"
231 else
232 log::section "Client Config"
233 cat "$(ctx::clients)/${full_name}.conf"
234 fi
235}