wgctl/commands/add.command.sh
2026-05-06 23:02:12 +00:00

211 lines
No EOL
5.4 KiB
Bash

#!/usr/bin/env bash
# ============================================
# Lifecycle
# ============================================
function cmd::add::on_load() {
flag::register --name
flag::register --type
flag::register --ip
flag::register --preset
flag::register --guest
flag::register --tunnel
flag::register --show-config
flag::register --show-qr
}
# ============================================
# Help
# ============================================
function cmd::add::help() {
cat <<EOF
Usage: wgctl add --name <name> --type <type> [options]
Add a new WireGuard client.
Options:
--name <name> Client name (e.g. nuno)
--type <type> Device type: desktop, laptop, phone, tablet, guest
--ip <ip> Override auto-assigned IP (optional)
--preset <name> Apply a firewall preset (repeatable)
--guest Shorthand for --type guest
--tunnel <mode> Tunnel mode: split (default) or full
--show-config Shows the WireGuard peer config
--show-qr Shows the WireGuard config in a QR Code
Device Types and Subnets:
desktop 10.1.1.x
laptop 10.1.2.x
phone 10.1.3.x
tablet 10.1.4.x
guest 10.1.100.x
Tunnel Modes:
split Route only VPN subnet + LAN through WireGuard (default)
full Route all traffic through WireGuard
Examples:
wgctl add --name nuno --type phone
wgctl add --name nuno --type laptop --ip 10.1.2.5
wgctl add --name nuno --type phone --tunnel full
wgctl add --name guest1 --type phone --guest
wgctl add --name restricted --type desktop --preset no-docker --preset no-proxmox
EOF
}
# ============================================
# Validation
# ============================================
function cmd::add::validate() {
local name="$1"
local type="$2"
local ip="$3"
local tunnel="$4"
if [[ -z "$name" ]]; then
log::error "Missing required flag: --name"
return 1
fi
if [[ -z "$type" ]]; then
log::error "Missing required flag: --type"
return 1
fi
if ! config::is_valid_type "$type"; then
log::error "Invalid device type: ${type}"
log::info "Valid types: $(config::device_types | tr ' ' ', ')"
return 1
fi
if [[ -n "$tunnel" && "$tunnel" != "split" && "$tunnel" != "full" ]]; then
log::error "Invalid tunnel mode: ${tunnel} (use 'split' or 'full')"
return 1
fi
if [[ -n "$ip" ]]; then
ip::require_valid "$ip"
fi
local full_name="${type}-${name}"
if [[ -f "$(ctx::clients)/${full_name}.conf" ]]; then
log::error "Client already exists: ${full_name}"
return 1
fi
}
# ============================================
# Display helpers
# ============================================
function cmd::add::is_mobile() {
local type="$1"
[[ "$type" == "phone" || "$type" == "tablet" ]]
}
# ============================================
# Run
# ============================================
function cmd::add::run() {
local name=""
local type=""
local ip=""
local tunnel=""
local guest=false
local presets=()
local show_config=false
local show_qr=false
# Parse flags
while [[ $# -gt 0 ]]; do
case "$1" in
--name) name="$2"; shift 2 ;;
--type) type="$2"; shift 2 ;;
--ip) ip="$2"; shift 2 ;;
--preset) presets+=("$2"); shift 2 ;;
--guest) guest=true; shift ;;
--tunnel) tunnel="$2"; shift 2 ;;
--show-config) show_config=true; shift ;;
--show-qr) show_qr=true; shift ;;
--help) cmd::add::help; return ;;
*)
log::error "Unknown flag: $1"
cmd::add::help
return 1
;;
esac
done
# --guest shorthand
if $guest; then
type="guest"
fi
# Build full client name
local full_name="${type}-${name}"
# Validate
cmd::add::validate "$name" "$type" "$ip" "$tunnel" || return 1
# Resolve tunnel mode — flag > device default
if [[ -z "$tunnel" ]]; then
tunnel=$(config::default_tunnel_for "$type")
fi
local allowed_ips
allowed_ips=$(config::allowed_ips_for "$type" "$tunnel") || return 1
log::section "Adding client: ${full_name}"
# Auto-assign IP if not provided
if [[ -z "$ip" ]]; then
ip=$(ip::next_for_type "$type") || return 1
fi
log::wg_add "Name: ${full_name}"
log::wg_add "Type: ${type}"
log::wg_add "IP: ${ip}"
log::wg_add "Tunnel: ${tunnel} (${allowed_ips})"
# Generate keys
keys::generate_pair "$full_name" || return 1
# Create client config
peers::create_client_config "$full_name" "$type" "$ip" "$allowed_ips" || return 1
# Add peer to server config
local public_key
public_key=$(keys::public "$full_name") || return 1
peers::add_to_server "$full_name" "$public_key" "$ip" || return 1
# Apply presets
for preset in "${presets[@]}"; do
firewall::apply_preset "$preset" "${ip}" || return 1
done
# Apply guest rules if guest type
if [[ "$type" == "guest" ]]; then
firewall::apply_guest_rules
fi
# Reload WireGuard
peers::reload || return 1
log::wg_success "Client added successfully: ${full_name} (${ip}) [${tunnel} tunnel]"
# Show QR for mobile by default, config for desktop/laptop
# --show-config overrides to always show config
if $show_qr; then
keys::qr "$full_name"
elif $show_config || ! cmd::add::is_mobile "$type"; then
log::section "Client Config"
cat "$(ctx::clients)/${full_name}.conf"
else
keys::qr "$full_name"
fi
}