--- # WireGuard Client Configuration Generator # Usage: ansible-playbook playbooks/generate-wireguard-client.yml -e "client_name=michael-laptop" - name: Generate WireGuard Client Configuration hosts: server become: true gather_facts: true vars: # Default values (can be overridden with -e) wireguard_config_dir: "/etc/wireguard" wireguard_interface: "wg0" wireguard_server_endpoint: "{{ ansible_default_ipv4.address }}" wireguard_server_port: 51820 wireguard_vpn_network: "10.8.0.0/24" wireguard_server_ip: "10.8.0.1" # Client output directory (local) client_config_dir: "{{ playbook_dir }}/../wireguard/configs" # Required variable (must be passed via -e) # client_name: "device-name" tasks: - name: Validate client_name is provided assert: that: - client_name is defined - client_name | length > 0 fail_msg: "ERROR: client_name must be provided via -e client_name=" success_msg: "Generating config for client: {{ client_name }}" - name: Validate client_name format (alphanumeric and hyphens only) assert: that: - client_name is match('^[a-zA-Z0-9-]+$') fail_msg: "ERROR: client_name must contain only letters, numbers, and hyphens" success_msg: "Client name format is valid" - name: Check if WireGuard server is configured stat: path: "{{ wireguard_config_dir }}/{{ wireguard_interface }}.conf" register: server_config - name: Fail if server config doesn't exist fail: msg: "WireGuard server config not found. Run setup-wireguard-host.yml first." when: not server_config.stat.exists - name: Read server public key slurp: src: "{{ wireguard_config_dir }}/server_public.key" register: server_public_key_raw - name: Set server public key fact set_fact: server_public_key: "{{ server_public_key_raw.content | b64decode | trim }}" - name: Get next available IP address shell: | # Parse existing peer IPs from wg0.conf existing_ips=$(grep -oP 'AllowedIPs\s*=\s*\K[0-9.]+' {{ wireguard_config_dir }}/{{ wireguard_interface }}.conf 2>/dev/null || echo "") # Start from .2 (server is .1) i=2 while [ $i -le 254 ]; do ip="10.8.0.$i" if ! echo "$existing_ips" | grep -q "^$ip$"; then echo "$ip" exit 0 fi i=$((i + 1)) done echo "ERROR: No free IP addresses" >&2 exit 1 register: next_ip_result changed_when: false - name: Set client IP fact set_fact: client_ip: "{{ next_ip_result.stdout | trim }}" - name: Display client IP assignment debug: msg: "Assigned IP for {{ client_name }}: {{ client_ip }}" - name: Check if client already exists shell: | grep -q "# Client: {{ client_name }}" {{ wireguard_config_dir }}/{{ wireguard_interface }}.conf register: client_exists changed_when: false failed_when: false - name: Warn if client already exists debug: msg: "WARNING: Client '{{ client_name }}' already exists in server config. Creating new keys anyway." when: client_exists.rc == 0 - name: Generate client private key shell: wg genkey register: client_private_key_result changed_when: true no_log: true - name: Generate client public key shell: echo "{{ client_private_key_result.stdout }}" | wg pubkey register: client_public_key_result changed_when: false no_log: true - name: Generate preshared key shell: wg genpsk register: preshared_key_result changed_when: true no_log: true - name: Set client key facts set_fact: client_private_key: "{{ client_private_key_result.stdout | trim }}" client_public_key: "{{ client_public_key_result.stdout | trim }}" preshared_key: "{{ preshared_key_result.stdout | trim }}" no_log: true - name: Create client config directory on control node delegate_to: localhost file: path: "{{ client_config_dir }}" state: directory mode: '0755' become: false - name: Generate client WireGuard configuration delegate_to: localhost copy: content: | [Interface] # Client: {{ client_name }} # Generated: {{ ansible_date_time.iso8601 }} PrivateKey = {{ client_private_key }} Address = {{ client_ip }}/32 DNS = 1.1.1.1, 8.8.8.8 [Peer] # WireGuard Server PublicKey = {{ server_public_key }} PresharedKey = {{ preshared_key }} Endpoint = {{ wireguard_server_endpoint }}:{{ wireguard_server_port }} AllowedIPs = {{ wireguard_vpn_network }} PersistentKeepalive = 25 dest: "{{ client_config_dir }}/{{ client_name }}.conf" mode: '0600' become: false no_log: true - name: Add client peer to server configuration blockinfile: path: "{{ wireguard_config_dir }}/{{ wireguard_interface }}.conf" marker: "# {mark} ANSIBLE MANAGED BLOCK - Client: {{ client_name }}" block: | [Peer] # Client: {{ client_name }} PublicKey = {{ client_public_key }} PresharedKey = {{ preshared_key }} AllowedIPs = {{ client_ip }}/32 no_log: true - name: Reload WireGuard configuration shell: wg syncconf {{ wireguard_interface }} <(wg-quick strip {{ wireguard_interface }}) args: executable: /bin/bash - name: Generate QR code (ASCII) delegate_to: localhost shell: | qrencode -t ansiutf8 < {{ client_config_dir }}/{{ client_name }}.conf > {{ client_config_dir }}/{{ client_name }}.qr.txt become: false changed_when: true - name: Generate QR code (PNG) delegate_to: localhost shell: | qrencode -t png -o {{ client_config_dir }}/{{ client_name }}.qr.png < {{ client_config_dir }}/{{ client_name }}.conf become: false changed_when: true - name: Display QR code for mobile devices delegate_to: localhost shell: cat {{ client_config_dir }}/{{ client_name }}.qr.txt register: qr_code_output become: false changed_when: false - name: Client configuration summary debug: msg: - "=========================================" - "WireGuard Client Configuration Created!" - "=========================================" - "" - "Client: {{ client_name }}" - "IP Address: {{ client_ip }}/32" - "Public Key: {{ client_public_key }}" - "" - "Configuration Files:" - " Config: {{ client_config_dir }}/{{ client_name }}.conf" - " QR Code (ASCII): {{ client_config_dir }}/{{ client_name }}.qr.txt" - " QR Code (PNG): {{ client_config_dir }}/{{ client_name }}.qr.png" - "" - "Server Configuration:" - " Endpoint: {{ wireguard_server_endpoint }}:{{ wireguard_server_port }}" - " Allowed IPs: {{ wireguard_vpn_network }}" - "" - "Next Steps:" - " Linux/macOS: sudo cp {{ client_config_dir }}/{{ client_name }}.conf /etc/wireguard/ && sudo wg-quick up {{ client_name }}" - " Windows: Import {{ client_name }}.conf in WireGuard GUI" - " iOS/Android: Scan QR code with WireGuard app" - "" - "Test Connection:" - " ping {{ wireguard_server_ip }}" - " curl -k https://{{ wireguard_server_ip }}:8080 # Traefik Dashboard" - "" - "=========================================" - name: Display QR code debug: msg: "{{ qr_code_output.stdout_lines }}"