- name: Join machine to headscale mesh network hosts: all become: yes vars_files: - ../infra_vars.yml - ../services_config.yml vars: headscale_host_name: "spacey" headscale_subdomain: "{{ subdomains.headscale }}" headscale_domain: "https://{{ headscale_subdomain }}.{{ root_domain }}" headscale_namespace: "{{ service_settings.headscale.namespace }}" tasks: - name: Set facts for headscale server connection set_fact: headscale_host: "{{ hostvars.get(headscale_host_name, {}).get('ansible_host', headscale_host_name) }}" headscale_user: "{{ hostvars.get(headscale_host_name, {}).get('ansible_user', 'counterweight') }}" headscale_key: "{{ hostvars.get(headscale_host_name, {}).get('ansible_ssh_private_key_file', '') }}" headscale_port: "{{ hostvars.get(headscale_host_name, {}).get('ansible_port', 22) }}" - name: Get user ID for namespace from headscale server via lapy delegate_to: "{{ groups['lapy'][0] }}" become: no vars: ssh_args: "{{ ('-i ' + headscale_key + ' ' if headscale_key else '') + '-p ' + headscale_port|string }}" shell: > ssh {{ ssh_args }} {{ headscale_user }}@{{ headscale_host }} "sudo headscale users list -o json" register: users_list_result changed_when: false failed_when: users_list_result.rc != 0 - name: Extract user ID from users list set_fact: headscale_user_id: "{{ (users_list_result.stdout | from_json) | selectattr('name', 'equalto', headscale_namespace) | map(attribute='id') | first }}" failed_when: headscale_user_id is not defined or headscale_user_id == '' - name: Generate pre-auth key from headscale server via lapy delegate_to: "{{ groups['lapy'][0] }}" become: no vars: ssh_args: "{{ ('-i ' + headscale_key + ' ' if headscale_key else '') + '-p ' + headscale_port|string }}" shell: > ssh {{ ssh_args }} {{ headscale_user }}@{{ headscale_host }} "sudo headscale preauthkeys create --user {{ headscale_user_id }} --expiration 1m --output json" register: preauth_key_result changed_when: true failed_when: preauth_key_result.rc != 0 - name: Extract auth key from preauth result set_fact: auth_key: "{{ (preauth_key_result.stdout | from_json).key }}" failed_when: auth_key is not defined or auth_key == '' - name: Install required packages for Tailscale apt: name: - curl - ca-certificates - gnupg state: present update_cache: yes - name: Create directory for GPG keyrings file: path: /etc/apt/keyrings state: directory mode: '0755' - name: Download Tailscale GPG key get_url: url: https://pkgs.tailscale.com/stable/debian/bookworm.gpg dest: /etc/apt/keyrings/tailscale.gpg mode: '0644' - name: Add Tailscale repository apt_repository: repo: "deb [signed-by=/etc/apt/keyrings/tailscale.gpg] https://pkgs.tailscale.com/stable/debian {{ ansible_lsb.codename }} main" state: present update_cache: yes - name: Install Tailscale apt: name: tailscale state: present update_cache: yes - name: Enable and start Tailscale service systemd: name: tailscaled enabled: yes state: started - name: Configure Tailscale to use headscale server command: > tailscale up --login-server {{ headscale_domain }} --authkey {{ auth_key }} --accept-dns=true register: tailscale_up_result changed_when: "'already authenticated' not in tailscale_up_result.stdout" failed_when: tailscale_up_result.rc != 0 and 'already authenticated' not in tailscale_up_result.stdout - name: Wait for Tailscale to be fully connected pause: seconds: 2 - name: Display Tailscale status command: tailscale status register: tailscale_status changed_when: false - name: Show Tailscale connection status debug: msg: "{{ tailscale_status.stdout_lines }}" - name: Deny all inbound traffic from Tailscale network interface ufw: rule: deny direction: in interface: tailscale0