--- - name: Setup WireGuard VPN Server hosts: production become: yes gather_facts: yes vars: # WireGuard variables are defined in group_vars/production.yml # Can be overridden via -e flag if needed wireguard_port: "{{ wireguard_port_default | default(51820) }}" wireguard_network: "{{ wireguard_network_default | default('10.8.0.0/24') }}" wireguard_server_ip: "{{ wireguard_server_ip_default | default('10.8.0.1') }}" pre_tasks: - name: Optionally load wireguard secrets from vault include_vars: file: "{{ playbook_dir }}/../secrets/production.vault.yml" no_log: yes ignore_errors: yes delegate_to: localhost become: no tasks: - name: Check if WireGuard is already installed command: which wg register: wireguard_installed changed_when: false failed_when: false - name: Update package cache apt: update_cache: yes cache_valid_time: 3600 when: not wireguard_installed.rc == 0 - name: Install WireGuard apt: name: - wireguard - wireguard-tools - qrencode state: present when: not wireguard_installed.rc == 0 notify: restart wireguard - name: Ensure WireGuard config directory exists file: path: "{{ wireguard_config_path }}" state: directory mode: '0700' owner: root group: root - name: Ensure WireGuard client configs directory exists file: path: "{{ wireguard_client_configs_path }}" state: directory mode: '0700' owner: root group: root - name: Check if WireGuard server keys exist stat: path: "{{ wireguard_private_key_file }}" register: server_private_key_exists - name: Generate WireGuard server private key command: "wg genkey" register: server_private_key changed_when: true when: not server_private_key_exists.stat.exists no_log: yes - name: Save WireGuard server private key copy: content: "{{ server_private_key.stdout }}" dest: "{{ wireguard_private_key_file }}" mode: '0600' owner: root group: root when: not server_private_key_exists.stat.exists no_log: yes - name: Read WireGuard server private key slurp: src: "{{ wireguard_private_key_file }}" register: server_private_key_content when: server_private_key_exists.stat.exists - name: Generate WireGuard server public key command: "wg pubkey" args: stdin: "{{ server_private_key.stdout if not server_private_key_exists.stat.exists else server_private_key_content.content | b64decode | trim }}" register: server_public_key changed_when: false when: not server_private_key_exists.stat.exists no_log: yes - name: Get existing server public key shell: "cat {{ wireguard_private_key_file }} | wg pubkey" register: existing_server_public_key changed_when: false when: server_private_key_exists.stat.exists no_log: yes failed_when: false - name: Set server public key fact set_fact: server_public_key_value: "{{ server_public_key.stdout if not server_private_key_exists.stat.exists else existing_server_public_key.stdout }}" - name: Save WireGuard server public key copy: content: "{{ server_public_key_value }}" dest: "{{ wireguard_public_key_file }}" mode: '0644' owner: root group: root - name: Enable IP forwarding sysctl: name: net.ipv4.ip_forward value: '1' state: present sysctl_set: yes reload: yes when: wireguard_enable_ip_forwarding - name: Make IP forwarding persistent lineinfile: path: /etc/sysctl.conf regexp: '^net\.ipv4\.ip_forward' line: 'net.ipv4.ip_forward=1' state: present when: wireguard_enable_ip_forwarding - name: Get server external IP address uri: url: https://api.ipify.org return_content: yes register: server_external_ip changed_when: false failed_when: false - name: Set server external IP from inventory if API fails set_fact: server_external_ip_content: "{{ ansible_host | default(server_external_ip.content | default('')) }}" when: server_external_ip.content is defined - name: Get server external IP from ansible_host set_fact: server_external_ip_content: "{{ ansible_host }}" when: server_external_ip.content is not defined - name: Read server private key for config slurp: src: "{{ wireguard_private_key_file }}" register: server_private_key_file_content when: server_private_key_exists.stat.exists - name: Set server private key for template (new key) set_fact: server_private_key_for_config: "{{ server_private_key.stdout }}" when: not server_private_key_exists.stat.exists - name: Set server private key for template (existing key) set_fact: server_private_key_for_config: "{{ server_private_key_file_content.content | b64decode | trim }}" when: server_private_key_exists.stat.exists - name: Get network interface name shell: "ip route | grep default | awk '{print $5}' | head -1" register: default_interface changed_when: false failed_when: false - name: Set default interface set_fact: wireguard_interface_name: "{{ default_interface.stdout | default('eth0') }}" - name: Check if WireGuard config exists stat: path: "{{ wireguard_config_file }}" register: wireguard_config_exists - name: Create WireGuard server configuration template: src: "{{ playbook_dir }}/../templates/wireguard-server.conf.j2" dest: "{{ wireguard_config_file }}" mode: '0600' owner: root group: root notify: restart wireguard - name: Check if WireGuard service is enabled systemd: name: "wg-quick@{{ wireguard_interface }}" register: wireguard_service_status failed_when: false changed_when: false - name: Enable WireGuard service systemd: name: "wg-quick@{{ wireguard_interface }}" enabled: yes daemon_reload: yes when: not wireguard_service_status.status.ActiveState is defined or wireguard_service_status.status.ActiveState != 'active' - name: Start WireGuard service systemd: name: "wg-quick@{{ wireguard_interface }}" state: started notify: restart wireguard - name: Check if UFW firewall is installed command: which ufw register: ufw_installed changed_when: false failed_when: false - name: Verify SSH access is allowed in UFW command: "ufw status | grep -q '22/tcp' || echo 'SSH not found'" register: ssh_ufw_check changed_when: false failed_when: false when: ufw_installed.rc == 0 - name: Warn if SSH is not explicitly allowed debug: msg: | ?? WARNING: SSH (port 22) might not be explicitly allowed in UFW! Please ensure SSH access is configured before proceeding. Run: sudo ufw allow 22/tcp when: ufw_installed.rc == 0 and 'SSH not found' in ssh_ufw_check.stdout - name: Allow WireGuard port in UFW firewall ufw: rule: allow port: "{{ wireguard_port }}" proto: udp comment: "WireGuard VPN" when: ufw_installed.rc == 0 - name: Allow WireGuard port in UFW firewall (alternative) shell: "ufw allow {{ wireguard_port }}/udp comment 'WireGuard VPN'" when: ufw_installed.rc == 0 failed_when: false changed_when: false - name: Check WireGuard status command: "wg show {{ wireguard_interface }}" register: wireguard_status changed_when: false failed_when: false - name: Display WireGuard status debug: msg: | WireGuard Status: {{ wireguard_status.stdout if wireguard_status.rc == 0 else 'WireGuard interface not active' }} - name: Display server public key debug: msg: | ======================================== WireGuard Server Setup Complete! ======================================== Server Public Key: {{ server_public_key_value }} Server IP: {{ wireguard_server_ip }} Server Endpoint: {{ server_external_ip_content }}:{{ wireguard_port }} Network: {{ wireguard_network }} To add a client, run: ansible-playbook -i inventory/production.yml playbooks/add-wireguard-client.yml -e "client_name=myclient" Client configs are stored in: {{ wireguard_client_configs_path }}/ ======================================== handlers: - name: restart wireguard systemd: name: "wg-quick@{{ wireguard_interface }}" state: restarted