250 lines
No EOL
6.4 KiB
Bash
250 lines
No EOL
6.4 KiB
Bash
#!/usr/bin/env bash
|
|
|
|
# ============================================
|
|
# Lifecycle
|
|
# ============================================
|
|
|
|
function cmd::add::on_load() {
|
|
flag::register --name
|
|
flag::register --type
|
|
flag::register --subtype
|
|
flag::register --rule
|
|
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 subtype=""
|
|
local rule=""
|
|
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 ;;
|
|
--subtype) subtype="$2"; shift 2 ;;
|
|
--rule) rule="$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
|
|
|
|
# Resolve guest subtype
|
|
local effective_type="$type"
|
|
if [[ "$type" == "guest" && -n "$subtype" ]]; then
|
|
# Validate subtype
|
|
local valid_subtypes="desktop laptop phone tablet"
|
|
if ! echo "$valid_subtypes" | grep -qw "$subtype"; then
|
|
log::error "Invalid subtype: ${subtype} (valid: desktop, laptop, phone, tablet)"
|
|
return 1
|
|
fi
|
|
effective_type="guest-${subtype}"
|
|
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
|
|
|
|
# Determine rule — explicit flag > type default
|
|
if [[ -z "$rule" ]]; then
|
|
if config::is_guest_type "$type"; then
|
|
rule="guest"
|
|
else
|
|
rule="user"
|
|
fi
|
|
fi
|
|
|
|
# Validate rule if specified
|
|
if ! rule::exists "$rule"; then
|
|
log::error "Rule not found: ${rule}"
|
|
return 1
|
|
fi
|
|
|
|
local allowed_ips
|
|
allowed_ips=$(config::allowed_ips_for "$effective_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 "$effective_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" "$effective_type" "$ip" "$allowed_ips" || return 1
|
|
|
|
# Store subtype in meta
|
|
if [[ -n "$subtype" ]]; then
|
|
peers::set_meta "$full_name" "subtype" "$subtype"
|
|
fi
|
|
|
|
# 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
|
|
|
|
log::wg_add "Rule: ${rule:-none}"
|
|
|
|
# Apply presets
|
|
for preset in "${presets[@]}"; do
|
|
fw::apply_preset "$preset" "${ip}" || return 1
|
|
done
|
|
|
|
# Apply rule
|
|
if [[ -n "$rule" ]]; then
|
|
rule::apply "$rule" "$ip" || return 1
|
|
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
|
|
local display_type="${subtype:-$type}"
|
|
|
|
if cmd::add::is_mobile "$display_type"; then
|
|
keys::qr "$full_name"
|
|
else
|
|
log::section "Client Config"
|
|
cat "$(ctx::clients)/${full_name}.conf"
|
|
fi
|
|
} |