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 firewall::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 }