- name: Deploy headscale and configure Caddy reverse proxy hosts: spacey become: no vars_files: - ../../infra_vars.yml - ./headscale_vars.yml vars: headscale_domain: "{{ headscale_subdomain }}.{{ root_domain }}" headscale_base_domain: "tailnet.{{ root_domain }}" tasks: - name: Install required packages become: yes apt: name: - wget - gnupg state: present update_cache: yes - name: Download headscale DEB package get_url: url: "https://github.com/juanfont/headscale/releases/download/v{{ headscale_version }}/headscale_{{ headscale_version }}_linux_amd64.deb" dest: /tmp/headscale.deb mode: '0644' - name: Install headscale package become: yes apt: deb: /tmp/headscale.deb state: present - name: Remove temporary DEB file file: path: /tmp/headscale.deb state: absent - name: Ensure headscale user exists become: yes user: name: headscale system: yes shell: /usr/sbin/nologin home: /var/lib/headscale create_home: yes state: present - name: Create headscale data directory become: yes file: path: /var/lib/headscale state: directory owner: headscale group: headscale mode: '0750' - name: Create headscale run directory become: yes file: path: /var/run/headscale state: directory owner: headscale group: headscale mode: '0770' - name: Ensure headscale user owns data directory become: yes file: path: /var/lib/headscale owner: headscale group: headscale recurse: yes mode: '0750' - name: Add counterweight user to headscale group become: yes user: name: counterweight groups: headscale append: yes - name: Create ACL policies file become: yes copy: dest: /etc/headscale/acl.json content: | { "ACLs": [], "Groups": {}, "Hosts": {}, "TagOwners": {}, "Tests": [] } owner: headscale group: headscale mode: '0640' notify: Restart headscale - name: Deploy headscale configuration file become: yes copy: dest: /etc/headscale/config.yaml content: | server_url: https://{{ headscale_domain }} listen_addr: 0.0.0.0:{{ headscale_port }} grpc_listen_addr: 0.0.0.0:{{ headscale_grpc_port }} grpc_allow_insecure: false private_key_path: /var/lib/headscale/private.key noise: private_key_path: /var/lib/headscale/noise_private.key prefixes: v4: 100.64.0.0/10 v6: fd7a:115c:a1e0::/48 derp: server: enabled: true region_id: 999 region_code: "headscale" region_name: "Headscale Embedded DERP" verify_clients: true stun_listen_addr: "0.0.0.0:3478" private_key_path: /var/lib/headscale/derp_server_private.key automatically_add_embedded_derp_region: true urls: - https://controlplane.tailscale.com/derpmap/default database: type: sqlite3 sqlite: path: /var/lib/headscale/db.sqlite unix_socket: /var/run/headscale/headscale.sock unix_socket_permission: "0770" log: level: info format: text policy: path: /etc/headscale/acl.json dns: base_domain: {{ headscale_base_domain | quote }} magic_dns: true search_domains: - {{ headscale_base_domain | quote }} nameservers: global: - 1.1.1.1 - 1.0.0.1 owner: root group: headscale mode: '0640' notify: Restart headscale - name: Test headscale configuration become: yes command: headscale configtest register: headscale_config_test failed_when: headscale_config_test.rc != 0 - name: Display headscale config test results debug: msg: "{{ headscale_config_test.stdout }}" - name: Enable and start headscale service become: yes systemd: name: headscale enabled: yes state: started - name: Wait for headscale unix socket to be ready become: yes wait_for: path: /var/run/headscale/headscale.sock state: present timeout: 60 delay: 2 - name: Create headscale namespace if it doesn't exist become: yes command: headscale users create {{ headscale_namespace }} register: create_namespace_result failed_when: create_namespace_result.rc != 0 and 'already exists' not in create_namespace_result.stderr and 'UNIQUE constraint' not in create_namespace_result.stderr changed_when: create_namespace_result.rc == 0 - name: Allow HTTPS through UFW become: yes ufw: rule: allow port: '443' proto: tcp - name: Allow HTTP through UFW (for Let's Encrypt) become: yes ufw: rule: allow port: '80' proto: tcp - name: Allow STUN through UFW (for DERP server) become: yes ufw: rule: allow port: '3478' proto: udp - name: Ensure Caddy sites-enabled directory exists become: yes file: path: "{{ caddy_sites_dir }}" state: directory owner: root group: root mode: '0755' - name: Ensure Caddyfile includes import directive for sites-enabled become: yes lineinfile: path: /etc/caddy/Caddyfile line: 'import sites-enabled/*' insertafter: EOF state: present backup: yes - name: Create Caddy reverse proxy configuration for headscale become: yes copy: dest: "{{ caddy_sites_dir }}/headscale.conf" content: | {{ headscale_domain }} { reverse_proxy localhost:{{ headscale_port }} } owner: root group: root mode: '0644' - name: Reload Caddy to apply new config become: yes command: systemctl reload caddy handlers: - name: Restart headscale become: yes systemd: name: headscale state: restarted