--- - name: "Validate required fields for tunnel {{ _tunnel.interface }}" ansible.builtin.assert: that: - _tunnel.interface is defined - _tunnel.interface | length > 0 - _tunnel.address is defined - _tunnel.address | length > 0 fail_msg: | Tunnel is missing required fields: 'interface' and 'address' are mandatory. See roles/wireguard/defaults/main.yml for configuration instructions. success_msg: "Tunnel {{ _tunnel.interface }} validation passed" - name: "Check if private key exists for {{ _tunnel.interface }}" ansible.builtin.stat: path: "{{ wireguard_config_base_path }}/{{ _tunnel.interface }}.privatekey" register: _tunnel_pkey_file when: _tunnel.private_key is not defined - name: "Generate wireguard keys if not present for {{ _tunnel.interface }}" ansible.builtin.shell: | set -o pipefail {{ wireguard_wg_bin }} genkey | tee {{ wireguard_config_base_path }}/{{ _tunnel.interface }}.privatekey | {{ wireguard_wg_bin }} pubkey > {{ wireguard_config_base_path }}/{{ _tunnel.interface }}.publickey args: executable: /bin/bash changed_when: true when: - _tunnel.private_key is not defined - not _tunnel_pkey_file.stat.exists - name: "Retrieve wireguard private key for {{ _tunnel.interface }}" ansible.builtin.slurp: src: "{{ wireguard_config_base_path }}/{{ _tunnel.interface }}.privatekey" register: _tunnel_private_key_b64 when: _tunnel.private_key is not defined - name: "Set wireguard private key fact from inventory for {{ _tunnel.interface }}" ansible.builtin.set_fact: _tunnel_private_key: "{{ _tunnel.private_key }}" when: _tunnel.private_key is defined - name: "Set wireguard private key fact from host for {{ _tunnel.interface }}" ansible.builtin.set_fact: _tunnel_private_key: "{{ _tunnel_private_key_b64['content'] | b64decode }}" when: _tunnel.private_key is not defined - name: "Resolve effective DNS for {{ _tunnel.interface }}" ansible.builtin.set_fact: _tunnel_effective_dns: "{{ (_tunnel.dns | default('')) if (unbound_custom_lan_records is not defined) else '' }}" - name: "Install wireguard config for {{ _tunnel.interface }}" ansible.builtin.template: src: wireguard.conf.j2 dest: "{{ wireguard_config_base_path }}/{{ _tunnel.interface }}.conf" owner: root group: "{{ wireguard_config_group }}" mode: "0600" notify: Reload wireguard tunnel - name: "Create systemd override directory for wg-quick@{{ _tunnel.interface }}" ansible.builtin.file: path: "/etc/systemd/system/wg-quick@{{ _tunnel.interface }}.service.d" state: directory mode: "0755" when: ansible_facts['os_family'] != 'Darwin' - name: "Deploy systemd override for network dependency for {{ _tunnel.interface }}" ansible.builtin.template: src: systemd-override.conf.j2 dest: "/etc/systemd/system/wg-quick@{{ _tunnel.interface }}.service.d/network-dependency.conf" mode: "0644" notify: Reload systemd when: ansible_facts['os_family'] != 'Darwin' - name: "Enable IP forwarding for {{ _tunnel.interface }}" ansible.builtin.copy: dest: /etc/sysctl.d/99-wireguard.conf content: | net.ipv4.ip_forward = 1 owner: root group: "{{ wireguard_config_group }}" mode: "0644" notify: Apply sysctl when: - _tunnel.server_mode | default(false) - ansible_facts['os_family'] != 'Darwin' - name: "Configure the firewall for {{ _tunnel.interface }}" community.general.ufw: rule: allow port: "{{ _tunnel.port }}" proto: udp direction: in comment: "Wireguard VPN ({{ _tunnel.interface }})" retries: 5 delay: 2 register: _ufw_result until: _ufw_result is succeeded when: - _tunnel.server_mode | default(false) - _tunnel.port is defined - ansible_facts['os_family'] != 'Darwin' - name: "Allow forwarding for {{ _tunnel.interface }}" community.general.ufw: rule: allow route: true interface_in: "{{ item.in }}" interface_out: "{{ item.out }}" comment: "Wireguard VPN forwarding ({{ _tunnel.interface }})" retries: 5 delay: 2 register: _ufw_result until: _ufw_result is succeeded loop: - in: "{{ _tunnel.interface }}" out: "{{ _tunnel.primary_interface | default(wireguard_primary_interface) }}" - in: "{{ _tunnel.primary_interface | default(wireguard_primary_interface) }}" out: "{{ _tunnel.interface }}" when: - _tunnel.server_mode | default(false) - ansible_facts['os_family'] != 'Darwin' - name: "Start and enable wg-quick@{{ _tunnel.interface }}" ansible.builtin.service: name: "wg-quick@{{ _tunnel.interface }}" state: started enabled: true daemon_reload: true when: ansible_facts['os_family'] != 'Darwin' # macOS: no systemd; wg-quick drives wireguard-go directly. Idempotent via the # list of active interfaces rather than a `creates` path. - name: "List active wireguard interfaces (macOS)" ansible.builtin.command: "{{ wireguard_wg_bin }} show interfaces" register: _wg_active_interfaces changed_when: false when: ansible_facts['os_family'] == 'Darwin' - name: "Bring up tunnel (macOS) for {{ _tunnel.interface }}" ansible.builtin.command: "{{ wireguard_darwin_bash }} {{ wireguard_wg_quick_bin }} up {{ _tunnel.interface }}" changed_when: true when: - ansible_facts['os_family'] == 'Darwin' - _tunnel.interface not in _wg_active_interfaces.stdout.split()